Sort multidimensional array by multiple columns

Viewed 87372

I'm trying to sort a multidimensional array by multiple keys, and I have no idea where to start. I looked at uasort(), but wasn't quite sure how to write a function for what I need.

I need to sort by the state, then event_type, then date_start.

My array looks like this:

[
    ['ID' => 1, 'title' => 'Boring Meeting',  'date_start' => '2010-07-30', 'event_type' => 'meeting', 'state' => 'new-york'],
    ['ID' => 2, 'title' => 'Find My Stapler', 'date_start' => '2010-07-22', 'event_type' => 'meeting', 'state' => 'new-york'],
    ['ID' => 3, 'title' => 'Mario Party',     'date_start' => '2010-07-22', 'event_type' => 'party',   'state' => 'new-york'],
    ['ID' => 4, 'title' => 'Duct Tape Party', 'date_start' => '2010-07-28', 'event_type' => 'party',   'state' => 'california']
]

My desired result is:

[
    ['ID' => 4, 'title' => 'Duct Tape Party', 'date_start' => '2010-07-28', 'event_type' => 'party',   'state' => 'california']
    ['ID' => 2, 'title' => 'Find My Stapler', 'date_start' => '2010-07-22', 'event_type' => 'meeting', 'state' => 'new-york'],
    ['ID' => 1, 'title' => 'Boring Meeting',  'date_start' => '2010-07-30', 'event_type' => 'meeting', 'state' => 'new-york'],
    ['ID' => 3, 'title' => 'Mario Party',     'date_start' => '2010-07-22', 'event_type' => 'party',   'state' => 'new-york'],
]
8 Answers

PHP7 Makes sorting by multiple columns SUPER easy with the spaceship operator (<=>) aka the "Combined Comparison Operator" or "Three-way Comparison Operator".

Resource: https://wiki.php.net/rfc/combined-comparison-operator

Sorting by multiple columns is as simple as writing balanced/relational arrays on both sides of the operator. Easy done!

When the $a value is on the left of the spaceship operator and the $b value is on the right, ASCending sorting is used.

When the $b value is on the left of the spaceship operator and the $a value is on the right, DESCending sorting is used.

When the spaceship operator compares two numeric strings, it compares them as numbers -- so you get natural sorting automagically.

I have not used uasort() because I don't see any need to preserve the original indexes.

Code: (Demo) -- sorts by state ASC, then event_type ASC, then date_start ASC

$array = [
    ['ID' => 1, 'title' => 'Boring Meeting', 'date_start' => '2010-07-30', 'event_type' => 'meeting', 'state' => 'new-york'],
    ['ID' => 2, 'title' => 'Find My Stapler', 'date_start' => '2010-07-22', 'event_type' => 'meeting', 'state' => 'new-york'],
    ['ID' => 3, 'title' => 'Mario Party', 'date_start' => '2010-07-22', 'event_type' => 'party', 'state' => 'new-york'],
    ['ID' => 4, 'title' => 'Duct Tape Party', 'date_start' => '2010-07-28', 'event_type' => 'party', 'state' => 'california']
];

usort($array, function($a, $b) {
    return [$a['state'], $a['event_type'], $a['date_start']]
           <=>
           [$b['state'], $b['event_type'], $b['date_start']];
});

var_export($array);

Output

array (
  0 => 
  array (
    'ID' => 4,
    'title' => 'Duct Tape Party',
    'date_start' => '2010-07-28',
    'event_type' => 'party',
    'state' => 'california',
  ),
  1 => 
  array (
    'ID' => 2,
    'title' => 'Find My Stapler',
    'date_start' => '2010-07-22',
    'event_type' => 'meeting',
    'state' => 'new-york',
  ),
  2 => 
  array (
    'ID' => 1,
    'title' => 'Boring Meeting',
    'date_start' => '2010-07-30',
    'event_type' => 'meeting',
    'state' => 'new-york',
  ),
  3 => 
  array (
    'ID' => 3,
    'title' => 'Mario Party',
    'date_start' => '2010-07-22',
    'event_type' => 'party',
    'state' => 'new-york',
  ),
)

p.s. Arrow syntax with PHP7.4 and higher (Demo)...

usort($array, fn($a, $b) =>
    [$a['state'], $a['event_type'], $a['date_start']]
    <=>
    [$b['state'], $b['event_type'], $b['date_start']]
);

The equivalent technique with array_multisort() and a call of array_column() for every sorting criteria is: (Demo)

array_multisort(
    array_column($array, 'state'),
    array_column($array, 'event_type'),
    array_column($array, 'date_start'),
    $array
);

Maybe it helps someone:

// data to sort
$output = array(
        array('ID' => 1, 'title' => 'Boring Meeting', 'event_type' => 'meeting'),
        array('ID' => 2, 'title' => 'Find My Stapler', 'event_type' => 'meeting'),
        array('ID' => 3, 'title' => 'Mario Party', 'event_type' => 'party'),
        array('ID' => 4, 'title' => 'Duct Tape Party', 'event_type' => 'party')
);

// multi column, multi direction order by
$body['order_by'] = array(
        array("field"=> "event_type", "order"=> "desc"),
        array("field"=> "title", "order"=> "asc"),
        array("field"=> "ID", "order"=> "asc"),
);

$output = $this->multiColumnMultiDirectionSort($body, $output);


public function multiColumnMultiDirectionSort(array $body, array $output)
{
    // get order fields and its direction in proper format
    $orderFieldDirection = [];
    if (!empty($body['order_by']) && is_array($body['order_by'])) {
        foreach ($body['order_by'] as $order) {
            $orderDirection = $order['order'] == "desc" ? SORT_DESC : SORT_ASC; // we need format that array_multisort supports
            $orderFieldDirection[$order['field']] = $orderDirection;
        }
    }

    if (!empty($orderFieldDirection)) {
        // get the list of sort columns and their data in the format that is required by array_multisort
        $amParams = [];
        $sort = [];
        foreach ($orderFieldDirection as $field => $order) {
            foreach ($output as $k => $v) {
                $sort[$field][$k] = $v[$field];
            }

            $amParams[] = $sort[$field];
            $amParams[] = $order;
            $amParams[] = SORT_REGULAR; // this is not needed, but we can keep as it might come handy in the future
        }

        $amParams[] = &$output; // very important to pass as reference
        call_user_func_array("array_multisort", $amParams);
    }

    return $output;
}
Related