strangeQuirks
strangeQuirks

Reputation: 5920

Sorting flat associative array by values DESC, then by whitelisted keys

I have the following array:

$array = [
    'z' => 2,
    'd' => 1,
    'a' => 2,
];

Now I would like to sort by the value (integer) and then sort by the key based on whether it is in this allowlist:

$allowlist = ['a', 'd'];

So I did the following:

arsort($array);
uksort($array, function($a, $b) {
    return in_array($a, $allowlist) ? -1 : 1;
});

But that returns:

[d]: 1
[a]: 2
[z]: 2

What I really want is to sort the values first and then if there is a tie breaker, sort the key based on whether its in that allowlist which should result in this:

[a]: 2
[z]: 2
[d]: 1

Upvotes: 1

Views: 1214

Answers (3)

mickmackusa
mickmackusa

Reputation: 47894

I recommend making as few evaluations as possible and as few function calls as possible.

This principle makes array_multisort() the top choice for this task. usort() will make more iterated calls than array_map() (which has a linear time complexity) and that will put a drag on performance.

Flipping the whitelist array will allow it to be used as a lookup array. Because false comes before true when sorting in an ascending direction, check that the key is NOT found in the lookup.

  • Sort by values descending (param1: $array and param2: SORT_DESC)
  • Sort by existence in the whitelist array (param3)
  • The final parameter will be affected by all sorts which occurred earlier.

Code: (Demo)

$array = [
    'z' => 2,
    'd' => 1,
    'a' => 2,
];
$allowlist = array_flip(['a', 'd']);

array_multisort(
    $array,
    SORT_DESC,
    array_map(fn($k) => !isset($allowlist[$k]), array_keys($array)),
    $array
);

var_export($array);

Output:

array (
  'a' => 2,
  'z' => 2,
  'd' => 1,
)

Upvotes: 1

Sjors Broersen
Sjors Broersen

Reputation: 31

The key is using the array_flip function on the keys. This give the uksort a value it can compare:

$array = [
    'a' => 2,
    'z' => 2,
    'd' => 1,
    'e' => 1,
];
$allowlist = array_flip(['a', 'e', 'd']);

uksort(
    $array,
    static function($a, $b) use ($array, $allowlist) {
        if ($array[$a] === $array[$b]) {
            if (!key_exists($a,$allowlist) || !key_exists($b,$allowlist)) {
                return 0;
            }
            return $allowlist[$a] <=> $allowlist[$b];
        }
        return $array[$b] <=> $array[$a];
    }
);

echo '<pre>'.print_r($array,1).'</pre>';

result in :

Array
(
    [a] => 2
    [z] => 2
    [e] => 1
    [d] => 1
)

Upvotes: 0

Chris Haas
Chris Haas

Reputation: 55427

You can use uksort which will give you the keys, and you can pass the $array into your sort function to get access to the value.

$array = [
    'a' => 2,
    'z' => 2,
    'd' => 1
];
$allowlist = ['a', 'd'];

uksort(
    $array,
    static function($a, $b) use ($array, $allowlist) {
        if($array[$a] === $array[$b]) {
            return in_array($a, $allowlist) ? -1 : 1;
        }
        return $array[$b] <=> $array[$a];
    }
);

This was inspired by this answer: https://stackoverflow.com/a/65315474/231316

Demo here: https://3v4l.org/toZIt

Upvotes: 2

Related Questions