Programming is a wonderful mix of art and science; source code is both a poem and a math problem. It should be as simple and elegant as it is functional and fast. This blog is about that (along with whatever else I feel like writing about).

Wednesday, January 02, 2008

PHP usort(): Sort an array of objects

PHP is nice enough to include a nice, fast sort() function that works on simple datatypes like numbers and strings. Saves you the time of having to write your own sorting functions. However, how often is your important data in an array of integers or strings? Considerably more often, you have an array of more complex data structures that you'd like sorted. And the sort() function just can't do it.

One option you have is to write your own sorting function, as I've already mentioned. In fact, I've done this numerous times. But why should I have to type out a quicksort algorithm -- slightly modified to compare objects or associative arrays -- rather than having the libraries do it and move on to more important things. This isn't CS class any more. I shouldn't have to "prove" that I understand the quicksort algorithm ... I need to get things done!

Well, as it turns out, PHP does indeed offer the capability to sort an array of objects. And it's not so bad. Check out usort. The basic concept is that you pass it the array of objects and a function (or static method) that'll act as a comparison function between two objects. This function is something you write yourself, since you're the one who knows about your classes. I usually put it right in the class itself, acting as a static method. Here's a quick example:


class Item {
var $a;
var $b;

function Item($pA, $pB) {
$this->a = $pA;
$this->b = $pB;
}

function _cmpAscA($m, $n) {
if ($m->a == $n->a) {
return 0;
}

return ($m->a < $n->a) ? -1 : 1;
}

function _cmpDescA($m, $n) {
if ($m->a == $n->a) {
return 0;
}

return ($m->a > $n->a) ? -1 : 1;
}
}

//let's make an array
$arr = array();
$arr[] = new Item(1, 2);
$arr[] = new Item(4, 3);
$arr[] = new Item(2, 3);

//now we can sort it!
$sorted = usort($arr, array('Item', '_cmpAscA'));


Note that the second parameter to the usort() function is an array. This is because I'm calling a static method. The first item('Item') denotes the class, and the second item('_cmpAscA') denotes the method name. If I had simply made a comparison function that wasn't in a class, the second parameter to usort() would have been a string, not an array.

I typically make two comparison functions per sortable field in my objects; I want to be able to sort ascending and descending, without having to muck with strrev(). My way is a bit faster ... but may force you to use a bit more code. It seems to me that that's a matter of personal preference. And I have this preference because I feel that it allows you to more accurately describe what you want. If you want to sort in descending order, you say "sort this in descending order," rather than "sort this and then reverse it."

Anyhow, usort() is a powerful tool to add to the toolbox. I know I'll be using it in the future for all my object-sorting needs. Hope you find it as useful.

10 comments:

Philippe Bernou said...

That's great !

be careful, usort returns a boolean, not the sorted array.

Sean Schulte said...

Yes, you're right.

In my example, $sorted is a boolean that tells whether the sort was successful. $arr is the array, and it is sorted after the call to usort().

Thanks for helping to clarify that.

Philippe Bernou said...

ah yes, I didn't get that...

hope it helps others

Thaiphoon said...

Hi

What if you would like to sort for both parameter 'a' and 'b' at the same time?

Primarily sort 'a' ascending, but if there are several identical 'a' then sort these for ascending 'b'.

I have an array of news-objects with several parameters. One of which is a 'date' (YYMMDD) and another is a 'time' (HH:MM). I would like to sort these objects for ascending 'date' and also for ascending 'time'.

jeffreyjarin said...

Hi just want to ask if this will sort the alphanumeric files :) i just find my implementation weird as its not sorting my alphanumeric files.

Thanks
jefoy1101@gmail.com

Anonymous said...

Thank you.

Anonymous said...

It's just what I was looking for...
Thank you so much, regards from Spain.

Anonymous said...

mighty useful example, thanks from Sydney. Jeff.

Anonymous said...

Very useful comments.Thaiphoon, in order to do that kind of sorting, then code the sorting function like this:

function cmpDateTime($obj1, $obj2){

$date1 = $obj1->date;
$date2 = $obj2->date;

if($date1>$date2)return 1;
if($date2>$date2)return -1;

if($date==$date2){ //dates are the same, compare time
if($obj1->time > $obj2->time)return 1;
if($obj1->time < $obj2->time)return -1;
if($obj1->time == $obj2->time)return 0;
}

Thaiphoon said...

Muchos gracias "Anonymous".
I can see that logic.