我有以下代码在PHP中从数组$array
中选择$n
个元素:
shuffle($array);
$result = array_splice($array, 0, $n);
当有一个大数组,但只有很少的元素(例如10000中的5个)时,这会相对较慢,因此我希望优化它,使得不必对所有元素进行洗牌。值必须是唯一的。
我正在寻找最高效的替代方案。我们可以假设$array没有重复,并且索引从0开始。
我有以下代码在PHP中从数组$array
中选择$n
个元素:
shuffle($array);
$result = array_splice($array, 0, $n);
当有一个大数组,但只有很少的元素(例如10000中的5个)时,这会相对较慢,因此我希望优化它,使得不必对所有元素进行洗牌。值必须是唯一的。
我正在寻找最高效的替代方案。我们可以假设$array没有重复,并且索引从0开始。
$randomArray = [];
while (count($randomArray) < 5) {
$randomKey = mt_rand(0, count($array)-1);
$randomArray[$randomKey] = $array[$randomKey];
}
这将快速提供恰好 5 个不重复的元素。键将被保留。
注意:您需要确保 $array 中有 5 个或更多的元素,或添加某种检查以防止无限循环。
n
逐渐接近数组长度,我会担心这将花费很长时间......选中它们后是否有快速的重新索引的方法? - Paul S.n
接近数组长度,则shuffle()或其他类似的解决方案会更好。 - Devon$array
的长度(在while
之外计算),而不是每次调用mt_rand
函数时都重新计算。 - Marten Koetsiershuffle-splice
解决方案相同的结果,在最终选择中还需要一个额外的shuffle
(以及array_values
)。关于这个问题的好文章,如果对你的情况有效,那就没问题,干杯! - Nikos M.$n
个元素执行洗牌操作,其中$n
是要选择的随机元素数量。它还适用于关联数组和稀疏数组。$array
是要处理的数组,$n
是要检索的随机元素数量。$max_index
定义为count($array) - 1 - $iteration
。$max_index
之间的随机数来工作。选择该索引处的键,并将其索引替换为$max_index
处的值,以便不能再次选择,因为在下一次迭代时$max_index
将减少一个并且无法访问。
总之,这是Richard Durstenfeld的Fisher-Yates shuffle,但仅对$n
个元素进行操作,而不是整个数组。function rand_pluck($array, $n) {
$array_keys = array_keys($array);
$array_length = count($array_keys);
$max_index = $array_length -1;
$iterations = min($n, $array_length);
$random_array = array();
while($iterations--) {
$index = mt_rand(0, $max_index);
$value = $array_keys[$index];
$array_keys[$index] = $array_keys[$max_index];
array_push($random_array, $array[$value]);
$max_index--;
}
return $random_array;
}
O(n)
,而是 O(N)
,因为必须使用 array_keys
等等。当然,它比原始的 shuffle
解决方案更快,而且是无偏的(因为它是 shuffle
的一个变体)。我的解决方案严格是 O(n)
,但存在其他一些问题... - Nikos M.shuffle
解决方案同样重要。function random_pick( $a, $n )
{
$N = count($a);
$n = min($n, $N);
$picked = array_fill(0, $n, 0); $backup = array_fill(0, $n, 0);
// partially shuffle the array, and generate unbiased selection simultaneously
// this is a variation on fisher-yates-knuth shuffle
for ($i=0; $i<$n; $i++) // O(n) times
{
$selected = mt_rand( 0, --$N ); // unbiased sampling N * N-1 * N-2 * .. * N-n+1
$value = $a[ $selected ];
$a[ $selected ] = $a[ $N ];
$a[ $N ] = $value;
$backup[ $i ] = $selected;
$picked[ $i ] = $value;
}
// restore partially shuffled input array from backup
// optional step, if needed it can be ignored, e.g $a is passed by value, hence copied
for ($i=$n-1; $i>=0; $i--) // O(n) times
{
$selected = $backup[ $i ];
$value = $a[ $N ];
$a[ $N ] = $a[ $selected ];
$a[ $selected ] = $value;
$N++;
}
return $picked;
}
$randomly_picked = random_pick($my_array, 5);
// or if an associative array is used
$randomly_picked_keys = random_pick(array_keys($my_array), 5);
$randomly_picked = array_intersect_key($my_array, array_flip($randomly_picked_keys));
针对PHP的洗牌算法,还有更多的变体和扩展:
$randomArray = array_rand(array_flip($array), $n);
mt_rand()
生成n个随机数,并将这些值填充到一个新数组中。为了避免返回相同的索引,我们使用实际返回的索引来填充新数组,并且始终检查索引是否存在于新数组中,如果是,则使用while循环遍历它,直到获取重复的索引。最后,我们使用array_values()
获取一个从0开始的数组。$count = count($array) - 1;
$new_array = array();
for($i = 0; $i < $n; $i++) {
$index = mt_rand(0, $count);
while(isset($new_array[$index])) {
$index = mt_rand(0, $count);
}
$new_array[$index] = $array[$index];
}
$new_array = array_values($new_array);
这将只对小的n
显示出与数组洗牌相比的好处,但您可以:
r
n
次,每次将限制减少1
伪代码
arr = []
used = []
for i = 0..n-1:
r = rand 0..len-i
d = 0
for j = 0..used.length-1:
if r >= used[j]:
d += 1
arr.append($array[r + d])
used.append(r)
return arr
array_flip
在大数组上的性能。 - Fabian Schmengler