<

Using PHP usort() to Simplify Complex Data Search

Published on
9,737 Points
2,137 Views
1 Endorsement
Last Modified:
Approved
Introduction
Many of the most common information processing tasks require sorting data sets.  For example, you may want to find the largest or smallest value in a collection.  Or you may want to order the data set in numeric or alphabetical order.  The correct data structure for this kind of work is the array.  Fortunately PHP provides an excellent collection of built-in array sorting functions.  These functions can re-arrange arrays in ascending or descending sequence.  They can preserve or renumber the keys.  They can even sort in "natural order" just like a human being would sort a collection, ignoring case-sensitivity in the data set.  And if everything you need to sort can be expressed in a one-dimensional array, one of the PHP built-in array sorting functions will surely do the trick.

Finding the Minimum and Maximum Values in a One-Dimensional Array
If you need to find a single min/max value, PHP has built-in functions to do the work for you.  For this simple task, you do not need to write any code.  Just call the PHP function that suits your needs.  Choose either min() or max().

But if there are any "interesting" edge cases, you may want to read the man pages carefully.  Because PHP is a loosely typed language, these comparisons are done under rules of type-coercion.  PHP will interpret the values in ways that allows PHP to make a duck-typed comparison.  Which value do you think would be the minimum among these array elements:
$arr = [2, '2', Two];

Open in new window

More readings for understanding of PHP data types are available here:
http://php.net/manual/en/language.types.boolean.php
http://php.net/manual/en/language.operators.comparison.php
http://php.net/manual/en/types.comparisons.php
http://php.net/manual/en/language.types.type-juggling.php
http://php.net/manual/en/language.types.string.php#language.types.string.conversion

Finding some Minimum and Maximum Values in a One-Dimensional Array
If you need to find more than a single min/max value, you need to sort the array.  You can sort in either ascending or descending order, then you can get a collection of the largest or smallest values with array_slice().  Once you have sorted the array, you can return the array in reverse order (you do not need to sort again) with array_reverse().

Using Multidimensional Arrays or Arrays of Objects
If you have a complex data structure, perhaps an array consisting of "sub-arrays" or objects, you cannot use the built-in PHP sorting functions.  PHP cannot directly compare an array to another array or an object to another object, so it will be necessary to write some code.  For these kinds of complex sorts, PHP provides the usort() function, and its related functions uasort() and uksort().  PHP usort() works with a callback function to tell PHP which data elements are greater or lesser.

PHP usort() gives us great flexibility to sort a data set on the basis of sub-elements inside the array elements.  These sub-elements can be array values or object properties.  An example helps to clarify how this works.  For this example, I'll use the following data collection.
<?php
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr[0]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr[1]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-08'; $obj->rating = '23'; $arr[3]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-09'; $obj->rating = '23'; $arr[4]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-11'; $obj->rating = '23'; $arr[6]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-07'; $obj->rating = '20'; $arr[7]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-10'; $obj->rating = '35'; $arr[10] = $obj;

Open in new window

Key things to note:
1. $arr is an array of objects
2. Each of the objects contains two properties, "created" and "rating"
3. The array has numeric keys and some of positions are empty or omitted

Practical Application #1
Sort the array by the "created" property in ascending order. 

For this application we will need to write our own usort() callback function that makes reference to the "created" property.  PHP usort() expects the callback function to return a value that is negative, zero, or positive, depending on the result of the comparison it performs.  The usort() callback will return a negative number if the first value is less than the second value, and a positive number if the first value is equal or greater than the second values.  Here is our sample code, showing how to sort by "created."
<?php
error_reporting(E_ALL);
echo '<pre>';

// CREATE A TEST DATA SET - AN ARRAY OF OBJECTS
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr[0]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr[1]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-08'; $obj->rating = '23'; $arr[3]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-09'; $obj->rating = '23'; $arr[4]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-11'; $obj->rating = '23'; $arr[6]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-07'; $obj->rating = '20'; $arr[7]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-10'; $obj->rating = '35'; $arr[10] = $obj;

// A FUNCTION TO COMPARE created PROPERTIES
function sort_created_asc($a, $b)
{
    return ($a->created < $b->created) ? -1 : 1;
}

// SORT BY created PROPERTY
usort($arr, 'sort_created_asc');
echo PHP_EOL . "SORTED BY created: " . PHP_EOL;
print_r($arr);

Open in new window


Practical Application #2
Sort the array by the "rating" property in ascending order.  Print out the objects with the lowest and highest ratings.

For this application we will need to write another usort() callback function that makes reference to the "rating" property.  PHP usort() expects the callback function to return a value that is negative, zero, or positive, depending on the result of the comparison it performs.  The usort() callback will return a negative number if the first value is less than the second value, and a positive number if the first value is equal or greater than the second values.  Here is our sample code, showing how to sort by "rating" and how to find the objects with the minimum and maximum ratings.  Note the use of current() and end() functions to recover the objects.  We could also have used array_slice() to achieve the same result.

<?php
error_reporting(E_ALL);
echo '<pre>';

