【问题标题】:Shuffle the order of keys in an associative array, if they have the same values?如果它们具有相同的值,则打乱关联数组中键的顺序?
【发布时间】:2016-06-21 04:23:01
【问题描述】:

给定一个这样的关联数组,你如何打乱具有相同值的键的顺序?

array(a => 1,
      b => 2,  // make b or c ordered first, randomly
      c => 2,
      d => 4,
      e => 5,  // make e or f ordered first, randomly
      f => 5);

我尝试的方法是把它变成这样的结构并对值(原始键的数组)进行洗牌,然后将其展平回原始形式。有没有更简单或更清洁的方法? (我不担心效率,这是针对小数据集的。)

array(1 => [a],
      2 => [b, c],  // shuffle these
      4 => [d], 
      5 => [e, f]); // shuffle these


function array_sort_randomize_equal_values($array) {
    $collect_by_value = array();
    foreach ($array as $key => $value) {
        if (! array_key_exists($value, $collect_by_value)) {
            $collect_by_value[$value] = array();
        }
        // note the &, we want to modify the array, not get a copy
        $subarray = &$collect_by_value[$value];
        array_push($subarray, $key);
    }

    arsort($collect_by_value);

    $reordered = array();
    foreach ($collect_by_value as $value => $array_of_keys) {
        // after randomizing keys with the same value, create a new array
        shuffle($array_of_keys);
        foreach ($array_of_keys as $key) {
            array_push($reordered, $value);
        }
    }

    return $reordered;
}

【问题讨论】:

    标签: php arrays sorting random associative-array


    【解决方案1】:

    我重写了整个代码,因为我找到了另一种比旧的更简单和更快的方式(如果你仍然对旧的感兴趣see the revision):

    • 旧代码(100,000 次执行):Ø 4.4 秒。
    • 新代码(100,000 次执行):Ø 1.3 秒。

    说明

    首先,我们使用array_flip() 从数组中获取所有唯一值,从那时起,这些值就是键,您不能在数组中有重复键,我们有唯一值。我们还创建了一个数组 $result 用于存储我们的结果,$keyPool 用于存储每个值的所有键。

    现在我们遍历我们的唯一值并将所有具有相同值的键放入带有array_keys() 的数组中,并将其保存在$keyPool 中,并将值作为键。我们也可以马上shuffle()keys数组,这样就已经是随机的了:

    foreach($uniqueValues as $value => $notNeeded){
        $keyPool[$value] = array_keys($arr, $value, TRUE);
        shuffle($keyPool[$value]);
    }
    

    现在我们已经可以遍历我们的原始数组并从$keyPool 中为每个值获取带有array_shift() 的键并将其保存在$result 中:

    foreach($arr as $value)
        $result[array_shift($keyPool[$value])] = $value;
    

    由于我们已经对数组进行了洗牌,因此键已经具有随机顺序,我们只使用array_shift(),因此我们不能使用两次。

    代码

    <?php
    
        $arr = ["a" => 1, "b" => 1, "c" => 1, "d" => 1, "e" => 1, "f" => 2,
                "g" => 1, "h" => 3, "i" => 4, "j" => 5, "k" => 5];
    
    
        function randomize_duplicate_array_value_keys(array $arr){
    
            $uniqueValues = array_flip($arr);
            $result = [];
            $keyPool = [];
    
            foreach($uniqueValues as $value => $notNeeded){
                $keyPool[$value] = array_keys($arr, $value, TRUE);
                shuffle($keyPool[$value]);
            }
    
            foreach($arr as $value)
                $result[array_shift($keyPool[$value])] = $value;
    
            return $result;
    
        }
    
    
        $result = randomize_duplicate_array_value_keys($arr);
        print_r($result);
    
    ?>
    

    (可能的)输出:

    Array (
        [b] => 1
        [g] => 1
        [a] => 1
        [e] => 1
        [d] => 1
        [f] => 2
        [c] => 1
        [h] => 3
        [i] => 4
        [k] => 5
        [j] => 5
    )
    

    脚注

    • 我使用 array_flip() 而不是 array_unique() 从数组中获取唯一值,因为它稍微快一些。

    • 我还删除了 if 语句来检查数组是否有多个元素并且需要重新洗牌,因为无论有没有 if 语句,代码运行的执行时间几乎相同。我只是将其删除以使其更易于理解并且代码更具可读性:

      if(count($keyPool[$value]) > 1)
          shuffle($keyPool[$value]);
      
    • 如果需要,您还可以进行一些优化更改:

      1. 如果你得到一个空数组,例如,抢先返回。

        function randomize_duplicate_array_value_keys(array $arr){
         
            if(empty($arr))
                return [];
         
            $uniqueValues = array_flip($arr);
            $result = [];
            //***
        }
      2. 如果没有重复值,则抢先返回数组:

        function randomize_duplicate_array_value_keys(array $arr){
         
            if(empty($arr))
                return [];
            elseif(empty(array_filter(array_count_values($arr), function($v){return $v > 1;})))
                return [];
         
            $uniqueValues = array_flip($arr);
            $result = [];
            //***
        }

    【讨论】:

      【解决方案2】:

      这是在跟踪前一个值的同时遍历排序数组的另一种方法。如果前一个值与当前值不同,则将前一个值添加到一个新数组中,而当前值成为前一个值。如果当前值与前一个值相同,则根据rand(0,1) 的结果,要么像以前一样将前一个值添加到新列表中,要么先将当前值添加到新列表中:

      <?php
      
      $l = ['a' => 1,'b' => 2, 'c' => 2,
          'd' => 4,'e' => 5,'f' => 5];
      
      asort($l);
      $prevK = key($l);
      $prevV = array_shift($l); //initialize prev to 1st element    
      $shuffled = [];
      
      foreach($l as $k => $v) {
          if($v != $prevV || rand(0,1)) {
              $shuffled[$prevK] = $prevV;
              $prevK = $k;
              $prevV = $v;
          }
          else {
              $shuffled[$k] = $v;
          }
      }
      
      $shuffled[$prevK] = $prevV;
      
      print_r($shuffled);
      

      【讨论】:

      • 有趣的解决方案。但是使用此代码,需要对数组进行排序,并且在数组中多次出现的值的第一个键与同样具有相同值的最后一个键改变位置的可能性非常小。因为钥匙必须通过所有元素和每次改变位置才能到达最后一个位置。
      • 是的,只有当有 2 个(而不是更多)重复值时,随机播放才会均匀随机
      猜你喜欢
      • 2018-02-22
      • 2020-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-30
      • 1970-01-01
      相关资源
      最近更新 更多