Home > other >  PHP multidimensional array sort with column priority
PHP multidimensional array sort with column priority

Time:12-09

I have the following array:

$data = 
[
    [
        'totals' => ['grand_total' => 729.81, 'lowest_order_date' => '', 'lowest_created_at' => '2022-11-23 15:23:06'],
    ],
    [
        'totals' => ['grand_total' => 746.03, 'lowest_order_date' => '', 'lowest_created_at' => '2022-11-22 19:46:13'],
    ],
    [
        'totals' => ['grand_total' => 729.81, 'lowest_order_date' => '2022-11-22 15:30:22', 'lowest_created_at' => '2022-11-21 14:25:07'],
    ],
];  

And I'm using the following code to sort it:

usort($data, function ($a, $b)
{
    return
        ($a['totals']['grand_total'] <=> $b['totals']['grand_total'])   
        ($a['totals']['lowest_order_date'] <=> $b['totals']['lowest_order_date'])  
        ($a['totals']['lowest_created_at'] <=> $b['totals']['lowest_created_at']);
});

I was expecting this function to sort the content by the following priority:

  1. Grand total <-- The main priority ASC
  2. Lowest order date <-- If grand total equal, check this
  3. Lowest created at <-- If grand total equal, check this

However, the output puts the 746.03 as first result:

$data = 
[
    [
        'totals' => ['grand_total' => 746.03, 'lowest_order_date' => '', 'lowest_created_at' => '2022-11-22 19:46:13'],
    ],
    [
        'totals' => ['grand_total' => 729.81, 'lowest_order_date' => '', 'lowest_created_at' => '2022-11-23 15:23:06'],
    ],
    [
        'totals' => ['grand_total' => 729.81, 'lowest_order_date' => '2022-11-08 15:30:22', 'lowest_created_at' => '2022-11-21 14:25:07'],
    ],
];

Expected output:

$data = 
[
    [
        'totals' => ['grand_total' => 729.81, 'lowest_order_date' => '', 'lowest_created_at' => '2022-11-23 15:23:06'],
    ],
    [
        'totals' => ['grand_total' => 729.81, 'lowest_order_date' => '2022-11-22 15:30:22', 'lowest_created_at' => '2022-11-21 14:25:07'],
    ],
    [
        'totals' => ['grand_total' => 746.03, 'lowest_order_date' => '', 'lowest_created_at' => '2022-11-22 19:46:13'],
    ],  
];

By looking at the problem, it seems to me logical that I first need to sort only by grand_total and then perform sort on lowest_order_date & lowest_created_at if the grand_total is equal. Is there an easy way to do it by using the sort functions?

CodePudding user response:

Yes, you can sort by multiple keys by defining the sort function to first sort by the grand total, and then by the other keys if the grand total is equal.

Here's one way to do it:

usort($data, function ($a, $b) {
    // Sort by grand total first
    if ($a['totals']['grand_total'] != $b['totals']['grand_total']) {
        return $a['totals']['grand_total'] <=> $b['totals']['grand_total'];
    }
    
    // If the grand total is equal, sort by lowest_order_date
    if ($a['totals']['lowest_order_date'] != $b['totals']['lowest_order_date']) {
        return $a['totals']['lowest_order_date'] <=> $b['totals']['lowest_order_date'];
    }
    
    // If the grand total and the lowest_order_date are equal, sort by lowest_created_at
    return $a['totals']['lowest_created_at'] <=> $b['totals']['lowest_created_at'];
});

This will sort the array in the order you specified.

CodePudding user response:

Adding together the results from <=> is probably not what you want, as it could return a -1 and a 1 and those negate to 0. Same goes for the return from strcmp() and friends.

usort($data, function ($a, $b)
{
    // First priority
    if (($result = ($a['totals']['grand_total'] <=> $b['totals']['grand_total'])) !== 0) {
        return $result;
    }
    // Second priority
    if (($result = ($a['totals']['lowest_order_date'] <=> $b['totals']['lowest_order_date'])) !== 0) {
        return $result;
    }
    // Lowest priority
    return $a['totals']['lowest_created_at'] <=> $b['totals']['lowest_created_at']);
});

DRYing up the code:

usort($data, function ($a, $b)
{
    // Keys in 'totals', highest-to-lowest sort priority
    $sort_priority = [
        'grand_total',
        'lowest_order_date',
        'lowest_created_at',
    ];
    foreach ($sort_priority as $sort_key) {
        if (($result = ($a['totals'][$sort_key] <=> $b['totals'][$sort_key])) !== 0) {
            return $result;
        }
    }
    // Compared all fields, they're a tie for sort order
    return 0;
});

CodePudding user response:

Easy-to-remember notation for PHP multidimensional array sorting with column priority:

usort($data,function($a,$b){
  return $a['totals']['grand_total']       <=> $b['totals']['grand_total'] 
      ?: $a['totals']['lowest_order_date'] <=> $b['totals']['lowest_order_date']
      ?: $a['totals']['lowest_created_at'] <=> $b['totals']['lowest_created_at']
  ;});

Another advantage is that if a column is to be sorted in descending order, only $a and $b in the row have to be swapped. Functions can also be easily embedded.

  •  Tags:  
  • php
  • Related