PHP: Sorting Arrays of Arrays
PHP provides a range of functions for sorting data based on the key or value of an associative array. Where it gets complicated is when you have an array of associative arrays which you need to sort according to one or more attributes.
This is a very common task for PHP programmers as data returned from database/SQL queries often appears in this format (ref: pg_fetch_assoc and mysql_fetch_assoc).
Note: The array-multisort function provides an equivalent functionality to the code presented here, but is rather convoluted to use.
The Data
The examples below are based on the following data as an associative array. The data contains six records, each of which has a value for "firstname", "lastname" and for "age".
<?PHP
$data = array(
array("firstname" => "Mary", "lastname" => "Johnson", "age" => 25),
array("firstname" => "Amanda", "lastname" => "Miller", "age" => 18),
array("firstname" => "James", "lastname" => "Brown", "age" => 31),
array("firstname" => "Patricia", "lastname" => "Williams", "age" => 7),
array("firstname" => "Michael", "lastname" => "Davis", "age" => 43),
array("firstname" => "Sarah", "lastname" => "Miller", "age" => 24),
array("firstname" => "Patrick", "lastname" => "Miller", "age" => 27)
);
?>
This dataset can be represented in tabular format as:
| firstname | lastname | age | |
|---|---|---|---|
| [0] | Mary | Johnson | 25 |
| [1] | Amanda | Miller | 18 |
| [2] | James | Brown | 31 |
| [3] | Patricia | Williams | 7 |
| [4] | Michael | Davis | 43 |
| [5] | Sarah | Miller | 24 |
| [6] | Patrick | Miller | 27 |
Let's start sorting!
Sorting on a single field
It turns out that sorting associative arrays is really quite simple - IF you know beforehand which field you want to sort by.
For example, to sort by lastname you can use the following function:
<?PHP
function compare_lastname($a, $b)
{
return strnatcmp($a['lastname'], $b['lastname']);
}
// sort alphabetically by name
usort($data, 'compare_lastname');
?>
The output is as follows:
| firstname | lastname | age | |
|---|---|---|---|
| [0] | James | Brown | 31 |
| [1] | Michael | Davis | 43 |
| [2] | Mary | Johnson | 25 |
| [3] | Sarah | Miller | 24 |
| [4] | Patrick | Miller | 27 |
| [5] | Amanda | Miller | 18 |
| [6] | Patricia | Williams | 7 |
You should be able quite easily to modify the compare_lastname function to order by firstname (see below), by age, or any other field you happen to have in the associative array. The strnatcmp function is particularly handy as it can be applied to numbers as well as strings.
To maintain index association, replace usort with uasort in the code:
| firstname | lastname | age | |
|---|---|---|---|
| [2] | James | Brown | 31 |
| [4] | Michael | Davis | 43 |
| [0] | Mary | Johnson | 25 |
| [5] | Sarah | Miller | 24 |
| [6] | Patrick | Miller | 27 |
| [1] | Amanda | Miller | 18 |
| [3] | Patricia | Williams | 7 |
This is useful when the 'index' is a database id that needs to be associated with each record.
Sorting on multiple fields
If you want to sort by lastname AND firstname, then you might think that just applying two sorts in sequence would give you the desired result:
<?PHP
function compare_firstname($a, $b)
{
return strnatcmp($a['firstname'], $b['firstname']);
}
// sort alphabetically by firstname, then by lastname
usort($data, 'compare_firstname');
usort($data, 'compare_lastname');
?>
You might think so, but you'd be wrong. Using the usort function this isn't the case. As the PHP manual says, since version 4.1.0 the sort algorithm is no longer stable (see Quick Sort Algorithm for more detail):
If two members compare as equal, their order in the sorted array is undefined
This means that, after ordering by first name, the result of then sorting by last name will not necessarily retain the correct order of first names.
So what's the solution? We need a new function:
<?PHP
function compare_fullname($a, $b)
{
$retval = strnatcmp($a['lastname'], $b['lastname']);
if(!$retval) return strnatcmp($a['firstname'], $b['firstname']);
return $retval;
}
// sort alphabetically by firstname and lastname
usort($data, 'compare_fullname');
?>
Finally, the result we wanted:
| firstname | lastname | age | |
|---|---|---|---|
| [0] | James | Brown | 31 |
| [1] | Michael | Davis | 43 |
| [2] | Mary | Johnson | 25 |
| [3] | Amanda | Miller | 18 |
| [4] | Patrick | Miller | 27 |
| [5] | Sarah | Miller | 24 |
| [6] | Patricia | Williams | 7 |
This function works because it first compares the last names, and only if they are identical will it compare the first names to work out the correct order.
Using an 'anonymous' function
This is where it starts to get interesting. You can create an anonymous function based on information gathered at run time. This allows for greater flexibility, without too much extra coding.
<?PHP
function makeSortFunction($field)
{
$code = "return strnatcmp(\$a['$field'], \$b['$field']);";
return create_function('$a,$b', $code);
}
$compare = makeSortFunction('age');
usort($data, $compare);
?>
The call can then be shortened to:
<?PHP
usort($data, makeSortFunction('age'));
?>
Finally, you can wrap it all into a single function as shown below. Now the code's starting to look more 'readable' which is a good sign.
<?PHP
function orderBy($data, $field)
{
$code = "return strnatcmp(\$a['$field'], \$b['$field']);";
usort($data, create_function('$a,$b', $code));
return $data;
}
$data = orderBy($data, 'age');
?>
| firstname | lastname | age | |
|---|---|---|---|
| [0] | Patricia | Williams | 7 |
| [1] | Amanda | Miller | 18 |
| [2] | Sarah | Miller | 24 |
| [3] | Mary | Johnson | 25 |
| [4] | Patrick | Miller | 27 |
| [5] | James | Brown | 31 |
| [6] | Michael | Davis | 43 |
Using 'pass by reference'
We can also pass the $data variable by reference and no longer worry about returning a value from the function. This is how built-in functions such as usort work (see the link below for more details from the PHP website).
<?PHP
function orderBy(&$data, $field)
{
$code = "return strnatcmp(\$a['$field'], \$b['$field']);";
usort($data, create_function('$a,$b', $code));
}
orderBy($data, 'age');
?>
Simply adding the & to the function definition means that instead of passing a copy of $data to the function, a reference or pointer is passed. That means that any manipulation of the array within the function will also apply outside the function so no return command is required.
The function is otherwise equivalent to the 'pass by value' version.
Sorting based on a list of values
There are situations where it's not possible to use a numeric or alphabet-based sort routine. Suppose that you have the following data to present on a website:
| name | position | |
|---|---|---|
| [0] | Mary Johnson | Secretary |
| [1] | Amanda Miller | Member |
| [2] | James Brown | Member |
| [3] | Patricia Williams | Member |
| [4] | Michael Davis | President |
| [5] | Sarah Miller | Vice-President |
| [6] | Patrick Miller | Member |
And that you want to sort these names by their position, based on the following list:
<?PHP
$sortorder = array(
'President',
'Vice-President',
'Secretary',
'Member'
);
?>
Again, it's not as difficult as you might think. All we need is the following custom function:
<?PHP
function mySort($a, $b)
{
global $sortorder;
if($a['position'] == $b['position']) {
return strcmp($a['name'], $b['name']);
}
$cmpa = array_search($a['position'], $sortorder);
$cmpb = array_search($b['position'], $sortorder);
return ($cmpa > $cmpb) ? 1 : -1;
}
?>
Note: If you don't have a second field that you want to sort by, just replace the highlighted line with return 0;
All we're doing here is looking at the values in the position field. If they're identical then we sort by the name field instead. Otherwise we do a comparison of where in the sortorder list the relevant values appear and sort based on that. The output is as follows:
| name | position | |
|---|---|---|
| [0] | Michael Davis | President |
| [1] | Sarah Miller | Vice-President |
| [2] | Mary Johnson | Secretary |
| [3] | Amanda Miller | Member |
| [4] | James Brown | Member |
| [5] | Patricia Williams | Member |
| [6] | Patrick Miller | Member |
As detailed above for simpler cases we can now enhance this function to make it more generic and re-usable, but I'll leave that as an exercise for you. You would need pass the sortorder array to the function along with details of which fields to sort by.
If you're working on the web with this type of data, you might also want to read the article Formatting Data as HTML which presents instructions for converting PHP data to HTML tables, lists and forms.
Multiple row sorting using a closure
Thomas Heuer from Germany has been kind enough to provide the following code and examples, which looks very nice, but will only work with PHP 5.3 and higher:
<?PHP
function sortArray($data, $field)
{
if(!is_array($field)) $field = array($field);
usort($data, function($a, $b) use($field) {
$retval = 0;
foreach($field as $fieldname) {
if($retval == 0) $retval = strnatcmp($a[$fieldname],$b[$fieldname]);
}
return $retval;
});
return $data;
}
?>
Example calls:
<?PHP
$data = sortArray($data, 'age');
$data = sortArray($data, array('lastname', 'firstname'));
?>
Note: I haven't been able to test this code yet as we're still running PHP 5.2, but it looks like it will work. You should also be able to modify the function to use 'pass by reference' as per previous examples.
References
Translations
- Russian - by Anton Afanasyev
David Hopkins 2 November, 2007
Thanks for this article saved me some time.
Matt Webb 15 November, 2007
Quick question for you - is it possible to expand the samples to sort by three columns?
Yes, of course. The code will be something like this (untested):
function compare_three_columns($a, $b){
$retval = strnatcmp($a['col1'], $b['col1']);
if(!$retval) $retval = strnatcmp($a['col2'], $b['col2']);
if(!$retval) $retval = strnatcmp($a['col3'], $b['col3']);
return $retval;
}
Haroon Ahmad 15 August, 2008
Very good post on handy use of built-in functions. It can perform array sorting tasks easily and quickly with less code.
staticfloat 23 December, 2008
A small improvement; combines a few concepts, and allows for a variable number of fields to search for;
function awesomeSort(&$data, $fields) {//Error checking, if it's a non-sortable list or $fields is empty
if( count( $fields ) == 0 or count( $data ) <= 1 ) return;
if( !is_array( $fields ) ) $fields = Array( $fields );
$code = "$retval = strnatcmp($a["".$fields[0].""], $b["".$fields[0].""]);";
for( $i=1;$i<count($fields);$i++ )
$code .= "if( !$retval ) $retval = strnatcmp($a["".$fields[$i].""], $b["".$fields[$i].""]);";
$code .= "return $retval;";
usort( $data, create_function( '$a,$b', $code ) );
}
Eakand 17 June, 2009
Great functions!!You saved my times a lot!!
Rob Williams 25 November, 2009
Extremely useful article! Makes sorting associative arrays so easy, especially when trying to sort on multple keys!
Jethro Arenz 5 March, 2010
Great Freaking article. You a life saver.
Thomas Heuer 25 June, 2010
Based on your examples, I've put that all together, having an array-sorting-function for single or multiple row-sorting - using a closure.
This will only work with PHP 5.3+
Thanks heaps. I've added your function to the page
Jerodev 1 September, 2010
Is it also possible to sort the array descending?
Sure. Just swap $a and $b in the strnatcmp() function calls. Or in the sort function definition (but not both)
Ray 8 December, 2010
wow great!
tnx!
I had problems with finding the correct function for sorting my arrays. With this it was easy peasy
Mike M 28 December, 2010
Well I implemented #2 and it worked as you said it would, but to be honest, I don't understand why. Could you please explain; First of all, a function compare_lastname($a, $b) is defined with 2 args, then its called with no args. That shouldn't even work, and what I don't understand even more than that is how the function even knows what $a and $b are, because they are never defined anywhere. Explaining these two concepts would really help my understanding of php. I'm kinda javaish. Thank You
I'll try. The usort and similar sorting functions in PHP work by applying a comparison function on successive pairs of values in the array to be sorted. So $a and $b are just references to the two elements being compared each time.

Maybe our JavaScript articles on sorting algorithms will help?
Jon 18 January, 2012
I took staticfloat's code and made it work (some of the quotes were messed up):
function sortByKeys(&$arr, $fields){
if(count($fields) == 0 || count($arr) <= 1) return;
if(!is_array($fields)) $fields = Array($fields);
$code = '$retval = strnatcmp($a["'.$fields[0].'"], $b["'.$fields[0].'"]);';
for($i=1; $i < count($fields); $i++) {
$code .= 'if(!$retval) $retval = strnatcmp($a["'.$fields[$i].'"], $b["'.$fields[$i].'"]);';
}
$code .= 'return $retval;';
usort($arr, create_function('$a,$b', $code));
}
Thanks for the great post!