PHP: Sorting Arrays of ArraysPHP provides a range of functions that you can use 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 a particular attribute or combination of 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 DataThe 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". $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:
Let's start sorting! Sorting on a single fieldIt 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: function compare_lastname($a, $b)
{
return strnatcmp($a['lastname'], $b['lastname']);
}
# sort alphabetically by name
usort($data, 'compare_lastname');
The output is as follows:
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 very handy as it can be applied to both numbers and strings. If you need to 'maintain index association', just replace usort with uasort which will result in the following output:
This is useful when the 'index' is a database id that needs to be associated with each record. Sorting on multiple fieldsIf 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: 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');
Unfortunately, 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: 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:
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' functionThis 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. 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: 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. function orderBy($data, $field)
{
$code = "return strnatcmp(\$a['$field'], \$b['$field']);";
usort($data, create_function('$a,$b', $code));
return $data;
}
$data = orderBy($data, 'age');
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). 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 valuesThere 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:
And that you want to sort these names by their position, based on the following list: $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: 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:
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. ReferencesTranslations
Feedback and Questions2007-11-01: David Hopkins says: Thanks for this article saved me some time. 2007-11-14: Matt Webb says: 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): 2008-08-15: Haroon Ahmad (PHP Web Developer) says: Very good post on handy use of built-in functions. It can perform array sorting tasks easily and quickly with less code. 2009-06-17: Eakand says: Great functions!!You saved my times a lot!! |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
© Copyright 2009 Chirp Internet
- Page Last Modified: 25 April 2008
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||