// CREATE A TEST DATA SET - AN ARRAY OF OBJECTS
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr[0]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr[1]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-08'; $obj->rating = '23'; $arr[3]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-09'; $obj->rating = '23'; $arr[4]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-11'; $obj->rating = '23'; $arr[6]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-07'; $obj->rating = '20'; $arr[7]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-10'; $obj->rating = '35'; $arr[10] = $obj;

// A FUNCTION TO COMPARE rating PROPERTIES
function sort_rating_asc($a, $b)
{
    return ($a->rating < $b->rating) ? -1 : 1;
}

// SORT BY avg_rating
usort($arr, 'sort_rating_asc');
echo PHP_EOL . "SORTED BY rating: " . PHP_EOL;
print_r($arr);

// FIND THE OBJECTS WITH THE LOWEST AND HIGHEST RATINGS
$min = current($arr);
$max = end($arr);
var_dump($min, $max);

Open in new window


Practical Application #3
Sort the array by the "rating" property, preserving the values of the numeric keys.

This is a little more involved, because as written usort() "... assigns new keys to the elements in array. It will remove any existing keys that may have been assigned, rather than just reordering the keys."  If you've been following along, and running the code examples above, you have seen the array keys getting renumbered by the usort() process.  Clearly usort() cannot do the entire job, so we need a strategy to preserve the keys during the sorting process.

Our strategy will be to use the complementary function, uasort(), that preserves the array keys (either associative or numeric) during the sort process.

<?php 
error_reporting(E_ALL);
echo '<pre>';

// CREATE A TEST DATA SET - AN ARRAY OF OBJECTS
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr[0]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr[1]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-08'; $obj->rating = '23'; $arr[3]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-09'; $obj->rating = '23'; $arr[4]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-11'; $obj->rating = '23'; $arr[6]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-07'; $obj->rating = '20'; $arr[7]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-10'; $obj->rating = '35'; $arr[10] = $obj;

// A FUNCTION TO COMPARE rating PROPERTIES
function sort_rating_asc($a, $b)
{
    return ($a->rating < $b->rating) ? -1 : 1;
}

// SORT AND PRESERVE THE KEYS
uasort($arr, 'sort_rating_asc');

echo PHP_EOL . "WITH KEYS PRESERVED, SORTED BY rating: " . PHP_EOL;
print_r($arr);

Open in new window


Practical Application #4
Sort the array by any property, with the property name assigned in a variable.

This is a bit of a quandary, given the way usort() and its relatives are designed.  The usort() callback uses only two parameters, the comparison values.  And the usort() function itself uses only two parameters, the array and the name of the callback.  There is no place to inject the name of the property we want to use for sorting.  As a result, we have to write one usort() callback for "created" and another nearly identical callback for "rating" and if there were many more properties, we would need many more nearly identical callbacks.  And we could not designate the callback at run time without some spaghetti code -- clearly not an efficient way to write our software!

Fortunately PHP 5.3 introduced the concept of anonymous functions and closures.  This makes it possible to inject our sort property name at run time, rather than writing numerous callbacks.  To facilitate this, PHP has "overloaded" the keyword use.

It may represent a namespace declaration, or it may identify the variables that will be inherited from the parent scope into the anonymous function.  It is this latter meaning of use that we exploit in the example below.

<?php 
error_reporting(E_ALL);
echo '<pre>';

// CREATE A TEST DATA SET - AN ARRAY OF OBJECTS
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr[0]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr[1]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-08'; $obj->rating = '23'; $arr[3]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-09'; $obj->rating = '23'; $arr[4]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-11'; $obj->rating = '23'; $arr[6]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-07'; $obj->rating = '20'; $arr[7]  = $obj;
$obj = new stdClass; $obj->created = '2015-04-10'; $obj->rating = '35'; $arr[10] = $obj;

// A USORT WRAPPER THAT USES A CLOSURE TO INJECT $key INTO THE FUNCTION SCOPE
function sort_by_property(&$items, $key)
{
    return uasort($items, function($a, $b) use ($key){
        return ($a->$key < $b->$key) ? -1 : 1;
    });
}

// WHAT PROPERTY DO WE WANT TO USE FOR THE SORT?
$property = 'rating';

// SORT AND PRESERVE THE KEYS
sort_by_property($arr, $property);

echo PHP_EOL . "WITH KEYS PRESERVED, SORTED BY PROPERTY NAMED $property: " . PHP_EOL;
print_r($arr);

Open in new window


Practical Application #5
Sort the array by associative key, when the key does not fit a regular sort order.

If the keys are straightforward and fit a natural order, perhaps ISO-8601 datetime values, sorting by keys is easy.  Just call PHP ksort() or krsort() and the built-in functions will do the work for you.  But what if the keys have meaning, but no natural order?  In this case we can use uksort() to provide the answer.  In this example we will translate the English-language name of the number into the corresponding integer value before making the comparison.  As a result we can get our output ordered correctly and still preserve the English-language associative keys.

<?php 
error_reporting(E_ALL);
echo '<pre>';

