Sorting 2D-arrays in PHP - anectodes and reflections


Update Aug 4, 2008: The below 2D sorting stuff has been superseded by the sorting section in the fluent article. The below example should be seen as something merely anecdotal and not be taken as a good example of how to sort 2D arrays by column. Alternatively, if you’re fine with a really simple approach see the end of the below article.

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.

Related Posts

Tags: , , ,