201

I have CSV data loaded into a multidimensional array. In this way each "row" is a record and each "column" contains the same type of data. I am using the function below to load my CSV file.

function f_parse_csv($file, $longest, $delimiter)
{
  $mdarray = array();
  $file    = fopen($file, "r");
  while ($line = fgetcsv($file, $longest, $delimiter))
  {
    array_push($mdarray, $line);
  }
  fclose($file);
  return $mdarray;
}

I need to be able to specify a column to sort so that it rearranges the rows. One of the columns contains date information in the format of Y-m-d H:i:s and I would like to be able to sort with the most recent date being the first row.

hakre
  • 178,314
  • 47
  • 389
  • 754
Melikoth
  • 2,386
  • 2
  • 17
  • 15
  • 7
    (2 yrs later...) If you're sorting dates stored as strings, you may first need to use strtotime [1] http://docs.php.net/manual/en/function.strtotime.php – Dan Burton May 19 '10 at 18:33
  • @deceze, https://stackoverflow.com/q/1597736/1709587 seems like a better dupe target to me. It's a more exact duplicate and the answers there consequently get to the point faster than yours at https://stackoverflow.com/a/17364128/1709587 while collectively having the same level of detail. What say you to switching the target? (Disclosure: am possibly biased as the author of one of the answers at the dupe target I'm proposing.) – Mark Amery Jun 29 '19 at 16:34
  • **See also:** https://stackoverflow.com/questions/1597736/how-to-sort-an-array-of-associative-arrays-by-value-of-a-given-key-in-php – dreftymac Jul 29 '19 at 21:30

11 Answers11

345

Introducing: a very generalized solution for PHP 5.3+

I 'd like to add my own solution here, since it offers features that other answers do not.

Specifically, advantages of this solution include:

  1. It's reusable: you specify the sort column as a variable instead of hardcoding it.
  2. It's flexible: you can specify multiple sort columns (as many as you want) -- additional columns are used as tiebreakers between items that initially compare equal.
  3. It's reversible: you can specify that the sort should be reversed -- individually for each column.
  4. It's extensible: if the data set contains columns that cannot be compared in a "dumb" manner (e.g. date strings) you can also specify how to convert these items to a value that can be directly compared (e.g. a DateTime instance).
  5. It's associative if you want: this code takes care of sorting items, but you select the actual sort function (usort or uasort).
  6. Finally, it does not use array_multisort: while array_multisort is convenient, it depends on creating a projection of all your input data before sorting. This consumes time and memory and may be simply prohibitive if your data set is large.

The code

function make_comparer() {
    // Normalize criteria up front so that the comparer finds everything tidy
    $criteria = func_get_args();
    foreach ($criteria as $index => $criterion) {
        $criteria[$index] = is_array($criterion)
            ? array_pad($criterion, 3, null)
            : array($criterion, SORT_ASC, null);
    }

    return function($first, $second) use (&$criteria) {
        foreach ($criteria as $criterion) {
            // How will we compare this round?
            list($column, $sortOrder, $projection) = $criterion;
            $sortOrder = $sortOrder === SORT_DESC ? -1 : 1;

            // If a projection was defined project the values now
            if ($projection) {
                $lhs = call_user_func($projection, $first[$column]);
                $rhs = call_user_func($projection, $second[$column]);
            }
            else {
                $lhs = $first[$column];
                $rhs = $second[$column];
            }

            // Do the actual comparison; do not return if equal
            if ($lhs < $rhs) {
                return -1 * $sortOrder;
            }
            else if ($lhs > $rhs) {
                return 1 * $sortOrder;
            }
        }

        return 0; // tiebreakers exhausted, so $first == $second
    };
}

How to use

Throughout this section I will provide links that sort this sample data set:

$data = array(
    array('zz', 'name' => 'Jack', 'number' => 22, 'birthday' => '12/03/1980'),
    array('xx', 'name' => 'Adam', 'number' => 16, 'birthday' => '01/12/1979'),
    array('aa', 'name' => 'Paul', 'number' => 16, 'birthday' => '03/11/1987'),
    array('cc', 'name' => 'Helen', 'number' => 44, 'birthday' => '24/06/1967'),
);

The basics

The function make_comparer accepts a variable number of arguments that define the desired sort and returns a function that you are supposed to use as the argument to usort or uasort.

The simplest use case is to pass in the key that you 'd like to use to compare data items. For example, to sort $data by the name item you would do

usort($data, make_comparer('name'));

See it in action.

The key can also be a number if the items are numerically indexed arrays. For the example in the question, this would be

