85

I've an array like this

Array (
 [0] => Array( "destination" => "Sydney",
               "airlines" => "airline_1",
               "one_way_fare" => 100,
               "return_fare => 300
       ),
 [2] => Array( "destination" => "Sydney",
               "airlines" => "airline_2",
               "one_way_fare" => 150,
               "return_fare => 350
       ),
 [3] => Array( "destination" => "Sydney",
               "airlines" => "airline_3",
               "one_way_fare" => 180,
               "return_fare => 380
       )
)

How can i sort the value by return_fare asc , one_way_fare asc ?

I tried array_multisort() but i ended up getting mixed up data..

asort only works for one dimensional array, i need to sort by two values or more, how can i achieve this like in SQL, order by field1 asc,field2 asc ?

Echilon
  • 10,064
  • 33
  • 131
  • 217
flyclassic
  • 2,459
  • 6
  • 30
  • 44

6 Answers6

150

array_multisort() is the correct function, you must have messed up somehow:

// Obtain a list of columns
foreach ($data as $key => $row) {
    $return_fare[$key]  = $row['return_fare'];
    $one_way_fare[$key] = $row['one_way_fare'];
}

// Sort the data with volume descending, edition ascending
array_multisort($data, $return_fare, SORT_ASC, $one_way_fare, SORT_ASC);

If you take a look at the comments at PHP's manual page for array_multisort(), you can find a very helpful array_orderby() function which allows you to shorten the above to just this:

$sorted = array_orderby($data, 'return_fare', SORT_ASC, 'one_way_fare', SORT_ASC);

To avoid the looping use array_column() (as of PHP 5.5.0):

array_multisort(array_column($data, 'return_fare'),  SORT_ASC,
                array_column($data, 'one_way_fare'), SORT_ASC,
                $data);
qozle
  • 140
  • 10
Tatu Ulmanen
  • 123,288
  • 34
  • 187
  • 185
50

In addition to array_multisort(), which requires you to build column-arrays first, there is also usort() which doesn't require such a thing.

usort($data, function($a, $b) { 
    $rdiff = $a['return_fare'] - $b['return_fare'];
    if ($rdiff) return $rdiff; 
    return $a['one_way_fare'] - $b['one_way_fare']; 
}); // anonymous function requires PHP 5.3 - use "normal" function earlier
Khrys aka Louis
  • 68
  • 2
  • 10
etarion
  • 16,935
  • 4
  • 43
  • 66
  • 11
    Or more shortly, `return $a['return_fare'] - $b['return_fare'] ?: a['one_way_fare'] - $b['one_way_fare'];` Everything feels more natural than `array_multisort()` – mpapec Aug 02 '13 at 13:12
  • i stumbled upon this one, i think it should be said that `array_multisort` seems to be significantly faster than `usort` - you can find an example here http://sandbox.onlinephpfunctions.com/code/4aeeb7f0639347c1d426ec263a676fd667d43a2f – Atural Aug 29 '18 at 11:39
  • Could very well be the case now (back in 2011 there was no arrray_column and so you had to build the arrays yourself which slowed things down considerably). – etarion Aug 29 '18 at 13:41
  • A `foreach` loop that generates two arrays is very likely to outperform two separate `array_column()` calls ...if you are worried about micro-optimization. The benefit of two `array_column()` calls is code brevity and the avoidance of making two variable declarations. – mickmackusa Oct 23 '20 at 11:23
17

Another example using the spaceship operator.

usort($data, function($a, $b) { 
    return $a['return_fare'] <=> $b['return_fare'] ?: $a['one_way_fare'] <=> $b['one_way_fare'];
});
472084
  • 17,666
  • 10
  • 63
  • 81
