Tutorial :Sort Array of MultiDiminsional Arrays on More Than One “Column” (Key) With Specified Sort Options



Question:

I'm looking to be able to sort an array of multi-dimensional arrays on more than one column. To further complicate it I'd like to be able to set specific sort options per key/column. I have what is similar the result of a DB query, but doesn't actually come from one, therefore the need to sort it in PHP rather than SQL.

Array  (      [0] => Array          (              [first_name] => Homer              [last_name] => Simpson              [city] => Springfield              [state] => Unknown              [zip] => 66735          )        [1] => Array          (              [first_name] => Patty              [last_name] => Bouvier              [city] => Scottsdale              [state] => Arizona              [zip] => 85250          )        [2] => Array          (              [first_name] => Moe              [last_name] => Szyslak              [city] => Scottsdale              [state] => Arizona              [zip] => 85255          )        [3] => Array          (              [first_name] => Nick              [last_name] => Riviera              [city] => Scottsdale              [state] => Arizona              [zip] => 85255          )    )  

I would like to be able to sort it similar to what could be done with a DB query. Oh, and sometimes a column/key needs to be specified by number.

What I had in mind was something similar to this:

$sortOptions = array( array( 'city', SORT_ASC, SORT_STRING ),                        array( 'zip', SORT_DESC, SORT_NUMERIC),                        array( 2, SORT_ASC, SORT_STRING) // 2='last_name'                      );  $sorter = new MultiSort($data, $sortOptions );  $sortedData = $sorter->getSortedArray() ;  print_r( $jmsSorted);  

What I would like to end up with is this:

Array  (      [0] => Array          (              [first_name] => Nick              [last_name] => Riviera              [city] => Scottsdale              [state] => Arizona              [zip] => 85255          )        [1] => Array          (              [first_name] => Moe              [last_name] => Szyslak              [city] => Scottsdale              [state] => Arizona              [zip] => 85255          )        [2] => Array          (              [first_name] => Patty              [last_name] => Bouvier              [city] => Scottsdale              [state] => Arizona              [zip] => 85250          )        [3] => Array          (              [first_name] => Homer              [last_name] => Simpson              [city] => Springfield              [state] => Unknown              [zip] => 66735          )    )  

UPDATE: I think that ideally, a solution would result in dynamically creating

array_multisort( $city, SORT_ASC, SORT_STRING, $zip, SORT_DESC, SORT_NUMERIC, $last_name, SORT_ASC, SORT_STRING, $inputArray);  

The problem is that I don't want to have to "hard code" those key names in there. I tried creating a solution based upon Example #3 Sorting database results from the array_multisort() documentation that ended up using array_multisort() but I cannot seem to find a way to use my dynamically built argument list for array_multisort().

My attempt was to "chain" those arguments together into an array and then

call_user_func_array( 'array_multisort', $functionArgs);  

That results in an

Warning: Parameter 2 to array_multisort() expected to be a reference, value given in...  


Solution:1

In PHP 5.3 every parameter in the array has to be a reference when calling array_multisort() with call_user_func_array().

This function sorts a multidimensional array and shows a way to build an array of referenced params that works correctly.

function msort()  {    $params = func_get_args();    $array = array_pop($params);      if (!is_array($array))      return false;      $multisort_params = array();    foreach ($params as $i => $param)     {      if (is_string($param))       {        ${"param_$i"} = array();        foreach ($array as $index => $row)         {          ${"param_$i"}[$index] = $row[$param];        }      }      else         ${"param_$i"} = $params[$i];        $multisort_params[] = &${"param_$i"};    }    $multisort_params[] = &$array;       call_user_func_array("array_multisort", $multisort_params);      return $array;  }  

Example:

$data is the given array from the question

$sorted_data = msort('city', SORT_ASC, SORT_STRING, 'zip', SORT_DESC, SORT_NUMERIC, $data)  


Solution:2

This should work for the situation you describe.

usort($arrayToSort, "sortCustom");    function sortCustom($a, $b)  {      $cityComp = strcmp($a['city'],$b['city']);      if($cityComp == 0)      {          //Cities are equal.  Compare zips.          $zipComp = strcmp($a['zip'],$b['zip']);          if($zipComp == 0)          {              //Zips are equal.  Compare last names.              return strcmp($a['last_name'],$b['last_name']);          }          else          {              //Zips are not equal.  Return the difference.              return $zipComp;          }      }      else      {          //Cities are not equal.  Return the difference.          return $cityComp;      }  }  

You could condense it into one line like so:

function sortCustom($a, $b)  {      return ($cityComp = strcmp($a['city'],$b['city']) ? $cityComp : ($zipComp = strcmp($a['zip'],$b['zip']) ? $zipComp : strcmp($a['last_name'],$b['last_name'])));  }  

As far as having a customizable sort function, you're reinventing the wheel. Take a look at the array_multisort() function.


Solution:3

You might want to try using usort. All you have to do is make a functions that tell the sorter how to sort it. The docs have more info on how to do that.


Solution:4

Here is what I finally settled on for being able to sort multi-dimensional arrays. Both of the answers above are good but I was also looking for something flexible.

I definitely don’t think there is any one “right” answer, but this is what works for my needs and is flexible.

As you can see from my @link in the comment of _usortByMultipleKeys() it was adapted from a comment in the PHP manual that currently doesn't seem to exist, but I believe http://www.php.net/manual/en/function.usort.php#104398 is a new version of the original comment. I have not explored using that new suggestion.

/**   * Sort the resultSet.   *   * Usage: $sortOptions = array(   *          'section', // Defaults to SORT_ASC   *          'row' => SORT_DESC,   *          'retail_price' => SORT_ASC);   *        $results->sortResults($sortOptions);   *   * @param array $sortOptions    An array of sorting instructions   */  public function sortResults(array $sortOptions)  {      usort($this->_results, $this->_usortByMultipleKeys($sortOptions));  }      /**   * Used by sortResults()   *   * @link http://www.php.net/manual/en/function.usort.php#103722   */  protected function _usortByMultipleKeys($key, $direction=SORT_ASC)  {      $sortFlags = array(SORT_ASC, SORT_DESC);      if (!in_array($direction, $sortFlags)) {          throw new InvalidArgumentException('Sort flag only accepts SORT_ASC or SORT_DESC');      }      return function($a, $b) use ($key, $direction, $sortFlags) {          if (!is_array($key)) { //just one key and sort direction              if (!isset($a->$key) || !isset($b->$key)) {                  throw new Exception('Attempting to sort on non-existent keys');              }              if ($a->$key == $b->$key) {                  return 0;              }              return ($direction==SORT_ASC xor $a->$key < $b->$key) ? 1 : -1;          } else { //using multiple keys for sort and sub-sort              foreach ($key as $subKey => $subAsc) {                  //array can come as 'sort_key'=>SORT_ASC|SORT_DESC or just 'sort_key', so need to detect which                  if (!in_array($subAsc, $sortFlags)) {                      $subKey = $subAsc;                      $subAsc = $direction;                  }                  //just like above, except 'continue' in place of return 0                  if (!isset($a->$subKey) || !isset($b->$subKey)) {                      throw new Exception('Attempting to sort on non-existent keys');                  }                  if ($a->$subKey == $b->$subKey) {                      continue;                  }                  return ($subAsc==SORT_ASC xor $a->$subKey < $b->$subKey) ? 1 : -1;              }              return 0;          }      };  }  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »