up vote 4 down vote favorite

If I have an object as such:

class Person {
  var $age;
  function __construct($age) {
    $this->age = $age;
  }
}

and I have any array of Person's

$person1 = new Person(14);
$person2 = new Person(5);
$people = array($person1, $person2);

Is there an easy way to sort the $people array by the Person->age property?

flag
I'm trying to avoid usort() because it is too expensive of a call as my people array grows. Let's assume I have 15,000 entries in $people. – skcubrats Sep 22 '09 at 20:52
What inefficiency do you think usort has that you'll be avoiding by any other method? usort will sort in place and should be pretty efficient. – Paul Dixon Sep 22 '09 at 21:05
It will make a function every call which makes it inefficient in large data sets. – skcubrats Sep 22 '09 at 21:21
I've posted some benchmark details - usort isn't too bad, but you can indeed go faster with a non-recursive quicksort. – Paul Dixon Sep 22 '09 at 21:37
What are you doing that you need to sort 15,000 objects at once? – Gumbo Sep 22 '09 at 22:13
show 1 more comment

6 Answers

up vote 7 down vote

For that specific scenario, you can sort it using the usort() function, where you define your own function to compare the items in the array.

<?php

class Person {
  var $age;
  function __construct($age) {
    $this->age = $age;
  }
}

function personSort( $a, $b ) {
    return $a->age == $b->age ? 0 : ( $a->age > $b->age ) ? 1 : -1;
}

$person1 = new Person(14);
$person2 = new Person(5);
$person3 = new Person(32);
$person4 = new Person(150);
$person5 = new Person(39);
$people = array($person1, $person2, $person3, $person4, $person5);

print_r( $people );

usort( $people, 'personSort' );

print_r( $people );
link|flag
I'm trying to avoid usort() because it is too expensive of a call as my people array grows. Let's assume I have 15,000 entries in $people – skcubrats Sep 22 '09 at 20:55
4  
I think you could implement personSort() more simply like this: return $a->age - $b->age; – Don Kirkby Sep 22 '09 at 21:05
hehe, really? i was just so accustomed to doing it like that I didn't really mathematically think it out. – meder Sep 22 '09 at 21:32
up vote 7 down vote

I tried a usort, and sorted 15000 Person objects in around 1.8 seconds.

As you are concerned about the inefficiency of the calls to the comparison function, I compared it with a non-recursive Quicksort implementation. This actually ran in around one third of the time, approx 0.5 seconds.

Here's my code which benchmarks the two approaches

// Non-recurive Quicksort for an array of Person objects
// adapted from http://www.algorithmist.com/index.php/Quicksort_non-recursive.php
function quickSort( &$array )
{
 $cur = 1;
 $stack[1]['l'] = 0;
 $stack[1]['r'] = count($array)-1;

 do
 {
  $l = $stack[$cur]['l'];
  $r = $stack[$cur]['r'];
  $cur--;

  do
  {
   $i = $l;
   $j = $r;
   $tmp = $array[(int)( ($l+$r)/2 )];

   // partion the array in two parts.
   // left from $tmp are with smaller values,
   // right from $tmp are with bigger ones
   do
   {
    while( $array[$i]->age < $tmp->age )
     $i++;

    while( $tmp->age < $array[$j]->age )
     $j--;

    // swap elements from the two sides
    if( $i <= $j)
    {
     $w = $array[$i];
     $array[$i] = $array[$j];
     $array[$j] = $w;

     $i++;
     $j--;
    }

   }while( $i <= $j );

 if( $i < $r )
   {
    $cur++;
    $stack[$cur]['l'] = $i;
    $stack[$cur]['r'] = $r;
   }
   $r = $j;

  }while( $l < $r );

 }while( $cur != 0 );


}


// usort() comparison function for Person objects
function personSort( $a, $b ) {
    return $a->age == $b->age ? 0 : ( $a->age > $b->age ) ? 1 : -1;
}


// simple person object    
class Person {
  var $age;
  function __construct($age) {
    $this->age = $age;
  }
}

//---------test internal usort() on 15000 Person objects------

