skip to content

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:

firstnamelastnameage
[0]MaryJohnson25
[1]AmandaMiller18
[2]JamesBrown31
[3]PatriciaWilliams7
[4]MichaelDavis43
[5]SarahMiller24
[6]PatrickMiller27

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:

firstnamelastnameage
[0]JamesBrown31
[1]MichaelDavis43
[2]MaryJohnson25
[3]SarahMiller24
[4]PatrickMiller27
[5]AmandaMiller18
[6]PatriciaWilliams7

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:

firstnamelastnameage
[2]JamesBrown31
[4]MichaelDavis43
[0]MaryJohnson25
[5]SarahMiller24
[6]PatrickMiller27
[1]AmandaMiller18
[3]PatriciaWilliams7

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:

firstnamelastnameage
[0]JamesBrown31
[1]MichaelDavis43
[2]MaryJohnson25
[3]AmandaMiller18
[4]PatrickMiller27
[5]SarahMiller24
[6]PatriciaWilliams7

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'); ?>
firstnamelastnameage
[0]PatriciaWilliams7
[1]AmandaMiller18
[2]SarahMiller24
[3]MaryJohnson25
[4]PatrickMiller27
[5]JamesBrown31
[6]MichaelDavis43

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:

nameposition
[0]Mary JohnsonSecretary
[1]Amanda MillerMember
[2]James BrownMember
[3]Patricia WilliamsMember
[4]Michael DavisPresident
[5]Sarah MillerVice-President
[6]Patrick MillerMember

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:

nameposition
[0]Michael DavisPresident
[1]Sarah MillerVice-President
[2]Mary JohnsonSecretary
[3]Amanda MillerMember
[4]James BrownMember
[5]Patricia WilliamsMember
[6]Patrick MillerMember

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

< PHP


Like Tweet     Bitcoin

User Comments and Notes

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!
I had problems with finding the correct function for sorting my arrays. With this it was easy peasy tnx!

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!

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

Send Feedback

Use this form to send a message to The Art of Web:


used only for us to reply, and to display your gravatar.

CAPTCHA refresh

<- copy the digits from the image into this box

press Esc or click outside this box to close

Load Feedback Form

top