user634545
  • 9,099
  • 5
  • 29
  • 40
  • This can be done with a single spaceship operator comparison. – mickmackusa Jun 03 '20 at 06:43
  • And yet you don't tell us how @mickmackusa? – Codemonkey Oct 23 '20 at 10:08
  • My answer on the duplicate page demonstrates how to do this. @codemonkey – mickmackusa Oct 23 '20 at 10:14
  • 1
    Here are some more if you like: https://stackoverflow.com/search?q=user%3A2943403+spaceship+operator&mixed=0 For the record, if you are making function calls in the comparisons, then I recommend using multiple spaceships, but if there are no function calls, then it is better to use one spaceship. My old answers might not toe this line. – mickmackusa Oct 23 '20 at 10:37
  • 1
    Ah ha. I'd considered concatenation but knew that wasn't safe - arrays hadn't occured to me, thanks. – Codemonkey Oct 23 '20 at 10:50
11

Or you can use uasort as follows

uasort($arr, function($a,$b){
    $c = $a['return_fare'] - $b['return_fare'];
    $c .= $a['one_way_fare'] - $b['one_way_fare'];
    return $c;
});

Fiddle

Narendrasingh Sisodia
  • 21,247
  • 6
  • 47
  • 54
  • 1
    This is not working when sorting with decimal values – Lijo Abraham Sep 28 '18 at 13:14
  • This has been an awesome tip: I was trying to do a double sort (group job IDs then order product IDs within the groups) and this did the trick perfectly (I was looking for a MySQL multi column sort solution, I had tried array_multisort and it was breaking other ordered sections in the groups - this is the only solution that has allowed me to get the desired order in a way I could read and understand, a very important thing when it comes to refactoring further down the line) – Ben Duffin Nov 01 '19 at 16:16
  • I would never use the concatenated string formed from two difference values. Even in the cases where it works, there are cleaner techniques available. – mickmackusa Oct 23 '20 at 11:18
8

I'll answer this in a way that can be generalized, no matter how many items you wish to sort on!

Sorting on return_fare then one_way_fare:

usort($data, function($a, $b) {
   if ($a['return_fare'] != $b['return_fare']) {
      return $a['return_fare'] <=> $b['return_fare'];
   }

   return $a['one_way_fare'] <=> $b['one_way_fare'];
});

Sorting on return_fare, then one_way_fare, then destination:

usort($data, function($a, $b) {
   if ($a['return_fare'] != $b['return_fare']) {
      return $a['return_fare'] <=> $b['return_fare'];
   }

   if ($a['one_way_fare'] != $b['one_way_fare']) {
      return $a['one_way_fare'] <=> $b['one_way_fare'];
   }

   return strnatcasecmp($a['destination'], $b['destination']);
});

Sorting on just return_fare:

usort($data, function($a, $b) {
   return $a['return_fare'] <=> $b['return_fare'];
});

Note: you don't have to use an anonymous function with usort!

function cmp($a, $b) {
   return $a['return_fare'] <=> $b['return_fare'];
}

usort($data, 'cmp');


// Use a function inside a class:
class MyClass {
   public static function compare($a, $b) {
      return $a['return_fare'] <=> $b['return_fare'];
   }
}

usort($data, ['MyClass', 'compare']);

You can also chain these using the Elvis Operator (?:):

usort($data, function($a, $b) {
      return $a['return_fare'] <=> $b['return_fare'] ?:
             $a['one_way_fare'] <=> $b['one_way_fare'] ?:
             strnatcasecmp($a['destination'], $b['destination']);
});

This last example used the Spaceship Operator (<=>) and the Elvis Operator (?:). Isn't programming great?

Westy92
  • 19,087
  • 4
  • 72
  • 54
-2

Ohh, i managed to solve my own question again....

function array_multi_sort($array, $on1,$on2, $order=SORT_ASC) 
{

    foreach($array as $key=>$value){
        $one_way_fares[$key] = $value[$on2];
        $return_fares[$key] = $value[$on1];
    }

    array_multisort($return_fares,$order,$one_way_fares,$order,$array);
}

The thing is i missed out the last parameter $array on array_multisort($return_fares,$order,$one_way_fares,$order,$array);

earlier!

kid_plama
  • 249
  • 1
  • 4
  • 11
flyclassic
  • 2,459
  • 6
  • 30
  • 44