我有一个大小为A = [a1,a2,a3,...aP]
的数组P
。我必须从数组A中抽取q
个元素。
我打算使用一个带q
次迭代的循环,并在每次迭代时从A中随机选择一个元素。但是,我怎样才能确保每次迭代时拾取的数字都不同?
答案 0 :(得分:17)
其他答案都涉及改组阵列,即O(n)
。
这意味着修改原始数组(破坏性)或复制原始数组(内存密集型)。
提高内存效率的第一种方法不是对原始数组进行混洗,而是对一组索引进行随机播放。
# Shuffled list of indexes into @deck
my @shuffled_indexes = shuffle(0..$#deck);
# Get just N of them.
my @pick_indexes = @shuffled_indexes[ 0 .. $num_picks - 1 ];
# Pick cards from @deck
my @picks = @deck[ @pick_indexes ];
它至少独立于@deck的内容,但仍然是O(nlogn)性能和O(n)内存。
一个更有效的算法(不一定更快,取决于你的数组现在很大)是查看数组的每个元素并决定它是否会进入数组。这与how you select a random line from a file without reading the whole file into memory类似,每行有1 / N的机会被选中,其中N是行号。所以第一行有1/1的机会(它总是被选中)。下一个是1/2。然后是1/3,依此类推。每个选择将覆盖上一个选择。这导致每一行都有1 / total_lines的机会。
你可以自己解决。一行文件有1/1的机会,所以第一个文件总是被选中。一个双行文件......第一行有1/1然后是1/2的幸存机会,即1/2,第二行有1/2机会。对于三行文件......第一行有1/1的机会被选中,然后有1/2 * 2/3的幸存机会,即2/6或1/3。等等。
该算法的速度为O(n),它迭代一次无序数组,并且不会消耗比存储选择所需的更多内存。
稍作修改,这适用于多个选择。它不是1/$position
机会,而是$picks_left / $position
。每次选择成功时,您都会减少$ picks_left。你从高位到低位工作。与以前不同,您不会覆盖。
my $picks_left = $picks;
my $num_left = @$deck;
my @picks;
my $idx = 0;
while($picks_left > 0 ) { # when we have all our picks, stop
# random number from 0..$num_left-1
my $rand = int(rand($num_left));
# pick successful
if( $rand < $picks_left ) {
push @result, $deck->[$idx];
$picks_left--;
}
$num_left--;
$idx++;
}
这是how perl5i implements its pick method(即将发布的下一篇文章)。
为了理解其中的原因,请以4元素列表中的选择2为例。每个人都有1/2的机会被选中。
1. (2 picks, 4 items): 2/4 = 1/2
足够简单。下一个元素有一个1/2的机会,一个元素已经被选中,在这种情况下,它有可能是1/3。否则它的机会是2/3。做数学......
2. (1 or 2 picks, 3 items): (1/3 * 1/2) + (2/3 * 1/2) = 3/6 = 1/2
接下来有四分之一的机会已经拾取了两个元素(1/2 * 1/2),那么它就没有机会了;只有一个将被挑选的1/2机会,然后它有1/2;剩下的1/4没有任何物品被挑选,在这种情况下它是2/2。
3. (0, 1 or 2 picks, 2 items): (0/2 * 1/4) + (1/2 * 2/4) + (2/2 * 1/4) = 2/8 + 1/4 = 1/2
最后,对于最后一个项目,前一个选择的时间是1/2。
4. (0 or 1 pick, 1 items): (0/1 * 2/4) + (1/1 * 2/4) = 1/2
不完全是证明,但有说服自己有效。
答案 1 :(得分:8)
如何随机播放数组?
如果你安装了Perl 5.8.0或更高版本,或者你有 安装了Scalar-List-Utils 1.03或更高版本,你可以说:
use List::Util 'shuffle'; @shuffled = shuffle(@list);
如果没有,你可以使用Fisher-Yates shuffle。
sub fisher_yates_shuffle { my $deck = shift; # $deck is a reference to an array return unless @$deck; # must not be empty! my $i = @$deck; while (--$i) { my $j = int rand ($i+1); @$deck[$i,$j] = @$deck[$j,$i]; } } # shuffle my mpeg collection # my @mpeg = <audio/*/*.mp3>; fisher_yates_shuffle( \@mpeg ); # randomize @mpeg in place print @mpeg;
您也可以使用List::Gen
:
my $gen = <1..10>;
print "$_\n" for $gen->pick(5); # prints five random numbers
答案 2 :(得分:4)
您可以使用Fisher-Yates shuffle algorithm随机置换数组,然后使用前q个元素的切片。这是来自PerlMonks的代码:
# randomly permutate @array in place
sub fisher_yates_shuffle
{
my $array = shift;
my $i = @$array;
while ( --$i )
{
my $j = int rand( $i+1 );
@$array[$i,$j] = @$array[$j,$i];
}
}
fisher_yates_shuffle( \@array ); # permutes @array in place
你可以通过在选择了q
个随机元素后停止shuffle来优化它。 (这是写的方式,你需要最后 q元素。)
答案 3 :(得分:-1)
你可以构造第二个数组,布尔大小为P,并存储 true 用于拾取的数字。当选择数字时,检查第二个表;如果“真实”,你必须选择下一个。