usort($data, make_comparer(0)); // 0 = first numerically indexed column

See it in action.

Multiple sort columns

You can specify multiple sort columns by passing additional parameters to make_comparer. For example, to sort by "number" and then by the zero-indexed column:

usort($data, make_comparer('number', 0));

See it in action.

Advanced features

More advanced features are available if you specify a sort column as an array instead of a simple string. This array should be numerically indexed, and must contain these items:

0 => the column name to sort on (mandatory)
1 => either SORT_ASC or SORT_DESC (optional)
2 => a projection function (optional)

Let's see how we can use these features.

Reverse sort

To sort by name descending:

usort($data, make_comparer(['name', SORT_DESC]));

See it in action.

To sort by number descending and then by name descending:

usort($data, make_comparer(['number', SORT_DESC], ['name', SORT_DESC]));

See it in action.

Custom projections

In some scenarios you may need to sort by a column whose values do not lend well to sorting. The "birthday" column in the sample data set fits this description: it does not make sense to compare birthdays as strings (because e.g. "01/01/1980" comes before "10/10/1970"). In this case we want to specify how to project the actual data to a form that can be compared directly with the desired semantics.

Projections can be specified as any type of callable: as strings, arrays, or anonymous functions. A projection is assumed to accept one argument and return its projected form.

It should be noted that while projections are similar to the custom comparison functions used with usort and family, they are simpler (you only need to convert one value to another) and take advantage of all the functionality already baked into make_comparer.

Let's sort the example data set without a projection and see what happens:

usort($data, make_comparer('birthday'));

See it in action.

That was not the desired outcome. But we can use date_create as a projection:

usort($data, make_comparer(['birthday', SORT_ASC, 'date_create']));

See it in action.

This is the correct order that we wanted.

There are many more things that projections can achieve. For example, a quick way to get a case-insensitive sort is to use strtolower as a projection.

That said, I should also mention that it's better to not use projections if your data set is large: in that case it would be much faster to project all your data manually up front and then sort without using a projection, although doing so will trade increased memory usage for faster sort speed.

Finally, here is an example that uses all the features: it first sorts by number descending, then by birthday ascending:

usort($data, make_comparer(
    ['number', SORT_DESC],
    ['birthday', SORT_ASC, 'date_create']
));

See it in action.

