简体   繁体   中英

Shuffle the order of keys in an associative array, if they have the same values?

Given an associative array like this, how can you shuffle the order of keys that have the same value?

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);

The approach I tried was to turn it into a structure like this and shuffle the values (which are arrays of the original keys) and then flatten it back into the original form. Is there a simpler or cleaner approach? (I'm not worried about efficiency, this is for small data sets.)

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;
}

I rewrote the entire code, since I found another way which is a lot simpler and faster than the old one(If you are still interested in the old one see the revision ):

  • old code (100,000 executions): Ø 4.4 sec.
  • new code (100,000 executions): Ø 1.3 sec.

Explanation

First we get all unique values from the array with array_flip() , since then the values are the keys and you can't have duplicate keys in an array we have our unique values. We also create an array $result for then storing our result in it and $keyPool for storing all keys for each value.

Now we loop through our unique values and get all keys which have the same value into an array with array_keys() and save it in $keyPool with the value as key. We can also right away shuffle() the keys array, so that they are already random:

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

Now we can already loop through our original array and get a key with array_shift() from the $keyPool for each value and save it in $result :

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

Since we already shuffled the array the keys already have a random order and we just use array_shift() , so that we can't use the key twice.

Code

<?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);

?>

(possible) output:

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

Footnotes

  • I used array_flip() instead of array_unique() to get the unique values from the array, since it's slightly faster.

  • I also removed the if statement to check if the array has more than one elements and needs to be shuffled, since with and without the if statement the code runs pretty much with the same execution time. I just removed it to make it easier to understand and the code more readable:

     
        
        
          if(count($keyPool[$value]) > 1) 
         shuffle($keyPool[$value]); 

  • You can also make some optimization changes if you want:

    1. Preemptively return, if you get an empty array, eg

       function randomize_duplicate_array_value_keys(array $arr){  $uniqueValues = array_flip($arr); $result = []; //*** } 
    2. Preemptively return the array, if it doesn't have duplicate values:

       function randomize_duplicate_array_value_keys(array $arr){ if(empty($arr)) return [];  $uniqueValues = array_flip($arr); $result = []; //*** } 

Here's another way that iterates through the sorted array while keeping track of the previous value. If the previous value is different than the current one, then the previous value is added to a new array while the current value becomes the previous value. If the current value is the same as the previous value, then depending on the outcome of rand(0,1) either the previous value is added to the new list as before, or the current value is added to the new list first:

<?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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM