其他的答案都涉及到对数组O(n)进行 Shuffle ,这意味着修改原始数组(破坏性的)或复制原始数组(内存密集型的)。 提高内存效率的第一种方法不是对 original 数组进行混洗,而是对索引数组进行混洗。
# 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 ];
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 @picks, $deck->[$idx];
$picks_left--;
}
$num_left--;
$idx++;
}
use List::Util 'shuffle';
@shuffled = shuffle(@list);
如果没有,可以使用Fisher-Yates随机播放。
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;
# 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来优化这个过程(按照这种写法,你需要 last q个元素)。
4条答案
按热度按时间zte4gxcn1#
其他的答案都涉及到对数组
O(n)
进行 Shuffle ,这意味着修改原始数组(破坏性的)或复制原始数组(内存密集型的)。提高内存效率的第一种方法不是对 original 数组进行混洗,而是对索引数组进行混洗。
它至少独立于@deck的内容,但其性能和内存仍然是O(nlogn)。
一种更有效的算法(不一定快,取决于你的数组的大小)是查看数组的每个元素,然后决定它是否会进入数组。这类似于你从文件中随机选择一行,而不是将整个文件读入内存,每一行都有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。你从高位置到低位置工作。与以前不同,你不覆盖。这是how perl5i implements its pick method(即将推出下一个版本)。
为了从本质上理解为什么会这样,以从一个4元素列表中选择2为例,每个元素都应该有1/2的机会被选中。
很简单。下一个元素有1/2的机会已经被选中,在这种情况下,它的机会是1/3。否则它的机会是2/3。
下一个有1/4的机会两个元素都已被挑选(1/2 * 1/2),则它没有机会;1/2的机会只有一个会被选中,那么它就有1/2;剩下的1/4表示没有物品被挑选,在这种情况下是2/2。
最后,对于最后一个项目,有一个1/2的前一个采取了最后的选择。
不完全是证明,但很好地说服自己它的工作。
jmp7cifd2#
从
perldoc perlfaq4
开始:如何随机移动数组
如果您安装了Perl 5.8.0或更高版本,或者安装了Scalar-List-Utils 1.03或更高版本,您可以说:
如果没有,可以使用Fisher-Yates随机播放。
您也可以使用
List::Gen
:j8ag8udp3#
你可以使用Fisher-Yates shuffle algorithm来随机排列你的数组,然后使用前q个元素的切片。
你可以通过在选择了
q
个随机元素之后停止shuffle来优化这个过程(按照这种写法,你需要 last q个元素)。nkhmeac64#
你可以构造第二个数组,大小为P的布尔值,并为选中的数字存储true。当数字被选中时,检查第二个表;如果为“真”,则必须选择下一个。