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
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
I recommend making as few evaluations as possible and as few function calls as possible.
There are deliberately two spaceship operators in my snippet -- this means that the in_array() calls are only executed when necessary. The spaceship operator will process arrays of sorting rules, but this will be less efficient because all in_array() functions will be called on every iteration. Don't use this demo.
Code: (Demo)
uksort(
$array,
fn($a, $b) =>
$array[$b] <=> $array[$a] // sort values DESC
?: in_array($b, $allowlist) <=> in_array($a, $allowlist) // sort keys with priority given to whitelisted keys
);
var_export($array);
This sorts by value descending then breaks ties by checking if in_array() descending. When sorting boolean outcomes, descending puts true before false.
If you are dealing with large array volumes, then I will urge you to avoid in_array(). Instead flip your lookup array and use isset() in your custom function.
The custom function's visible cascading rules syntax makes it easy to read and extend. If you wanted to add another tie breaker for when both or neither key is whitelisted, then simply add another line with the new rule:
?: $a <=> $b // sort by key ascending (alphabetically)