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

504

Is there a way to sort a PHP array by return value of a custom function? Like Python'ssorted(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 callskeyfn (which can be slow) much more often than needed. Is there a more performant approach?

1

Answer

Solution:

Some simple code for caching results ofpredicate and sorting (usingspaceship operator which reduces lines where you return 0,1,-1). In case ofpredicate result asint 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

907

Answer

Solution:

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

Answer

Solution:

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

People are also looking for solutions to the problem: php - xTrying to include a header block in Sympfony 3

Source

Didn't find the answer?

Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.

Ask a Question

Write quick answer

Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.

Similar questions

Find the answer in similar questions on our website.