PHP Array Sorting

Chapter 31 47 mins

Learning outcomes:

  1. The different kinds of sorting functions
  2. How arrays are sorted in PHP
  3. The sort() and rsort() functions
  4. The ksort() and krsort() functions
  5. The asort() and arsort() functions
  6. Sorting constants
  7. Sorting by user-defined comparison functions

Introduction

Arrays are one of the most fundamental types of data in programming. And one of the most fundamental operations performed on arrays is that of sorting.

Sorting is essentially to reorder elements of a given array such that they either line up in ascending (increasing) order or in descending (decreasing) order.

In this chapter, we shall explore how to sort arrays in PHP. In particular, we'll be looking at the three different categories of functions, i.e ones that sort by values (sort(), rsort(), usort()); ones that sort by keys (ksort(), krsort(), uksort()); and ones that sort by values but keeping the keys in tact (asort(), arsort(), uasort()).

Different kinds of sorting

If we look up the official documentation of PHP, we see that there is a large collection of functions meant to sort arrays.

To be precise, we have sort(), rsort() and usort(); ksort(), krsort() and uksort(); asort(), arsort() and uasort().

If this wasn't enough, we also have natsort() and natcasesort().

Now some questions that immediately arise in the mind upon looking at this are: What's the point of so many sorting functions? What's asort() for? How's uksort() different from ksort()? What's krsort() then?

Well, contrary to how it seems, the answers to all of these questions are actually really simple. But first we need to clarify the main distinction between them.

Sorting by values

To start with, we have sort() and rsort() — the most basic functions. Both of them sort by values which simply means that they look at the values of a given array while sorting it, not its keys.

sort() sorts an array in ascending order (of values). On the other hand, rsort() is just the reverse of sort(), i.e. it reorders the elements in descending order.

sort() (and rsort() and usort()) is typically used on indexed arrays where the key-value association doesn't really matter to us. That is, it doesn't matter whether the first element (with the key 0) becomes, let's say, the second element (with the key 1).

There is yet another function in this sort-by-value category and that is usort().

