PHP: Sorting Arrays of Arrays
PHP provides a range of functions for sorting data based on either the key or value of an associative array. Where it gets complicated is when you need to sort an array of associative arrays by one or more conditions.
This is a very common task for PHP programmers as data returned from database/SQL queries often appears in associative array format (i.e. using pg_fetch_assoc and mysql_fetch_assoc).
The array-multisort function provides an equivalent functionality to the code presented here, but is rather convoluted to use.
The Data
The following examples 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("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:
index | 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
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 usort (user-defined search) 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:
index | firstname | lastname | age |
---|---|---|---|
[0] | James | Brown | 31 |
[1] | Michael | Davis | 43 |
[2] | Mary | Johnson | 25 |
[3] | Amanda | Miller | 18 |
[4] | Sarah | Miller | 24 |
[5] | Patrick | Miller | 27 |
[6] | Patricia | Williams | 7 |
To sort by different fields just replace the compare_lastname function with a function that orders by firstname (see below), by age, or any other field in the associative array. The strnatcmp ("natural order" string comparison) function is handy here as it can be applied to numbers as well as strings.
Below we will explore using a single function to sort by different fields by specifying the sort field in the function call. The comparison function can also be generated on the fly as an anonymous function.
Maintaining index association
To maintain index association, replace usort with uasort in the code:
<?PHP
function compare_lastname($a, $b)
{
return strnatcmp($a['lastname'], $b['lastname']);
}
// sort alphabetically by name
uasort($data, 'compare_lastname');
?>
with the result that the array values can be referenced using the original index values:
index | firstname | lastname | age |
---|---|---|---|
[2] | James | Brown | 31 |
[4] | Michael | Davis | 43 |
[0] | Mary | Johnson | 25 |
[1] | Amanda | Miller | 18 |
[5] | Sarah | Miller | 24 |
[6] | Patrick | Miller | 27 |
[3] | Patricia | Williams | 7 |
This is useful when the 'index' is a database id that needs to stay 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']);
}
function compare_lastname($a, $b)
{
return strnatcmp($a['lastname'], $b['lastname']);
}
// sort alphabetically by firstname, then by lastname
usort($data, __NAMESPACE__ . '\compare_firstname');
usort($data, __NAMESPACE__ . '\compare_lastname');
?>
Note that here we've specifed the current namespace __NAMESPACE__ to make the code compatible with PHP namespaces. Otherwise any function you pass has to exist in the global namespace.
You might think so, but since PHP version 4.1 the usort and related functions will essentially shuffle the array before sorting (the algorithm is 'no longer stable'):
If two members compare as equal, their order in the sorted array is undefined
See our Quick Sort Algorithm article for more detail.
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 that can compare both fields at the same time and then apply the sort:
<?PHP
function compare_fullname($a, $b)
{
// sort by last name
$retval = strnatcmp($a['lastname'], $b['lastname']);
// if last names are identical, sort by first name
if(!$retval) $retval = strnatcmp($a['firstname'], $b['firstname']);
return $retval;
}
// sort alphabetically by firstname and lastname
usort($data, __NAMESPACE__ . '\compare_fullname');
?>
Finally, the result we wanted:
index | 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.
You can extend this function to use more variables by inserting more conditions every time $retval has a zero value.
Calling from a class
If you are trying to sort using a function inside a class, you can't just pass the comparison function as '$this->compare_fullname', but instead need to include either the object or the class name in the call to usort. Here's an example:
<?PHP
class ClassName
{
public $data = [
...
];
public function compare_fullname($a, $b)
{
$retval = strnatcmp($a['lastname'], $b['lastname']);
if(!$retval) return strnatcmp($a['firstname'], $b['firstname']);
return $retval;
}
public function doStuff()
{
// apply sort function from INSIDE class
usort($this->data, [$this, 'compare_fullname']);
}
}
// apply sort function from OUTSIDE class
$myObject = new ClassName();
usort($myObject->data, ["ClassName", 'compare_fullname']);
?>
From inside the class $this refers to both the object as well as the class and all associated functions. From outside we can't use $this so have to refer to the class by name.
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;
}
$sorted_data = orderBy($data, 'age');
?>
index | 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 |
We now have a simple generic function that can be used to sort any associative array on a single scalar attribute.
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.
create_function is deprecated
As of PHP 8 you can no longer use create_function so the previous code needs to be updated as follows:
<?PHP
function orderBy($data, $field)
{
usort($data, function($a, $b) use ($field) {
return strnatcmp($a[$field], $b[$field]);
});
return $data;
}
?>
And similarly for the 'pass by reference' version. Note that the keyword use in this code brings the $field variable into the scope of the anonymous function.
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:
index | 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 = [
'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;
}
?>
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:
index | 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 = [$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, ['lastname', 'firstname']);
?>
References
- PHP.net: Array Functions
- PHP.net: Passing by Reference
Related Articles - Sorting Algorithms
- JavaScript DHTML Insertion Sort
- JavaScript DHTML Shell Sort
- JavaScript Sorting Algorithm Comparison
- JavaScript DHTML Sorting Using OOP - Example 1
- JavaScript DHTML Sorting Algorithms
- JavaScript DHTML Quick Sort
- JavaScript DHTML Bubble Sort
- PHP Sorting Arrays of Arrays
- PHP Sorting SimpleXMLElement Object arrays
Cristian 10 December, 2019
Hi, How to sort by last name an array like this:
$array = array(
array("name" => "Mary Johnson","age" => 43),
array("name" => "Amanda Miller","age" => 23),
array("name" => "James Brown","age" => 47),
array("name" => "Patricia Williams","age" => 31),
array("name" => "Michael Davis","age" => 15),
array("name" => "Sarah Miller","age" => 35),
array("name" => "Patrick Miller","age" => 44)
);
is it possible? thank you
Yes. You just need a sorting function that first extracts the last names, and then compares them.
Ömür Yanıkoğlu 7 November, 2018
Thank you!
"Sorting based on a list of values" is the one for me. Awesome!
Sanjay Oza 15 June, 2016
This is nice article but I am not able to sort in descending order by perticular field(i.e. last name or age), it gives me an error "rsort() expects parameter 2 to be long, string given". Please help me. Thanks in advance.
The easiest way to reverse the sort is to just switch '$a' and '$b' in the comparison function.
MattAosta 30 May, 2014
Hi, could you help me with sorting array of array with 2nd order-parameter that is shuffle?
$data = array(
array("firstname" => "Mary", "age" => 25),
array("firstname" => "Amanda", "age" => 20),
array("firstname" => "James", "age" => 18),
array("firstname" => "Mary", "age" => 23)
);
and I would like to sort it by "firstname" and then, in the case that there is some "firstname" equal to another "firstname", I would like to have a random.
Could you help me with this?
Since PHP 4.1 the order of the sorted array when some values are equal is 'undefined', which can mean that it's random already.
To force the order to be random you can use something like this:
<?PHP
function sort_name_shuffle($a, $b)
{
$retval = strnatcmp($a['firstname'], $b['firstname']);
if(!$retval) return mt_rand(0,1) ? -1 : 1;
return $retval;
}
// sort alphabetically by firstname and shuffle
usort($data, 'sort_name_shuffle');
?>
Barbara McLennan 24 May, 2014
I need to sort an array on two fields, but one is numeric and the other is a string. How would I do this
ebeer 31 March, 2014
You could also make use of some closures to make your sorting more flexible and composable:
function stabilize_sorters() {
$args = func_get_args();
return function($a, $b) use ($args) {
$retVal = 0;
for ($i = 0; $i < count($args); $i++) {
$fn = $args[$i];
if (0 == $retVal) $retVal = $fn($a, $b);
else break;
}
return $retVal;
};
}
function create_sorter($key, $fn) {
return function($a, $b) use ($key, $fn) {
return call_user_func_array($fn, [$a[$key], $b[$key]]);
};
}
//then you may use it like
usort($anArray,
stabilize_sorters(
create_sorter('name', 'strnatcmp'), create_sorter('age', function($a, $b) {
//some weird comparison you may want to apply to age
}))
);
Yes, I would use something like this now. The article was written well before closures were available in PHP
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!
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?
Ray 8 December, 2010
wow great!
I had problems with finding the correct function for sorting my arrays. With this it was easy peasy tnx!
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)
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
Jethro Arenz 5 March, 2010
Great Freaking article. You a life saver.
Rob Williams 25 November, 2009
Extremely useful article! Makes sorting associative arrays so easy, especially when trying to sort on multple keys!
Eakand 17 June, 2009
Great functions!!You saved my times a lot!!
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 ) );
}
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.
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;
}
David Hopkins 2 November, 2007
Thanks for this article saved me some time.