Jon
  • 396,160
  • 71
  • 697
  • 768
  • @Jon When I check your last action (with the callback `date_create`), the birthday are in the wrong order. Could you confirm ? I get `01/12/1979`, `03/11/1987`, `12/03/1980` and `24/06/1967`. If I use `strtotime` instead, I get a good result. I assume it's DateTime that's broken. – David Bélanger Aug 08 '13 at 19:48
  • @DavidBélanger: Which URL exactly? All examples work correctly both on ideone.com and on my local machine. – Jon Aug 08 '13 at 22:21
  • Hey, @Jon, I dont know how I can contact you but through comment. I see you created tag "multidimensional array", but we need also "multidimensional data" for similar term in data visualization and related disciplines. I can't create tags, may I ask you to create one, if you find it makes sense? – VividD Jan 09 '14 at 01:36
  • @VividD: I didn't create the tag myself, and tag creation cannot happen by itself. New tags are implicitly created when someone with the required privileges uses them to tag a question; given that I have never seen and probably will never see a question that I feel should be tagged like that, I can't really help. – Jon Jan 09 '14 at 11:17
  • Could you give an example how to implement case-insensitive sort? I tried converting projection to lowercase but it won't work... – Andrew Mar 18 '14 at 10:16
  • @Andrew: [Example](http://ideone.com/5IH1zt). But remember that this is inefficient so don't do it if your data set is not small. – Jon Mar 18 '14 at 11:05
  • @Jon thanks, i just tried it and it's 0.006 sec slower than without it, sorting 200 rows. I think i'll accept this kind of inefficiency :) – Andrew Mar 18 '14 at 11:19
  • I have another array of values that I would like to sort a column against and the value in the column can have multiple matches. The sort array is of file types so I always want file types of a list of files to show in a specific order. Is it possible in this construct? – Ecropolis May 27 '14 at 11:45
  • @Ecropolis: Please post a good example of your input(s) and desired output somewhere (e.g. ideone.com or pastebin.com) so that we can eliminate misunderstandings. – Jon May 27 '14 at 11:48
  • @Jon - Thank you here are example arrays with comments. http://ideone.com/utbrNt – Ecropolis May 27 '14 at 21:52
  • @Ecropolis: OK, so you can make an array where keys are filetypes and values are "weights" (smaller weight = item goes on top) with `$weights = array_flip($sort_array)`. `$weights` is now `['mp4' => 0, 'mpeg' => 1, ...]`. You can then utilize this in the comparer by sorting on filetype and using this as a projection: `function($t) use ($weights) { return $weights[$t]; })`. – Jon May 28 '14 at 09:08
  • Excellent! Thank you @Jon. I updated http://ideone.com/utbrNt for the complete example of how to use this function to sort an array using another for reference sort values. – Ecropolis May 29 '14 at 16:01
  • @Ecropolis: You have extra quotes around `function($t) ...` that need to be removed. Cheers! – Jon May 29 '14 at 16:42
  • 1
    @Jon - Thanks for all of your help. I tried to implement this on my server and I get this:PHP Parse error: syntax error, unexpected '[', expecting ')' -- I tried different variation, I'm just a bit lost on how this really works. I'm on v5.3.28 - ideone.com runs php 5.4 - is that the issue? – Ecropolis Jun 05 '14 at 20:34
  • 4
    @Ecropolis: PHP 5.3 does not support the short `[...]` array syntax, you will have to use `array(...)` instead. I didn't do that in the examples for extra brevity, but `make_comparer` itself is compatible with 5.3. – Jon Jun 05 '14 at 20:49
  • 1
    @Jon Great answer and I agree that this is like a web site rather than answer. Thanks. Only have one question. How can I make it work to run for objects? – YahyaE Oct 03 '14 at 21:21
  • 2
    @YahyaE: Sorting arrays of objects? Replace `$first[$column]` with `$first->$column`, and same for `$second`. Four replacements in total. – Jon Oct 10 '14 at 00:02
  • mine is a 4D array ? how can i pass the index if that possible ? – Dashrath Nov 16 '14 at 07:40
  • @Dashrath: I'm not sure what you structure is. Either pass in `$array['index']` if you want to get rid of the top level, or else use a projection `function($sub) { return $sub['index']; }` if you want to get rid of a mid-level dimension named `index`. – Jon Nov 18 '14 at 13:22
  • 1
    @CalculatingMachine I read the question, but you don't show what you tried to do. It seems just `usort($data['content'], get_comparer('price'))` would be enough, but I can't be sure. – Jon Apr 22 '16 at 09:00
  • @Jon thanks for your comment. However, I am still reading your answer and trying to understand it. If needed i will use the same in my project. Just had one doubt. I am not sure it will work with my data/array or not. In your example data is in different format. – Rocx Apr 22 '16 at 11:26
215

You can use array_multisort()

Try something like this:

foreach ($mdarray as $key => $row) {
    // replace 0 with the field's index/key
    $dates[$key]  = $row[0];
}

array_multisort($dates, SORT_DESC, $mdarray);

For PHP >= 5.5.0 just extract the column to sort by. No need for the loop:

array_multisort(array_column($mdarray, 0), SORT_DESC, $mdarray);
AbraCadaver
  • 73,820
  • 7
  • 55
  • 81