usort() is related to sort() in that it also sorts by values (where the keys aren't preserved), but this time we manually provide a comparison function to it.

If you don't understand what 'sort by values' means here, don't worry at all — we'll be presenting examples below of each of these functions which'll hopefully clarify any confusion that you might have at this stage.

Sorting by keys

Next up, we have ksort(), krsort() and uksort().

Before we even explain the purpose of these functions, notice a pattern here: we have the names 'sort' and 'rsort' again and even a name with 'u' at its beginning. Hmm. This is interesting.

The name 'sort' essentially implies sorting in ascending order; 'rsort' implies sorting in descending order (which is just reverse ascending order); and 'u' implies sorting based on a user-defined comparison function.

But then what does the letter 'k' represent here? Well, 'k' stands for 'key'.

Likewise, ksort() sorts an array in ascending order of its keys. Similarly, krsort() does the reverse of ksort(), i.e. it sorts the array in descending order of its keys.

ksort() is, as you can probably guess, used on associative arrays where we want the ordering to be governed by the keys of the arrays.

And then just like we have usort() akin to sort(), we have uksort() akin to ksort(). That is, uksort() behaves like ksort(), just that the comparisons in the sorting routine are based on a user-defined function.

Sorting by values, with key preservations

Last up, we have asort(), arsort() and uasort().

Recall the nomenclature pattern from the previous section? It's in action here as well.

We can make an educated guess that asort() would sort in ascending order of 'something', arsort() would sort in descending order of that 'something', and uasort() would showcase a similar behavior, sorting in terms of that 'something,' albeit with a user-defined comparison function.

So now, what's the 'a' for?

The 'a' here stands for 'association' and means that the key-value associations are retained in these sorting functions. To be precise, all of these three functions sort an array by values, but also keep the keys intact, unlike the first category of functions that we saw above.

asort() sorts an array in ascending order of its values, with keys preserved. arsort() is simply the reverse of asort().

Finally, uasort() is just asort() with a user-defined comparison function.

How are arrays sorted in PHP?

Before anything can begin, one has to choose a given sorting algorithm that handles almost the entire logic of sorting; the rest logic being handled by the comparison function.

As a matter of fact, PHP uses an implementation of the Quicksort algorithm. The exact details of the algorithm are beyond the scope of this chapter.

To learn more about Quicksort, refer to this Wikipedia article on Quicksort.

The second most important thing after the sorting algorithm is the comparison function.

If you've ever worked with sorting algorithms before, or at least have tried solving the PHP Control Flow — Selection Sort Exercise in this course, you'd know that sorting is all about comparisons.

Different items in a given array, which is meant to be sorted, are compared to one another in order to determine which one should come first, which one second, and so on. Comparison is an integral part of sorting.

Now all sorting functions in PHP, except for usort(), uksort() and uasort(), rely on predefined comparison functions.

These comparison functions essentially take two values, a and b, that are meant to be compared, and then return either -1, 0 or 1, to specify which of those two values should come first.

The rules are really simple:

  • -1 means that a should come before b.
  • 0 means that a should remain in its original order relative to b.
  • 1 means that b should come before a.

This integer returning approach is not unique to PHP; it's conventional and employed in many other programming languages as well, including JavaScript.

For instance, the comparison function used by sort() returns -1 when a is less than b, so a comes first. On the same lines, the comparison function used by rsort() returns -1 when a is greater than b, so that a comes first.

All sorting functions in PHP that are based on predefined comparison functions provide us with a second parameter that can be leveraged to configure the underlying comparison function. If we don't specify any configuration — that is, any argument to the second parameter — a function that is based on the default comparison behavior is used.

And the default comparison behavior is to perform a regular comparison of the two given values.

A regular comparison is basically just <, > and ==, followed by returning an integer based on the relation between the values and the sorting function being used.

The reason we call it 'regular' is perhaps because it's the regular, i.e. the normal, way of comparing two values in PHP.

Regular comparison semantics

The official documentation provides us with all the necessary information to understand how exactly values are compared in PHP when using <, > or ==.

Shown below is the sequence of steps used when comparing two arbitrary values, a and b, for either of the relations <, > or ==:

Type of aType of bType jugglingComparison
String or NULLStringBoth the values are coerced into strings.Numerical comparison if the both the strings are numeric strings, or else lexical comparison.
Boolean or NULLAnythingBoth the values are coerced into Booleans.Normal comparison of Booleans, where false < true.
ObjectObject-Normal comparison of objects.
String, resource, int or floatString, resource, int or floatBoth the operands are coerced into numbers.Numerical comparison. (It's just basic math!)
ObjectAnything-Object is greater.
ArrayAnything-Array is greater.

This sequence of steps might seem intimidating, and rightly so.

Fortunately, the good news is that you're not required to remember this; in fact, given that you always make sure that an array has elements of the same type, you won't even need to consult it!

It's just a well-defined algorithm that PHP uses to compare any two arbitrary values together using the <, > and == operators.

You might have noticed the word 'lexical' above. Lexical comparison refers to the standard way comparing two strings with one another to determine whether they are equal or not, and if not, then which one is lexically smaller and which one lexically larger.

The next snippet explains how lexical comparison happens.

How lexical comparison happens?

Iteration is performed over both the given strings simultaneously, comparing corresponding code units with one another.

If at any point a mismatch is found, the string with the code unit whose value is greater than the corresponding code unit in the other string is termed as lexically larger than that string; accordingly, the other string is termed as lexically smaller.

However, if no mismatch is found until either one of the strings has been exhausted by the iterations (i.e. its end reached), then at that point there are two possibilities:

  • The length of both the strings is the same. In that case, the string are exactly identical to one another.
  • The length of both the strings is NOT the same. In that case, the string with a larger length is lexicographically larger than the other one; accordingly, the other string is the lexicographically smaller one.

For example, in sort(), the comparison function will return -1 if a < b. But in rsort(), the comparison function will return 1 if a < b, because in that case b ought to be placed before a.

At this point, it's worthwhile mentioning that if two values being compared are found to be equal to one another, then their original relative order is preserved in the sorted array. In the language of sorting algorithms, this feature makes the sorting algorithm be termed as stable.

Stable sorting means that if two values are equal to one another in an array, their original order would remain preserved in the sorted array.

Since PHP 8.0.0, all sorting functions perform stable sorting. Previously, this wasn't the case.

sort() and rsort()

Let's now start exploring the sorting functions individually, in detail, starting with the first kind, i.e. functions sorting by values (without retaining keys).

We have sort() and rsort() to consider here. usort() will be considered in a separate section along with uksort() and uasort().

Here's the syntax of sort():

sort(&$array, $flags = SORT_REGULAR)

The first argument is the array to sort. Recall that & implies that $array is a reference parameter, i.e. it refers to the original array provided.

To learn more about how reference parameters work in PHP and why we even need them in the first place, refer to the PHP References chapter.

The second $flags argument configures the logic of the comparison function used internally. We'll explore this second argument later on below.

Let's consider a couple of examples now.

Let's say we have the following array of numbers and wish to sort them in ascending order, which is perhaps the most basic of wishes from the perspective of sorting:

<?php

$nums = [10, -5, 0, 0.7, 10, -7.8, 33];

Time to get it done using sort():

<?php

$nums = [10, -5, 0, 0.7, 10, -7.8, 33];
sort($nums);

And we're done.

Let's confirm the result using print_r():

<?php

$nums = [10, -5, 0, 0.7, 10, -7.8, 33];
sort($nums);

print_r($nums);
Array ( [0] => -7.8 [1] => -5 [2] => 0 [3] => 0.7 [4] => 10 [5] => 10 [6] => 33 )

Yup, we have exactly what we expected.

Now suppose we have to sort these numbers in descending order.

Once again, no problem; just use rsort():

<?php

$nums = [10, -5, 0, 0.7, 10, -7.8, 33];
rsort($nums);

print_r($nums);
Array ( [0] => 33 [1] => 10 [2] => 10 [3] => 0.7 [4] => 0 [5] => -5 [6] => -7.8 )

Simple, isn't this?

As another example, suppose we have to sort the following list of programming languages in descending order:

<?php

$langs = ['PHP', 'JavaScript', 'C', 'Ada', 'Bash', 'C#', 'Go', 'Ruby'];

rsort(), over to you:

<?php

$langs = ['PHP', 'JavaScript', 'C', 'Ada', 'Bash', 'C#', 'Go', 'Ruby'];
rsort($langs);

print_r($langs);
Array ( [0] => Ruby [1] => PHP [2] => JavaScript [3] => Go [4] => C# [5] => C [6] => Bash [7] => Ada )

And we're done.

If you've worked with JavaScript, you'll know that there is additional code required if we wish to sort an array of numbers vs. if we wish to sort an array of strings (which doesn't require any code). This is by virtue of the fact that JavaScript sorts elements lexically by default.

In PHP, though, we don't need to worry about whether we have an array of numbers or an array of strings (as long as all the elements are of the same type); the default 'regular' comparison logic abstracts away this from us.

Let's see what happens when we have an array of numbers and stringified numbers (strings containing numeric values) and then sort it using sort():

<?php

$arr = [10, '-5', '50', 0.7, 10, '-7.8', 33];
sort($arr);

print_r($arr);
Array ( [0] => -7.8 [1] => -5 [2] => 0.7 [3] => 10 [4] => 10 [5] => 33 [6] => 50 )

Surprised or not by the output?

Well there should be some element of surprise since sort() seems to have performed really intelligently here — it didn't group the strings at one end and the numbers at the other; instead it figured out that the strings are numeric strings and, likewise, compared them with numbers after having coerced them into numbers as well.

For instance, if 10 and '-7.8' were compared in the sorting routine, first '-0.7' would've been converted into a number to give -7.8, and then this number would've been compared with 10.

ksort() and krsort()

In order to sort an array based on its keys, we use ksort() and krsort().

ksort() sorts an array in ascending order of keys. Here's its syntax:

ksort(&$array, $flags = SORT_REGULAR)

On the same lines, krsort() sorts an array in descending order of keys.

ksort() (and krsort()) is typically meant to be used on associative arrays, not indexed arrays (because the keys there, i.e. 0, 1, 2, ..., are already sorted), where we want to base the sorting upon keys.

Let's consider an example. Shown below we have an associative array depicting the count of given words in a scanned piece of arbitrary text:

<?php

$word_counts = [
   'us' => 21,
   'great' => 5,
   'simple' => 1,
   'cats' => 30,
   'cute' => 10,
   'original' => 10,
];

We want to showcase the words, along with their counts, in ascending order. Note that the words must be in ascending order, not their counts.

Now since this task is concerned with the keys of $word_counts, what we need is ksort():

<?php

$word_counts = [
   'us' => 21,
   'great' => 5,
   'simple' => 1,
   'cats' => 30,
   'cute' => 10,
   'original' => 10,
];

ksort($word_counts);
print_r($word_counts);
Array ( [cats] => 30 [cute] => 10 [great] => 5 [original] => 10 [simple] => 1 [us] => 21 )

Voila! The words have been lined up in ascending order.

Let's also demonstrate krsort() with the same $word_counts array:

<?php

$word_counts = [
   'us' => 21,
   'great' => 5,
   'simple' => 1,
   'cats' => 30,
   'cute' => 10,
   'original' => 10,
];

krsort($word_counts);
print_r($word_counts);
Array ( [us] => 21 [simple] => 1 [original] => 10 [great] => 5 [cute] => 10 [cats] => 30 )

Perfect; all the words sorted in descending order.

asort() and arsort()

Akin to sort(), asort() sorts an array based on its values, but this time the keys are kept intact unlike in sort().

In particular, asort() sorts an array in ascending order of values while arsort() sorts it in descending order of values, whereby they key bindings remain preserved.

Syntactically, the functions are identical to sort() and ksort():

asort(&$array, $flags = SORT_REGULAR)

Time for an example.

Let's say we want to sort the $word_counts array shown above based on the counts of words, not the word themselves.

Clearly, using sort() won't be helpful in this case, as demonstrated below:

<?php

$word_counts = [
   'us' => 21,
   'great' => 5,
   'simple' => 1,
   'cats' => 30,
   'cute' => 10,
   'original' => 10,
];

// sort() ain't gonna be helpful!
sort($word_counts);
print_r($word_counts);
Array ( [0] => 1 [1] => 5 [2] => 10 [3] => 10 [4] => 21 [5] => 30 )

Even though the counts indeed line up in ascending order, we've lost their association to given words.

What we need instead is asort(), to keep the key-value association intact:

<?php

$word_counts = [
   'us' => 21,
   'great' => 5,
   'simple' => 1,
   'cats' => 30,
   'cute' => 10,
   'original' => 10,
];

asort($word_counts);
print_r($word_counts);
Array ( [simple] => 1 [great] => 5 [cute] => 10 [original] => 10 [us] => 21 [cats] => 30 )

See how the words are lined up in ascending order of their counts, with the words kept intact. Perfect!

As before, let's also consider the reverse function arsort():

<?php

$word_counts = [
   'us' => 21,
   'great' => 5,
   'simple' => 1,
   'cats' => 30,
   'cute' => 10,
   'original' => 10,
];

arsort($word_counts);
print_r($word_counts);
Array ( [cats] => 30 [us] => 21 [cute] => 10 [original] => 10 [great] => 5 [simple] => 1 )

Simple and elegant.

Sorting constants

With the exception of usort(), uksort() and uasort(), the syntax of all the sorting functions shown above is the exact same. That is, the first argument is the array to sort (which is passed in by reference) while the second argument is a set of constants specifying the nature of the comparisons.

Basically, the second argument is a means of customizing the comparison function with given presets.

Here's a list of all the constants that can be provided in as the second argument to configure the comparison function:

ConstantDescription
SORT_REGULARThe comparisons are regular, i.e. using the same routine used internally by the <=> operator.
SORT_NUMERICWhen comparing two elements with one another, they are first coerced into numbers and then numerical comparison is performed.
SORT_STRINGWhen comparing two elements with one another, they are first coerced into strings and then lexical comparison is performed.
SORT_LOCALE_STRINGSame as SORT_STRING but based on the current locale.
SORT_NATURALAlmost same as SORT_STRING except for that it uses natural comparisons instead of strictly lexical comparisons.
SORT_FLAG_CASEAn additional flag that could blended in with SORT_STRING and SORT_NATURAL to make the comparisons case-insensitive.

SORT_REGULAR

First in line we have SORT_REGULAR.

This refers to the 'regular' comparison semantics, i.e. the one performed by the spaceship (<=>) operator. Type juggling is obviously in action in this kind of comparison. We've already explored how these regular comparisons work in the previous sections.

SORT_NUMERIC

Next, we have SORT_NUMERIC.

As the name suggests, SORT_NUMERIC performs numerical comparisons and that by converting each element to a number before comparison. This leads to pretty surprising, albeit easy to reason about, results.

Consider the following code:

<?php

$arr = [-1, 'us', 0, 'is', 'as'];
sort($arr, SORT_NUMERIC);

print_r($arr);

We have a mixture of different kinds of elements in $arr and want to see the result when it's sorted using the SORT_NUMERIC flag set:

Array ( [0] => -1 [1] => us [2] => is [3] => as [4] => 1 )

We obviously know that 'as' comes before 'is', and 'is' comes before 'us', yet in the sorted array above, that's not the case. Each of these three strings remains in their original relative orders, thanks to the fact that they all coerce to the same number 0 and that sort() does stable sorting.

The fact that all the strings coerce to 0 can be further confirmed by the fact that in the sorted array, -1 comes first, followed by the strings, followed by 1.

SORT_STRING

Let's now move to the third kind that is SORT_STRING.

Just like SORT_NUMERIC, SORT_STRING is very easy to make sense of. It means that the comparisons are all made in terms of strings — or better to say, the comparisons are lexical.

In SORT_STRING, when comparing two elements, they both are converted into strings and then the strcmp() function is used to perform the comparison.

Comparisons are always lexical in the case of SORT_STRING, even though when numbers are coerced into strings, they becomes numeric strings, which are otherwise compared numerically in PHP.

Shown below is an example:

<?php

$arr = [20, 3, 25, 100];
sort($arr, SORT_STRING);

print_r($arr);

Without SORT_STRING, we expect the result to be as follows: 3, 20, 25, 100, as sort() sorts in ascending order. But with the SORT_STRING flag set, there's a significant difference:

Array ( [0] => 100 [1] => 20 [2] => 25 [3] => 3 )

The reason is simple: the first character of '100' is lexically smaller than the first character of '20', '25' and '3', likewise it is lexically smaller than all of the given strings. A similar idea applies to '20', '25' and '3'.

SORT_NATURAL

Let's now talk about SORT_NATURAL.

As stated above, SORT_NATURAL works pretty much like SORT_STRING in that it also converts each element to a string before comparison.

However, it uses what's called a 'natural comparison algorithm' whereby sequences of digits are treated how we'd otherwise treat them naturally; they are NOT treated lexically.

For example, lexically 'a50' is smaller than 'a6' (because of the comparison of the corresponding characters 5 and 6), however naturally, 'a50' is larger than 'a6', as 50 is naturally a larger number as compared to 6.

Let's see SORT_NATURAL in action on the same array of four numbers shown above:

<?php

$arr = [20, 3, 25, 100];
sort($arr, SORT_NATURAL);

print_r($arr);
Array ( [0] => 3 [1] => 20 [2] => 25 [3] => 100 )

And there's the difference.

SORT_FLAG_CASE

Last but not the least, we have the flag SORT_FLAG_CASE.

SORT_FLAG_CASE is basically just an additional flag that we can blend in with either SORT_STRING or SORT_NATURAL in order to make the string comparison case-insensitive. The blending is done via the bitwise OR (|) operator.

What SORT_FLAG_CASE does internally is that when two strings are compared, they are first converted into lower case and then the comparison is performed.

Consider the following example:

<?php

$technologies = ['php', 'Python', 'ada', 'JavaScript', 'jQuery', 'jest'];
sort($technologies);

print_r($technologies);
Array ( [0] => JavaScript [1] => Python [2] => ada [3] => jQuery [4] => jest [5] => php )

Here we have an array of strings, some starting with lowercase letters while some starting with uppercase letters.

When we sort it, the strings starting with uppercase letters are grouped together and then we have all the strings starting with lowercase letters. And obviously each group itself is sorted.

Now the reason the uppercase strings come first is because the uppercase alphabet comes before the lowercase alphabet lexically. (The code unit of A is 65 while the code unit of a is 97.)

Anyways, now let's make the comparison case-insensitive using SORT_STRING blended together with SORT_FLAG_CASE:

<?php

$technologies = ['php', 'Python', 'ada', 'JavaScript', 'jQuery', 'jest'];
sort($technologies, SORT_STRING | SORT_FLAG_CASE);

print_r($technologies);
Array ( [0] => ada [1] => JavaScript [2] => jest [3] => jQuery [4] => php [5] => Python )

Witness the sorted array here — the strings are now grouped according to their first letters regardless of the casing of those letters. We have 'ada' right at the start because a comes first, while 'Python' at the end because 'p' (in lowercase) comes last here.

Just as shown in this example, SORT_FLAG_CASE is really handy when we need to sort an array of strings where we know that the strings would have different casings.

User-defined sorting

There are three sorting functions in PHP that work with user-defined comparison functions instead of relying on built-in comparison functions. They are usort(), uksort() and uasort().

Recall that the 'u' here stands for 'user-defined' which signifies the fact that each of these functions operates around a user-defined comparison function.

These sorting functions are handy when our arrays are comprised of elements whose comparison does not happen the way we intend it to happen; in that case, our own custom comparison function comes to the rescue.

Here's the syntax of usort(), which is the same as that of uksort() and uasort():

usort(&$array, $comparison_fx)

The first argument is the array to sort while the second argument is the function to use when comparing two elements of the array in the sorting routine.

As the sorting routine progresses, it invokes the given comparison function on different pairs of elements to determine which one should come before the other.

In this regard, the comparison function receives the elements as two arguments, let's call them $a and $b. It's meant to return either -1, 0, or 1, to help the sorting routine determine which of the two elements should come first.

Here's how these return values work:

  • -1 if $a is less than $b, i.e. $a should come before $b.
  • 0 if $a is equal to $b.
  • 1 if $a is greater than $b, i.e. $a should come after $b.

Let's consider an example.

Suppose we have the following array where each element represents a point on a Cartesian plane:

<?php

$points = [ [0, 1], [1, 0], [3, 9], [-5, 9], [-1, -1], [-9, -27] ];

Our job is to sort the array based on the distances of each point from the origin, in ascending order.

Obviously, because computing the distance requires a bit of processing, we need a configurable sorting function — usort() to be precise.

First, let's define the comparison function:

<?php

function compare_distances($p1, $p2) {
   $dist1 = ($p1[0] ** 2 + $p1[1] ** 2) ** 0.5;
   $dist2 = ($p2[0] ** 2 + $p2[1] ** 2) ** 0.5;

   return $dist1 <=> $dist2;
}

$p1 and $p2 represent two different points in the $points array. We compute the distances of both of them from the origin and then perform regular comparison of the resulting distances (which are numbers) via the spaceship (<=>) operator.

Now, let's make the call to usort() providing in our function's name as the second argument:

<?php

function compare_distances($p1, $p2) {
   $dist1 = ($p1[0] ** 2 + $p1[1] ** 2) ** 0.5;
   $dist2 = ($p2[0] ** 2 + $p2[1] ** 2) ** 0.5;

   return $dist1 <=> $dist2;
}

$points = [ [0, 1], [1, 0], [3, 9], [-5, 9], [-1, -1], [-9, -27] ];
usort($points, 'compare_distances');

As we shall learn in the PHP Functions unit, if PHP expects a function as an argument, we can provide it a string denoting the name of a global function; it's not possible to pass a global function directly.

Now, let's see the ordering of elements in the $points array by outputting it. Owing to the fact that print_r() would make the output quite verbose in this case, we'll spin up a foreach loop to output all of the individual points, along with their distances from the origin.

Here's the final code:

<?php

function compare_distances($p1, $p2) {
   $dist1 = ($p1[0] ** 2 + $p1[1] ** 2) ** 0.5;
   $dist2 = ($p2[0] ** 2 + $p2[1] ** 2) ** 0.5;

   return $dist1 <=> $dist2;
}

$points = [ [0, 1], [1, 0], [3, 9], [-5, 9], [-1, -1], [-9, -27] ];
usort($points, 'compare_distances');

foreach ($points as $point) {
   $distance = ($point[0] ** 2 + $point[1] ** 2) ** 0.5;
   printf("(%d, %d), distance: %.2f\n", $point[0], $point[1], $distance);
}

Let's run it and see the output produced:

(0, 1), distance: 1.00 (1, 0), distance: 1.00 (-1, -1), distance: 1.41 (3, 9), distance: 9.49 (-5, 9), distance: 10.30 (-9, -27), distance: 28.46

And there you have it! The points have been lined up in increasing order of their distances from the origin.

If we wish to sort the points in decreasing order of their distances, we can simply negate the output of the spaceship (<=>) operator, using the arithmetic negation (-) operator, in the function compare_distances(), as follows.

Let's try doing so:

<?php

function compare_distances($p1, $p2) {
   $dist1 = ($p1[0] ** 2 + $p1[1] ** 2) ** 0.5;
   $dist2 = ($p2[0] ** 2 + $p2[1] ** 2) ** 0.5;

   // This time we want the points in descending order.
return -($dist1 <=> $dist2); } $points = [ [0, 1], [1, 0], [3, 9], [-5, 9], [-1, -1], [-9, -27] ]; usort($points, 'compare_distances'); foreach ($points as $point) { $distance = ($point[0] ** 2 + $point[1] ** 2) ** 0.5; printf("(%d, %d), distance: %.2f\n", $point[0], $point[1], $distance); }
(-9, -27), distance: 28.46 (-5, 9), distance: 10.30 (3, 9), distance: 9.49 (-1, -1), distance: 1.41 (0, 1), distance: 1.00 (1, 0), distance: 1.00

As you can see, the points are now lined up in decreasing order. Amazing!

Another way to accomplish this exact same thing is to swap the operands of the spaceship operator in compare_distances(), as follows:

<?php

function compare_distances($p1, $p2) {
   $dist1 = ($p1[0] ** 2 + $p1[1] ** 2) ** 0.5;
   $dist2 = ($p2[0] ** 2 + $p2[1] ** 2) ** 0.5;

   // Negating is the same as swapping the order of operands.
return $dist2 <=> $dist1; } ...
(-9, -27), distance: 28.46 (-5, 9), distance: 10.30 (3, 9), distance: 9.49 (-1, -1), distance: 1.41 (0, 1), distance: 1.00 (1, 0), distance: 1.00

So what do you say? Isn't this easy?

If you noticed one thing in all of the examples above, it's that the point [0, 1] always comes before [1, 0]. This is because the distances of both these points are the exact same, and then since all sorting functions in PHP are stable, the relative order of these two points remains the same as in the initial array.