srand(1);
$people=array();
for ($x=0; $x<15000; $x++)
{
     $people[]=new Person(rand(1,100));
}


$start=microtime(true);
usort( $people, 'personSort' );
$total=microtime(true)-$start;

echo "usort took $total\n";


//---------test custom quicksort on 15000 Person objects------

srand(1);
$people=array();
for ($x=0; $x<15000; $x++)
{
     $people[]=new Person(rand(1,100));
}


$start=microtime(true);
quickSort( $people );
$total=microtime(true)-$start;

echo "quickSort took $total\n";

An interesting suggestion was to add a __toString method to the class and use sort(), so I tried that out too. Trouble is, you must pass SORT_STRING as the second parameter to sort get it to actually call the magic method, which has the side effect of doing a string rather than numeric sort. To counter this, you need to pad the numbers with zeroes to make it sort properly. Net result was that this was slower than both usort and the custom quickSort

sort 10000 items took      1.76266698837
usort 10000 items took     1.08757710457
quickSort 10000 items took 0.320873022079

Here's the code for the sort() using __toString():

$size=10000;

class Person {
  var $age;
  function __construct($age) {
    $this->age = $age;
    $this->sortable=sprintf("%03d", $age);
  }


  public function __toString()
  {
     return $this->sortable;
  }
}

srand(1);
$people=array();
for ($x=0; $x<$size; $x++)
{
     $people[]=new Person(rand(1,100));
}


$start=microtime(true);
sort( $people, SORT_STRING);
$total=microtime(true)-$start;

echo "sort($size) took $total\n"
link|flag
1  
The most complete and correct answer. – rogeriopvl Sep 22 '09 at 21:57
Did you check that algorithm for correctness? You need to change $array[$i] < $tmp by $array[$i]->age < $tmp->age, $tmp < $array[$j] by $tmp->age < $array[$j]->age and $i->age <= $j->age by $i <= $j. – Gumbo Sep 22 '09 at 21:57
Oh, and if you want to compare both algorithms, you should run them on the same data and not just on data of the same size but complete different characteristics. Generate your people array once and sort a copy of that same data with both algorithms. – Gumbo Sep 22 '09 at 22:01
Well spotted, corrected. – Paul Dixon Sep 22 '09 at 23:23
1  
kudos, nice answer – meder Sep 23 '09 at 1:13
show 3 more comments
up vote 2 down vote

I do not advice my solution in your example because it would be ugly (And I have not benchmarked it), but it works.... And depending of the need, it may help. :)

class Person
{
  public $age;

  function __construct($age)
  {
    $this->age = $age;
  }

  public function __toString()
  {
    return $this->age;
  }
}

$person1 = new Person(14);
$person2 = new Person(5);

$persons = array($person1, $person2);
asort($persons);
link|flag
+1 for the idea, I benchmarked it in my answer – Paul Dixon Sep 23 '09 at 8:37
As you pointed out: it is slow. :))) – Toto Sep 23 '09 at 10:17
up vote 1 down vote

usort() or uasort() /* to maintain index association if you were using an associative array */

link|flag
up vote 1 down vote

Here’s a stable Radix Sort implementation for values 0...256:

function radixsort(&$a)
{
    $n = count($a);
    $partition = array();
    for ($slot = 0; $slot < 256; ++$slot) {
        $partition[] = array();
    }
    for ($i = 0; $i < $n; ++$i) {
        $partition[$a[$i]->age & 0xFF][] = &$a[$i];
    } 
    $i = 0;
    for ($slot = 0; $slot < 256; ++$slot) {
        for ($j = 0, $n = count($partition[$slot]); $j < $n; ++$j) {
            $a[$i++] = &$partition[$slot][$j];
        }
    }
}

This costs only O(n) since Radix Sort is a non-comparing sorting algorithm.

link|flag
up vote 0 down vote

Yes. If you implement spl ArrayObject in your person object, all the normal php array functions will work properly with it.

link|flag

Your Answer

get an OpenID
or
never shown

Not the answer you're looking for? Browse other questions tagged or ask your own question.