Shinhan
  • 2,750
  • 1
  • 16
  • 21
  • 8
    So in this example, $mdarray might be a two-dimensional array, like an array of database records. In this example, 0 is the index of the 'date' column in each record (or row). So you construct the $dates array (basically the same array, but with only that column), and tell the array_multisort function to sort $mdarray based on that particular column's values. – Dan Burton May 19 '10 at 18:26
  • 8
    For clarity, you might add to the beginning of this example `$dates = array();` – Dan Burton May 19 '10 at 18:27
  • 1
    Should array_multisort work with associative arrays (changing `$row[0]` to `$row['whatever']`? No go here. After I changed my array to numeric the function worked as expected. – a coder Feb 14 '13 at 22:24
  • 1
    Isn't the inclusion of the `$key` unnecessary when using *array_multisort()*? It seems simpler and more intentional to write `foreach ($mdarray as $row) { $sortByDate[] = $row['date']; }` then `array_multisort( $sortByDate, SORT_DESC, $mdarray );` (your semantic mileage my vary). – Mark Fox Mar 13 '13 at 21:02
  • 1
    If `array_multi_sort()` is the answer, the question was not understood. While technically it will work, there usually is a better solution with a user generated compare function and the usage of a `usort()` function. It's easier to maintain. With multisort, you usually create code that prepares the data for sorting. If the data structure changes, that code might be thrown away. With usort(), you change the compare function - in the same way you changed the data structure. – Sven Jul 17 '13 at 07:54
  • There's a more extensive example of this answer now on the PHP array_multisort page: http://php.net/manual/en/function.array-multisort.php#example-4928. – icc97 Sep 27 '13 at 08:17
  • Warning: array_multisort(): Array sizes are inconsistent – RaviPatidar Oct 24 '16 at 07:36
34

With usort. Here's a generic solution, that you can use for different columns:

class TableSorter {
  protected $column;
  function __construct($column) {
    $this->column = $column;
  }
  function sort($table) {
    usort($table, array($this, 'compare'));
    return $table;
  }
  function compare($a, $b) {
    if ($a[$this->column] == $b[$this->column]) {
      return 0;
    }
    return ($a[$this->column] < $b[$this->column]) ? -1 : 1;
  }
}

To sort by first column:

$sorter = new TableSorter(0); // sort by first column
$mdarray = $sorter->sort($mdarray);
troelskn
  • 107,146
  • 23
  • 127
  • 148
  • I get Parse error: parse error, unexpected T_STRING, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' on the second line of that class. – Melikoth Sep 18 '08 at 21:35
  • 3
    Replace "protrected" with "var" and "__construct" with "TableSorter", and it will work in PHP4. Notice however, that PHP4 is discontinued. – troelskn Sep 18 '08 at 22:23
  • I set PHP to v5, didn't know it was running v4 by default. Having looked at it for a while I think I understand how to modify it for different types of sorts as well – Melikoth Sep 18 '08 at 23:19
11

Multiple row sorting using a closure

Here's another approach using uasort() and an anonymous callback function (closure). I've used that function regularly. PHP 5.3 required – no more dependencies!

/**
 * Sorting array of associative arrays - multiple row sorting using a closure.
 * See also: http://the-art-of-web.com/php/sortarray/
 *
 * @param array $data input-array
 * @param string|array $fields array-keys
 * @license Public Domain
 * @return array
 */
function sortArray( $data, $field ) {
    $field = (array) $field;
    uasort( $data, function($a, $b) use($field) {
        $retval = 0;
        foreach( $field as $fieldname ) {
            if( $retval == 0 ) $retval = strnatcmp( $a[$fieldname], $b[$fieldname] );
        }
        return $retval;
    } );
    return $data;
}

/* example */
$data = array(
    array( "firstname" => "Mary", "lastname" => "Johnson", "age" => 25 ),
    array( "firstname" => "Amanda", "lastname" => "Miller", "age" => 18 ),
    array( "firstname" => "James", "lastname" => "Brown", "age" => 31 ),
    array( "firstname" => "Patricia", "lastname" => "Williams", "age" => 7 ),
    array( "firstname" => "Michael", "lastname" => "Davis", "age" => 43 ),
    array( "firstname" => "Sarah", "lastname" => "Miller", "age" => 24 ),
    array( "firstname" => "Patrick", "lastname" => "Miller", "age" => 27 )
);

$data = sortArray( $data, 'age' );
$data = sortArray( $data, array( 'lastname', 'firstname' ) );
feeela
  • 26,359
  • 6
  • 56
  • 68
7

I know it's 2 years since this question was asked and answered, but here's another function that sorts a two-dimensional array. It accepts a variable number of arguments, allowing you to pass in more than one key (ie column name) to sort by. PHP 5.3 required.

function sort_multi_array ($array, $key)
{
  $keys = array();
  for ($i=1;$i<func_num_args();$i++) {
    $keys[$i-1] = func_get_arg($i);
  }

  // create a custom search function to pass to usort
  $func = function ($a, $b) use ($keys) {
    for ($i=0;$i<count($keys);$i++) {
      if ($a[$keys[$i]] != $b[$keys[$i]]) {
        return ($a[$keys[$i]] < $b[$keys[$i]]) ? -1 : 1;
      }
    }
    return 0;
  };

  usort($array, $func);

  return $array;
}

Try it here: http://www.exorithm.com/algorithm/view/sort_multi_array

Mike C
  • 1,688
  • 13
  • 16
  • 2
    Could the first 3 lines of the function be replaced with `$keys = func_get_args(); array_unshift($keys);`? –  Mar 13 '12 at 18:44
6

You can sort an array using usort function.

 $array = array(
  array('price'=>'1000.50','product'=>'product 1'),
  array('price'=>'8800.50','product'=>'product 2'),
  array('price'=>'200.0','product'=>'product 3')
);

function cmp($a, $b) {
  return $a['price'] > $b['price'];
}
usort($array, "cmp");
print_r($array);

Output :

Array
(
    [0] => Array
        (
            [price] => 134.50
            [product] => product 1
        )

    [1] => Array
        (
            [price] => 2033.0
            [product] => product 3
        )

    [2] => Array
        (
            [price] => 8340.50
            [product] => product 2
        )

)

Example

Kamal
  • 675
  • 7
  • 11
2

The "Usort" function is your answer.
http://php.net/usort

Jan Hančič
  • 49,796
  • 15
  • 87
  • 97
  • 2
    I have downvoted you because of your failure to provide a solution with explanation and examples to the original question. Update your response and I will reverse my vote. – crafter Apr 05 '17 at 16:13
2

Here is a php4/php5 class that will sort one or more fields:

// a sorter class
//  php4 and php5 compatible
class Sorter {

  var $sort_fields;
  var $backwards = false;
  var $numeric = false;

  function sort() {
    $args = func_get_args();
    $array = $args[0];
    if (!$array) return array();
    $this->sort_fields = array_slice($args, 1);
    if (!$this->sort_fields) return $array();

    if ($this->numeric) {
      usort($array, array($this, 'numericCompare'));
    } else {
      usort($array, array($this, 'stringCompare'));
    }
    return $array;
  }

  function numericCompare($a, $b) {
    foreach($this->sort_fields as $sort_field) {
      if ($a[$sort_field] == $b[$sort_field]) {
        continue;
      }
      return ($a[$sort_field] < $b[$sort_field]) ? ($this->backwards ? 1 : -1) : ($this->backwards ? -1 : 1);
    }
    return 0;
  }

  function stringCompare($a, $b) {
    foreach($this->sort_fields as $sort_field) {
      $cmp_result = strcasecmp($a[$sort_field], $b[$sort_field]);
      if ($cmp_result == 0) continue;

      return ($this->backwards ? -$cmp_result : $cmp_result);
    }
    return 0;
  }
}

/////////////////////
// usage examples

// some starting data
$start_data = array(
  array('first_name' => 'John', 'last_name' => 'Smith', 'age' => 10),
  array('first_name' => 'Joe', 'last_name' => 'Smith', 'age' => 11),
  array('first_name' => 'Jake', 'last_name' => 'Xample', 'age' => 9),
);

// sort by last_name, then first_name
$sorter = new Sorter();
print_r($sorter->sort($start_data, 'last_name', 'first_name'));

// sort by first_name, then last_name
$sorter = new Sorter();
print_r($sorter->sort($start_data, 'first_name', 'last_name'));

// sort by last_name, then first_name (backwards)
$sorter = new Sorter();
$sorter->backwards = true;
print_r($sorter->sort($start_data, 'last_name', 'first_name'));

// sort numerically by age
$sorter = new Sorter();
$sorter->numeric = true;
print_r($sorter->sort($start_data, 'age'));
Devon
  • 5,670
  • 4
  • 36
  • 45
0

I tried several popular array_multisort() and usort() answers and none of them worked for me. The data just gets jumbled and the code is unreadable. Here's a quick a dirty solution. WARNING: Only use this if you're sure a rogue delimiter won't come back to haunt you later!

Let's say each row in your multi array looks like: name, stuff1, stuff2:

// Sort by name, pull the other stuff along for the ride
foreach ($names_stuff as $name_stuff) {
    // To sort by stuff1, that would be first in the contatenation
    $sorted_names[] = $name_stuff[0] .','. name_stuff[1] .','. $name_stuff[2];
}
sort($sorted_names, SORT_STRING);

Need your stuff back in alphabetical order?

foreach ($sorted_names as $sorted_name) {
    $name_stuff = explode(',',$sorted_name);
    // use your $name_stuff[0] 
    // use your $name_stuff[1] 
    // ... 
}

Yeah, it's dirty. But super easy, won't make your head explode.

PJ Brunet
  • 3,003
  • 34
  • 33
0

Before I could get the TableSorter class to run I had came up with a function based on what Shinhan had provided.

function sort2d_bycolumn($array, $column, $method, $has_header)
  {
  if ($has_header)  $header = array_shift($array);
  foreach ($array as $key => $row) {
    $narray[$key]  = $row[$column]; 
    }
  array_multisort($narray, $method, $array);
  if ($has_header) array_unshift($array, $header);
  return $array;
  }
  • $array is the MD Array you want to sort.
  • $column is the column you wish to sort by.
  • $method is how you want the sort performed, such as SORT_DESC
  • $has_header is set to true if the first row contains header values that you don't want sorted.
Community
  • 1
  • 1
Melikoth
  • 2,386
  • 2
  • 17
  • 15
-2

I prefer to use array_multisort. See the documentation here.

Krease
  • 14,501
  • 8
  • 47
  • 82
Tim Boland
  • 9,841
  • 8
  • 24
  • 24