Sorting 2D-arrays in PHP - anectodes and reflections

One of the many problems with PHP that detractors are eager to point out is the fact that the language has thousands of global functions. Without the awesome CHM version of the PHP manual - finding your way in this jungle would be a mess. Simply finding a function like array_multisort is not trivial. And even if you find it, understanding it completely is not trivial either!

One of the first things you might run into as a PHP developer is having to sort a two dimensional array (table) by an arbitrary field. Consider the following array for instance:

$customers = array(
	array("name" => "David", "age" => 32),
	array("name" => "Bernard", "age" => 45)
);

What if you want to sort it so that Bernard ends up on top instead? When I first ran into this problem I checked the manual of course for something that would help me. I didn’t really understand the explanation on array_multisort(). I got the impression it only sorted one dimensional arrays with the help of other arrays. Check it out in the manual yourself, the full functionality is not exactly something that Homer Simpson would ever figure out even if you gave him a thousand years. Figuring out that it can be used to sort the above array, by name for instance, can be a stretch, at least it was for me.

So I ended up writing this function instead:

static function sort2d_asc(&$arr, $key){
    //we loop through the array and fetch a value that we store in tmp
    for($i = 0; $i < count($arr); $i++){
        $tmp = $arr[$i];
        $pos = false;
        //we check if the key we want to sort by is a string
        $str = is_numeric($tmp[$key]);
        if(!$str){
            //we loop the array again to compare against the temp value we have
      for($j = $i; $j < count($arr); $j++){
        if(StringManip::is_date($tmp[$key])){
          if(StringManip::compareDates($arr[$j][$key], $tmp[$key], $type = 'asc')){
            $tmp = $arr[$j];
              $pos = $j;
          }
          //we string compare, if the string is "smaller" it will be assigned to the temp value  
        }else if(strcasecmp($arr[$j][$key], $tmp[$key]) < 0){
            $tmp = $arr[$j];
            $pos = $j;
        }
      } 
    }else{
        for($j = $i; $j < count($arr); $j++){
        if($arr[$j][$key] < $tmp[$key]){
            $tmp = $arr[$j];
            $pos = $j;
        }
      }
    }
    if($pos !== false){
        $arr[$pos] = $arr[$i];
        $arr[$i] = $tmp;
    }
  }
}

You pass in the array you want to sort as &$arr and the key you want to sort by as $key and the array get sorted in an ascending fashion. This works OK as long as you have a two dimensional array like the one above. But it wont work if your array looks like this:

$customers = array(
	"cus1" => array("name" => "David", "age" => 32),
	"cus2" => array("name" => "Bernard", "age" => 45)
);

This array does not have numerical keys, the results using the above mega-function will not be reliable. Usually this is not a problem since 2D arrays retrieved from the database will be numbered numerically. My home grown function actually worked fine for me for over a year until I wanted to sort an array like the one just above. It wouldn’t work of course so I reviewed the manual and the array_multisort function again. This time I realized that it can be used to sort arrays just like I wanted. This is the result:

static function multi2dSortAsc(&$arr, $key){
  $sort_col = array();
  foreach ($arr as $sub) $sort_col[] = $sub[$key];
  array_multisort($sort_col, $arr);
}

The crux is that we have to create the 1D array we want to sort by on the fly, once we have this array it can be used to sort the parent 2D array by passing it as a second argument. This function will work on both of the customer arrays. Somewhat slicker than the home grown monster huh? Although the new function might need some more attention before it can handle as many different types as the mega version it will still be a hell of a lot shorter and faster.

These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • blogmarks
  • Reddit
  • Simpy
  • StumbleUpon
  • Technorati
  • DZone
  • Ma.gnolia

Related Posts

Tags: , , ,

9 Responses to “Sorting 2D-arrays in PHP - anectodes and reflections”

  1. Andy Says:

    Um… Just use any of the sort functions that accept a callback…

    usort() or uasort() would solve the above problem *much* faster, using far less code,
    and will not care what the array keys are. The following will sort by age, and if the same,
    sort by name. Nicer, no?

    function _callback($a, $b) {
    if ($a[’age’] $b[’age’]) return -1;
    return strcmp($a[’name’], $b[’name’]);
    }

    uasort($customers, ‘_callback’);

  2. Andy Says:

    Sorry, the formatting is screwed up in the above post…

    It should really be:

    function _callback($a, $b) {
    if ($a[’age’] < $b[’age’]) return 1;
    if ($a[’age’] > $b[’age’]) return -1;
    return strcmp($a[’name’], $b[’name’]);
    }

    uasort($customers, ‘_callback’);

  3. Henrik Says:

    Even better, thanks Andy! I remember this way of doing things from Java, nice that it will work in PHP too. I didn’t even know these u* functions existed!

  4. Henrik Says:

    I ended up discarding Andy’s way of doing things. I couldn’t figure out how to make it work with arbitrary keys and/or in a static function setting.

    I ended up using array_multisort anyway:

    static function sort2DAsc(&$arr, $key){
    	$sort_col = array(); 
    	foreach ($arr as $sub) $sort_col[] = $sub[$key];
    	if(is_numeric($sort_col[0]))
    		array_multisort($sort_col, SORT_NUMERIC, $arr);
    	else if(is_string($sort_col[0])){
    		$sort_col = array_map('strtolower', $sort_col);
    		array_multisort($sort_col, SORT_STRING, $arr);
    	}
    }
    
    static function sort2DDesc(&$arr, $key){
    	$sort_col = array();
    	foreach ($arr as $sub) $sort_col[] = $sub[$key];
    	if(is_numeric($sort_col[0]))
    		array_multisort($sort_col, SORT_NUMERIC, SORT_DESC, $arr);
    	else if(is_string($sort_col[0])){
    		$sort_col = array_map('strtolower', $sort_col);
    		array_multisort($sort_col, SORT_STRING, SORT_DESC, $arr);
    	}
    }

    Multisort is case sensitive and that is not the behavior I want so in addition to creating the whole sort column on the fly we also need to make it all lower case. It’s not so beautiful anymore but it works and it is still shorter than the original monster(s).

  5. Henrik Says:

    However the uasort function can come in handy to implement custom object sorting a la Java implements sortable.

  6. PHPDeveloper.org Says:

    ProDevTips: Sorting 2D-arrays in PHP - anectodes and reflections…

    On the ProDevTips website today, there’s a new article looking at ……

  7. developercast.com » ProDevTips: Sorting 2D-arrays in PHP - anectodes and reflections Says:

    […] the ProDevTips website today, there’s a new article looking at the sorting of arrays, specifically of the two- and three-dimensional sort. One of the […]

  8. René Leonhardt Says:

    Where can the implementaiton of class StringManip be found?

  9. Henrik Says:

    Leonard: That lib is one of my earlier creations, working but ugly “legacy” code that shows up now and then in my projects because I’m too lazy to prettify it. I don’t want people to get the wrong ideas by showing it. But basically what it does in the above example is checking if the string is a date with a simple regular expression and you really have to know how it works because it might capture stuff that is not a date (quick, sloppy implementation that worked for the original project).

    The date compare function just converts the dates to timestamps (with strtodate) and compares them as numbers instead.

Leave a Reply