// CREATE A TEST DATA SET - AN ARRAY OF OBJECTS WITH ASSOCIATIVE KEYS
$obj = new stdClass; $obj->created = '2015-04-09'; $obj->rating = '23'; $arr['four']  = $obj;
$obj = new stdClass; $obj->created = '2015-04-11'; $obj->rating = '23'; $arr['six']   = $obj;
$obj = new stdClass; $obj->created = '2015-04-07'; $obj->rating = '20'; $arr['seven'] = $obj;
$obj = new stdClass; $obj->created = '2015-04-10'; $obj->rating = '35'; $arr['ten']   = $obj;
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr['zero']  = $obj;
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr['one']   = $obj;
$obj = new stdClass; $obj->created = '2015-04-08'; $obj->rating = '23'; $arr['three'] = $obj;

// A FUNCTION TO COMPARE STRING KEYS
function sort_string_keys_asc($a, $b)
{
    static $key_translations = array
    ( 'zero'  => 0
    , 'one'   => 1
    , 'two'   => 2
    , 'three' => 3
    , 'four'  => 4
    , 'five'  => 5
    , 'six'   => 6
    , 'seven' => 7
    , 'eight' => 8
    , 'nine'  => 9
    , 'ten'   => 10
    )
    ;
    $a_numeric_key = strtr($a, $key_translations);
    $b_numeric_key = strtr($b, $key_translations);
    return ($a_numeric_key < $b_numeric_key) ? -1 : 1;
}

// SORT BY NUMERIC REPRESENTATION OF THE KEYS
uksort($arr, 'sort_string_keys_asc');

echo PHP_EOL . "SORTED BY NUMERIC TRANSLATION OF ASSOCIATIVE KEY: " . PHP_EOL;
print_r($arr);

Open in new window


Practical Application #6
Sort the array by associative key, when the key must be treated in a natural order and/or case-insensitive manner.

This concept may be helpful when dealing with poorly filtered input or unnormalized data.  Such data can appear in systems that do not use case-sensitivity in the file names, or in collections that use number strings of different lengths.  A human being would probably look at "Img_10" and expect it to come after "Img_9" but PHP will not sort those elements in a human-friendly way.  Because PHP comparison functions do not consider fragmentary values or the case-sensitivity of the data, the traditional sorting functions aren't very useful when we're looking for "natural" order.  But PHP has the strnatcasecmp() function to handle this problem.  All we have to do is use it as the callback function in uksort(), like this example. 

Note two things tangentially related to this Practical Application.  First, there are other XXXcmp() functions that can be used as usort() callbacks; they are mentioned on the See Also paragraph of the strnatcasecmp() man page.  Second, at the time of this writing (2015) the "natural" order works well for ASCII strings, but as UTF-8 replaces ASCII encoding, it's worth getting a good understanding of character encoding and testing your assumptions about natural order of multi-byte characters.

<?php // demo/uksort_case_example.php
error_reporting(E_ALL);
echo '<pre>';

// CREATE A TEST DATA SET - AN ARRAY OF OBJECTS WITH MIXED-CASE ASSOCIATIVE KEYS
$obj = new stdClass; $obj->created = '2015-04-09'; $obj->rating = '23'; $arr['A_100'] = $obj;
$obj = new stdClass; $obj->created = '2015-04-11'; $obj->rating = '23'; $arr['a_99']  = $obj;
$obj = new stdClass; $obj->created = '2015-04-07'; $obj->rating = '20'; $arr['B']     = $obj;
$obj = new stdClass; $obj->created = '2015-04-10'; $obj->rating = '35'; $arr['d']     = $obj;
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr['ee']    = $obj;
$obj = new stdClass; $obj->created = '2015-04-06'; $obj->rating = '23'; $arr['F*?']   = $obj;
$obj = new stdClass; $obj->created = '2015-04-08'; $obj->rating = '23'; $arr['GGG']   = $obj;

// SORT BY KEYS - CASE-INSENSITIVE
uksort($arr, 'strnatcasecmp');

echo PHP_EOL . "SORTED BY CASE-INSENSITIVE ASSOCIATIVE KEY: " . PHP_EOL;
print_r($arr);

Open in new window


Summary
This article has shown how to identify minimum and maximum values in data collections, and has shown how to sort arrays of values.  Not only can we sort simple data structures, we can also sort complex data structures.  We can sort a complex array and preserve the keys.   And with the application of anonymous functions, we can get away from the rigid structure of the original PHP usort() design, and create an elegant and generalized solution for the usort() callback.

Please give us your feedback!
If you found this article helpful, please click the "thumb's up" button below. Doing so lets the E-E community know what is valuable for E-E members and helps provide direction for future articles.  If you have questions or comments, please add them.  Thanks!
 
1
Comment
Author:Ray Paseur
1 Comment
 
LVL 29

Expert Comment

by:fibo
Seems that the ->key use you describe will be ghe solution to my previous use of usort wjth the argument array passed by reference, now deprecated.
Thx
0

Featured Post

Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

Join & Write a Comment

The viewer will learn how to count occurrences of each item in an array.
The viewer will learn how to create a basic form using some HTML5 and PHP for later processing. Set up your basic HTML file. Open your form tag and set the method and action attributes.: (CODE) Set up your first few inputs one for the name and …
Suggested Courses
Course of the Month14 days, 8 hours left to enroll

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month