从 PHP 数组中高效地挑选 n 个随机元素(无随机播放)
Efficiently pick n random elements from PHP array (without shuffle)
我有以下代码从 PHP 中的数组 $array
中选取 $n
个元素:
shuffle($array);
$result = array_splice($array, 0, $n);
给定一个大数组但只有几个元素(例如 10000
中的 5
),这相对较慢,所以我想优化它以便不是所有元素都必须被洗牌。这些值必须是唯一的。
我正在寻找性能最高的替代方案。我们可以假设 $array
没有重复项并且是 0
索引的。
您可以使用 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
$randomArray = [];
while (count($randomArray) < 5) {
$randomKey = mt_rand(0, count($array)-1);
$randomArray[$randomKey] = $array[$randomKey];
}
这将提供恰好 5 个没有重复的元素,而且速度非常快。密钥将被保留。
注意:您必须确保 $array 有 5 个或更多元素或添加某种检查以防止死循环。
诀窍是使用 shuffle 的变体,或者换句话说,部分洗牌。
性能不是唯一的标准,统计效率,即无偏抽样同样重要(与原来的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;
}
注意 算法在 和 space 中严格地 O(n)
,产生 unbiased selections(这是一个partial unbiased shuffling)并产生output,它是具有连续键的正确数组(不需要额外的array_values
等..)
使用示例:
$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 的洗牌的进一步变化和扩展:
- PHP - shuffle only part of an array
- PHP shuffle with seed
- How can I take n elements at random from a Perl array?
此函数仅对 $n
个元素执行随机播放,其中 $n
是您要选择的随机元素的数量。它还适用于关联数组和稀疏数组。 $array
是要处理的数组,$n
是要检索的随机元素的数量。
如果我们将$max_index
定义为count($array) - 1 - $iteration
。
它的工作原理是生成一个介于 0 和 $max_index
之间的随机数。在该索引处选择键,并将其索引替换为 $max_index
处的值,这样它就永远不会被再次选择,因为 $max_index
将在下一次迭代中少一个并且无法访问。
总之 这是 Richard Durstenfeld's 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;
}
我有以下代码从 PHP 中的数组 $array
中选取 $n
个元素:
shuffle($array);
$result = array_splice($array, 0, $n);
给定一个大数组但只有几个元素(例如 10000
中的 5
),这相对较慢,所以我想优化它以便不是所有元素都必须被洗牌。这些值必须是唯一的。
我正在寻找性能最高的替代方案。我们可以假设 $array
没有重复项并且是 0
索引的。
您可以使用 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
$randomArray = [];
while (count($randomArray) < 5) {
$randomKey = mt_rand(0, count($array)-1);
$randomArray[$randomKey] = $array[$randomKey];
}
这将提供恰好 5 个没有重复的元素,而且速度非常快。密钥将被保留。
注意:您必须确保 $array 有 5 个或更多元素或添加某种检查以防止死循环。
诀窍是使用 shuffle 的变体,或者换句话说,部分洗牌。
性能不是唯一的标准,统计效率,即无偏抽样同样重要(与原来的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;
}
注意 算法在 和 space 中严格地 O(n)
,产生 unbiased selections(这是一个partial unbiased shuffling)并产生output,它是具有连续键的正确数组(不需要额外的array_values
等..)
使用示例:
$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 的洗牌的进一步变化和扩展:
- PHP - shuffle only part of an array
- PHP shuffle with seed
- How can I take n elements at random from a Perl array?
此函数仅对 $n
个元素执行随机播放,其中 $n
是您要选择的随机元素的数量。它还适用于关联数组和稀疏数组。 $array
是要处理的数组,$n
是要检索的随机元素的数量。
如果我们将$max_index
定义为count($array) - 1 - $iteration
。
它的工作原理是生成一个介于 0 和 $max_index
之间的随机数。在该索引处选择键,并将其索引替换为 $max_index
处的值,这样它就永远不会被再次选择,因为 $max_index
将在下一次迭代中少一个并且无法访问。
总之 这是 Richard Durstenfeld's 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;
}