Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/227.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/arrays/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
高效地从PHP数组中拾取n个随机元素(无需洗牌)_Php_Arrays_Performance_Random_Shuffle - Fatal编程技术网

高效地从PHP数组中拾取n个随机元素(无需洗牌)

高效地从PHP数组中拾取n个随机元素(无需洗牌),php,arrays,performance,random,shuffle,Php,Arrays,Performance,Random,Shuffle,我有以下代码可以从PHP中的数组$array中选择$n元素: shuffle($array); $result = array_splice($array, 0, $n); 给定一个大数组,但只有几个元素(例如10000中的5),这是相对缓慢的,因此我希望对其进行优化,以便不必对所有元素进行洗牌。这些值必须是唯一的 我正在寻找最有效的替代方案。我们可以假设$array没有重复项,并且是0索引的。您可以使用mt_rand()生成n倍的随机数,然后将这些值填充到新数组中。为了避免两次返回同一索引的

我有以下代码可以从PHP中的数组
$array
中选择
$n
元素:

shuffle($array);
$result = array_splice($array, 0, $n);
给定一个大数组,但只有几个元素(例如
10000
中的
5
),这是相对缓慢的,因此我希望对其进行优化,以便不必对所有元素进行洗牌。这些值必须是唯一的


我正在寻找最有效的替代方案。我们可以假设
$array
没有重复项,并且是
0
索引的。

您可以使用
mt_rand()
生成n倍的随机数,然后将这些值填充到新数组中。为了避免两次返回同一索引的情况,我们使用实际返回的索引填充新数组,并始终检查新数组中是否存在索引,如果存在,则使用while循环,只要获得重复的索引。最后,我们使用
array\u 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);
$count=count($array)-1;
$new_array=array();
对于($i=0;$i<$n;$i++){
$index=mt_rand(0,$count);
while(isset($new_数组[$index])){
$index=mt_rand(0,$count);
}
$new_数组[$index]=$array[$index];
}
$new\u array=数组值($new\u 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(计数($randomArray)<5){
    $randomKey=mt_rand(0,计数($array)-1);
    $randomArray[$randomKey]=$array[$randomKey];
    }
    
    这将提供5个元素,没有重复,而且非常快。钥匙将被保留


    注意:您必须确保$array有5个或更多元素,或者添加某种检查以防止无休止的循环。

    诀窍是使用一种变体,或者换句话说,使用部分洗牌

    性能不是唯一的标准,统计效率,即无偏抽样同样重要(就像原始的
    洗牌
    解决方案一样)

    有关PHP洗牌的进一步变化和扩展:


  • 此函数仅对
    $n
    元素执行洗牌,其中
    $n
    是要拾取的随机元素数。它还将用于关联数组和稀疏数组
    $array
    是要处理的数组,
    $n
    是要检索的随机元素数

    如果我们将
    $max\u索引
    定义为
    count($array)-1-$iteration

    它的工作原理是生成一个介于0和
    $max\u index
    之间的随机数。在该索引处拾取键,并将其索引替换为
    $max\u index
    处的值,这样就再也无法拾取该键,因为
    $max\u index
    将在下一次迭代中减少一个,并且无法访问

    总之这是一个数组,但只对
    $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;
    }
    

    如果mt_rand给你两次相同的索引会怎样?@Endijs范围为10000,这是不太可能的,但是我们可以检查它是否已经返回,如果已经重新生成。被否决的帖子,但是在再次阅读代码后,它是错误的,可以重新投票帖子,如果做了一个小的编辑,那么投票是正确的unlocked@NikosM. 好了。看:我也读过了,但是我有点担心
    array\u flip
    在大型数组上的性能。@FabianSchmengler感谢您的博客文章和基准测试。我认为你应该编辑你的问题,为未来的读者简要解释(两个争议中的)哪种解决方案最适合在哪种情况下使用。哦!另外,在你的博客上放一个链接,上面有所有的细节。页面已经存档了,我不得不说这个解决方案更好。随着
    n
    接近数组长度,我担心这需要很长时间。。。有没有一种快速的方法可以在你选择它们之后重新索引它们?@PaulS。这一切都取决于数组的大小。如果
    n
    接近数组长度,则shuffle()或其他类似的解决方案会更好。如果效率确实存在问题,您还可以缓存
    $array
    的长度(在
    之外计算它,而
    )而不是每次调用
    mt_rand
    函数时都计算它。这将在输出数组中生成较大的间隙,而不是连续的键(如
    $n
    随机选取的元素),因为输出数组应该是
    $n
    大小,但示例代码使用原始数组的索引生成数组,例如
    数组(0=>$a1,100=>$a2,…)
    是的,shuffe算法的变化是最好的(类似于我的答案),从性能和统计角度来看,即无偏采样,+1严格地说,这个解决方案不是
    O(n)
    ,而是
    O(n)
    ,因为必须使用
    数组_键
    ,所以它当然比原始的
    洗牌
    解决方案快,而且没有偏见(因为它是
    洗牌
    的变体),我的解决方案严格地说是
    O(n)
    但还有一些其他问题。@NikosM。事实上,
    array\u keys
    在大规模数组(数十万个元素)上的速度非常快。区分时间复杂度和实际花费的时间很重要。虽然我不怀疑没有它,您的方法可能会更快,但我认为在任何阵列上工作的好处比每100k个元素可能产生的10毫秒惩罚更重要。是的,我们在这里似乎有一个折衷,我正在考虑如何操作用另一个变体来优化我发布的答案,否则看起来你的答案应该是最好的解决方案。我们似乎发布了相同算法的变体。+1因为你在我的答案中提到的原因。正如我在下面所说,我的算法
    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));
    
    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;
    }