Sorting functions in PHP

And the Spaceship Operator ๐Ÿš€๐Ÿš€๐Ÿš€

rock-mountain-santiago-compostela

When dealing with PHP, there are multiple ways to sort a collection of items, and sometimes itโ€™s not clear which method we should use and why.

What is the difference between sort(), arsort() and uasort()?

Before explaining the repercussions of a, r, k and u + sort() function, we need to know the very basic concept of lists and maps.

List

A list is an ordered collection.
The order is sequential starting from 0 and increasing 1 by 1 for each item.
It is also possible to have duplicated items in a list.

$list = ['Croatia', 'Belgium', 'Austria', 'Belgium', 'Denmark'];
print_r($list);

Array
(
    [0] => Croatia
    [1] => Belgium
    [2] => Austria
    [3] => Belgium
    [4] => Denmark
)

Map (also known as Dictionaries)

A map is an unordered collection.
Each element is composed of a key and a value.
It is not possible to have the same key more than once.

$map = [
    'Croatia' => 0,
    'Belgium' => 1,
    'Austria' => 2,
    'Denmark' => 3,
    'Belgium' => 4,
];
print_r($map);

Array
(
    [Croatia] => 0
    [Belgium] => 4 // 'Belgium => 1' was overriden
    [Austria] => 2
    [Denmark] => 3
)

It was important to do a quick overview of maps and lists, they are different kinds of collections, and because of that, each one has its own specific sorting methods.

The idea of those a, r, k & u + sort() is to work as modifiers as follows:

R = reverse โž sort in descencing order, lists & maps
K = key โž sort by key, only for maps
A = associative โž sort by value, only for maps
U = user-defined โž defined by the user in a callback

Sorting Lists ๐Ÿงต

sort() & rsort()

These methods sort lists in ascending/descending order.

# sort: Sort list by *ascending* order.
$sort = ['Croatia', 'Belgium', 'Austria', 'Belgium'];
sort($sort);

Array
(
    [0] => Austria
    [1] => Belgium
    [2] => Belgium
    [3] => Croatia
)

---

# rsort: Sort list by *descending* order.
$rsort = ['Croatia', 'Belgium', 'Austria', 'Belgium'];
rsort($rsort);

Array
(
    [0] => Croatia
    [1] => Belgium
    [2] => Belgium
    [3] => Austria
)

Sorting Maps ๐Ÿ—บ๏ธ

asort(), arsort(), ksort() & krsort()

These methods sort associative arrays by key/value in ascending/descending order.

# ksort: Sort map by *key* in *ascending* order.
$ksort = [100 => 'Croatia', 200 => 'Austria', 300 => 'Belgium'];
ksort($ksort);

Array
(
    [100] => Croatia
    [200] => Austria
    [300] => Belgium
)

---

# krsort: Sort map by *key* in *descending* order.
$krsort = [100 => 'Croatia', 200 => 'Austria', 300 => 'Belgium'];
krsort($krsort);

Array
(
    [300] => Belgium
    [200] => Austria
    [100] => Croatia
)

---

# asort: Sort map by *value* in *ascending* order.
$asort = [100 => 'Croatia', 200 => 'Austria', 300 => 'Belgium'];
asort($asort);

Array
(
    [200] => Austria
    [300] => Belgium
    [100] => Croatia
)

---

# arsort: Sort map by *value* in *descending* order.
$arsort = [100 => 'Croatia', 200 => 'Austria', 300 => 'Belgium'];
arsort($arsort);

Array
(
    [100] => Croatia
    [300] => Belgium
    [200] => Austria
)

User-defined functions ๐Ÿ‘ฉโ€๐Ÿ’ป

Until now everything was nice, but as you know, usually we have a collection of complex objects, and sometimes we want to define our own sorting function, in these scenarios, the asort() method simply doesn't work. Eg:

โ€œSorting a collection of Product by price in ascending order, and when more than one product has the same price, sort them alphabetically by the nameโ€.

We need to find a more sophisticated approach. And this is only possible if we can define our own sorting method, in this case, we can take advantage of using the spaceship operator! ๐Ÿš€

Basically, this operator is syntactic sugar specialized for comparisons.

The first parameter is the array we want to sort and the second is a callback that returns an integer value.

/** @var callable(mixed,mixed):int $callable */
$callable = function (mixed $a, mixed $b): int { ... }

usort(&$array, $callable): bool

The values this callable must return are -1, 0 and 1 if the current item is less, equal or greater than the previous one, to be sorted before or after in the collection.

function compare(int $a, int $b): int
{
    if ($a == $b) {
        return 0;
    }
    return ($a < $b) ? -1 : 1;
}

# Is the same than...

function compare(int $a, int $b): int
{
    return $a <=> $b;
}

# Is the same than...

fn (int $a, int $b): int => $a <=> $b;

usort(), uksort() & uasort()

These methods sort by keys or values (preserving or resetting the keys) in a user-defined callback. Letโ€™s see some examples:

# usort: Sort list/map by callback from the values, keys are reset.
$usort = [100 => 'Croatia', 300 => 'Austria', 200 => 'Belgium'];
usort($usort, fn (string $a, string $b): int => $a <=> $b);

Array
(
    [0] => Austria
    [1] => Belgium
    [2] => Croatia
)

---

# uksort: Sort map by callback from the keys.
$uksort = [100 => 'Croatia', 300 => 'Austria', 200 => 'Belgium'];
uksort($uksort, fn (int $a, int $b): int => $a <=> $b);

Array
(
    [100] => Croatia
    [200] => Belgium
    [300] => Austria
)

---

# uasort: Sort map by callback from the values preserving the keys.
$uasort = [100 => 'Croatia', 300 => 'Austria', 200 => 'Belgium'];
uasort($uasort, fn (string $a, string $b): int => $a <=> $b);

Array
(
    [300] => Austria
    [200] => Belgium
    [100] => Croatia
)

For the sake of simplicity, I used the function: fn ($a, $b) => $a <=> $b; if the collection was holding complex objects, it would be the same but comparing some property, it depends on your needs.

๐Ÿ“‘ Cheat Sheet

sort() - sort list in ascending order
rsort() - sort list in descending order

asort() - sort map by value in ascending order 
ksort() - sort map by key in ascending order
arsort() - sort map by value in descending order
krsort() - sort map by key in descending order

usort() - sort list or map by value in a user-defined callback, reset keys
uksort() - sort map by keys in a user-defined callback
uasort() - sort map by value in a user-defined callback

* Take into account that the array is passed by reference to the sorting function, which means you must initialize the array in a different line, and the sorting function will mutate the original array ๐Ÿ‘€

Reference