0x60
0x60

Reputation: 3214

Sort array elements based on their return value from a custom function call

Is there a way to sort a PHP array by return value of a custom function? Like Python's sorted(arr, key=keyfn) or Clojure's (sort-by keyfn arr),

usort($array, function ($a, $b) {
    $key_a = keyfn($a);
    $key_b = keyfn($b);
    if ($key_a == $key_b) return 0;
    if ($key_a < $key_b) return -1;
    return 1;
});

The above does what I want, but it's verbose and it calls keyfn (which can be slow) much more often than needed. Is there a more performant approach?

Upvotes: 1

Views: 152

Answers (3)

mickmackusa
mickmackusa

Reputation: 47934

From PHP7.4 and higher you can modernize/refine @u_mulder's snippet using the null coalescing assignment operator as the following.

Code: (Demo)

usort($array, function ($a, $b) {
    static $cache;
    return ($cache[$a] ??= keyfn($a)) <=> ($cache[$b] ??= keyfn($b));
});

Alternatively, making mapped calls of your custom function will be more concise, but will result in a greater number of custom function calls. (Demo)

array_multisort(array_map('keyfn', $array), $array);

This can be prevented in a classic loop, again with the null coalescing operator, but it may be a little harder to conceptualize with the double-assignment line. (Demo)

$new = [];
foreach ($array as $v) {
    $new[] = $cache[$v] ??= keyfn($v);
}
array_multisort($new, $array);

Upvotes: 0

0x60
0x60

Reputation: 3214

I ended up implementing it this way:

function sort_by($key_f, $arr) {
    $values = array_map($key_f, $arr);
    asort($values);
    $sorted_arr = array();

    foreach ($values as $idx => $value) {
        $sorted_arr[] = $arr[$idx];
    }

    return $sorted_arr;
}

Upvotes: 2

u_mulder
u_mulder

Reputation: 54841

Some simple code for caching results of predicate and sorting (using spaceship operator which reduces lines where you return 0,1,-1). In case of predicate result as int you can even return $key_a - $key_b:

$array = [2,2,2,1,1,0,0,8];
$values_cache = [];
usort($array, function ($a, $b) use (&$values_cache) {
    $key_a = isset($values_cache[$a]) ? $values_cache[$a] : ($values_cache[$a] = keyfn($a));
    $key_b = isset($values_cache[$b]) ? $values_cache[$b] : ($values_cache[$b] = keyfn($b));
    return $key_a <=> $key_b;
});
echo '<pre>', print_r($array), '</pre>';

function keyfn($v) {
    echo 'call a keyfn' . PHP_EOL;
    return 2 * $v;
}

Simple fiddle https://3v4l.org/W1N7Y

Upvotes: 1

Related Questions