2
\$\begingroup\$

PHP's shuffle() function destroys the array keys, so I decided to write a shuffle function that doesn't do that and instead rearranges key-value associations. As I would be using the function in place of shuffle() most of the time, I'd like it to be as fast and memory efficient (if a time-space tradeoff is needed, I think I would prioritise time) as possible:

<?
    function swap(&$a, &$b)
    {
        $tmp = $a;
        $a = $b;
        $b = $tmp;
    }

    function shuffleX(&$arr)    #Shuffles the key-value associations in an array.
    {
        $keys = array_keys($arr);   #extract the keys from the array.
        $length = count($keys);
        $i = 0; #Index.
        while ($i < $length-1) 
        {
            $target = rand(($i+1), $length-1);  #This ensures that no value ends up mapped to the same key.
            swap($arr[$keys[$i]], $arr[$keys[$target]]);    #Swap each element of the array with another.
            $i++;
        }
    }
?>
\$\endgroup\$
4
\$\begingroup\$

Rather than making iterated rand() calls, you should randomize the data once for best efficiency. To do this, just isolate the keys, shuffle them, then rejoin the values to the appropriate keys in the new order.

Code: (Demo)

function preserve_shuffle(&$arr)    #Shuffles the key-value associations in an array.
{
    $keys = array_keys($arr);   #extract the keys from the array.
    shuffle($keys);
    for ($index = 0, $length = count($keys); $index < $length; ++$index) {
        $result[$keys[$index]] = $arr[$keys[$index]];
    }
    $arr = $result;
}

$arr = ["a" => "apple", "b" => "banana", "c" => "capsicum", "d" => "dill"];
preserve_shuffle($arr);
var_export($arr);

Or, if you prefer greater brevity or a functional syntax, you could use the following inside your custom function:

$keys = array_keys($arr);   #extract the keys from the array.
shuffle($keys);
$arr = array_merge(array_flip($keys), $arr);

Proof that it also works: https://3v4l.org/eLMo6


My earlier snippets only reorder the associative data. The following will shuffle the associations without shuffling the key orders. It does not guarantee that all initially associations will be destroyed -- which I feel is beneficial / less predictable in a randomized result.

function random_disassociate(&$assoc_array)
{
    if (sizeof($assoc_array) < 2) {
        return;  // data cannot be disassociated
    }
    $keys = array_keys($assoc_array);
    shuffle($assoc_array);
    $assoc_array = array_combine($keys, $assoc_array);
}

$arr = ["a" => "apple", "b" => "banana", "c" => "capsicum", "d" => "dill"];
random_disassociate($arr);
var_export($arr);
\$\endgroup\$
  • \$\begingroup\$ Your function version doesn't work. \$\endgroup\$ – Tobi Alafin Jan 3 at 18:10
  • \$\begingroup\$ That doesn't rearrange the key to value mappings. It just changes the order in which they are presented, but the key to value mappings are the same. The function is supposed to rearrange the key to value mappings. \$\endgroup\$ – Tobi Alafin Jan 4 at 5:39
  • \$\begingroup\$ You require all initial pairs to be disassociated in the result? \$\endgroup\$ – mickmackusa Jan 4 at 5:46
  • \$\begingroup\$ That is the idea. They don't have to be disassociated, but I expect that the probability of finding the original associations would be extremely small (probability drops exponentially with the size of the array). My original code actually made it 0, but that isn't necessary for what I would consider a working shuffle function. \$\endgroup\$ – Tobi Alafin Jan 4 at 7:40
  • 1
    \$\begingroup\$ I think the edited function is much more legible and easier to read than the first one. Ultimately, I would probably just use the faster one. Thank you very much for your time and assistance. \$\endgroup\$ – Tobi Alafin Jan 4 at 8:00
3
\$\begingroup\$

The swap() function is unecessary

There's no need to create a swap() function. This can be done as a one-liner using native PHP. And, generally speaking, native functionality is going to be more performant than user-defined functions. It also means less code for you to write or maintain.

list($a,$b) = [$b, $a];

This takes two values, places them in an array and then using list() swaps them. In your case it would look like:

list($arr[$keys[$i]],$arr[$keys[$target]]) = [$arr[$keys[$target]], $arr[$keys[$i]]];

Friendly reminder: don't use short tags

Short PHP tags (<?) has been discouraged for a long time. Although it is still supported it is disabled by default in the php.ini file and its use is discouraged. It sounds like it is not a big deal but this means having to make sure every time you set up an environment for this to run you have to make a special configuration change which is risky and time consuming and really shouldn't be necessary. (The short echo tag (<?=) is not discouraged and always available so feel free to use that as much as you like).

PHP also allows for short open tag <? (which is discouraged since it is only available if enabled using the short_open_tag php.ini configuration file directive, or if PHP was configured with the --enable-short-tags option).

Source

\$\endgroup\$
  • \$\begingroup\$ Oh my, the great John Conde comes to CodeReview. Welcome sir! I wonder what your opinion on php7 array deconstruction is. \$\endgroup\$ – mickmackusa Jan 3 at 14:38
  • 1
    \$\begingroup\$ I like it. In fact, it is better than my proposed solution. I've been stuck in a PHP 5.6 world due to the size and complexity of my company's applications. But we're finishing up our (large and painful) PHP 7 migration so I'll get to start thinking in PHP 7 full time very soon! \$\endgroup\$ – John Conde Jan 3 at 16:12
  • \$\begingroup\$ I don't know about great but I figured participating here will help me think creatively as I do less coding now than every before and I hope to keep my mind sharp. Or at least working. \$\endgroup\$ – John Conde Jan 3 at 16:14
  • \$\begingroup\$ @JohnConde I'm aware that it is discouraged, and only use it for convenience in a testing/learning environment. \$\endgroup\$ – Tobi Alafin Jan 3 at 17:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.