Search code examples
phparraysmultidimensional-arrayusort

Sort elements in each row based on multiple rules comparing associative keys


I've a multidimensional array and want to sort its sub-arrays by their keys in a custom order where I have some fixed positions and some relative positions.

The array looks like this:

$array = [
    [
        "ARRIVAL" => '2022-03-08',
        "INFO" => "TEST",
        "V1_PROZ_WET" => 7,
        "V1_ABS_WET" => 200,
        "V1_PROZ" => 4,
        "V1_ABS" => 150,
        "V2_PROZ_WET" => 10,
        "V2_ABS_WET" => 250,
        "V2_PROZ" => 5,
        "V2_ABS" => 180,
        "BEZ" => "TEST",
        "WET_TOTAL" => 500,
        "DRY_TOTAL" => 300
    ],
    [
        "ARRIVAL" => '2022-03-07',
        "INFO" => "TEST",
        "V1_PROZ_WET" => 7,
        "V1_ABS_WET" => 200,
        "V1_PROZ" => 4,
        "V1_ABS" => 150,
        "V2_PROZ_WET" => 10,
        "V2_ABS_WET" => 250,
        "V2_PROZ" => 5,
        "V2_ABS" => 180,
        "BEZ" => "TEST",
        "WET_TOTAL" => 500,
        "DRY_TOTAL" => 300
    ],
    [
        "ARRIVAL" => '2022-03-06',
        "INFO" => "TEST",
        "V1_PROZ_WET" => 7,
        "V1_ABS_WET" => 200,
        "V1_PROZ" => 4,
        "V1_ABS" => 150,
        "V2_PROZ_WET" => 10,
        "V2_ABS_WET" => 250,
        "V2_PROZ" => 5,
        "V2_ABS" => 180,
        "BEZ" => "TEST",
        "WET_TOTAL" => 500,
        "DRY_TOTAL" => 300
    ],
];

I want to sort it in following order:

$sortOrder = [
    "INFO",         //fixed pos 1
    "BEZ",          //fixed pos 2
    "WET_TOTAL",    //fixed pos 3
    "DRY_TOTAL",    //fixed pos 4
    "_PROZ",        //all dry percentage values
    "_ABS",         //all dry abs values
    "_PROZ_WET",    //all wet percentage values
    "_ABS_WET"      //all wet abs values
];

An expected output of a sub-array would be:

[
    "INFO" => "TEST",
    "BEZ" => "TEST",
    "WET_TOTAL" => 500,
    "DRY_TOTAL" => 300,
    "V1_PROZ" => 4,
    "V2_PROZ" => 5,
    "V1_ABS" => 150,
    "V2_ABS" => 180,
    "V1_PROZ_WET" => 7,
    "V2_PROZ_WET" => 10,
    "V1_ABS_WET" => 200,
    "V2_ABS_WET" => 250,
    "ARRIVAL" => '2022-03-06'
]

I've seen PHP's usort is capable of doing custom sort functions, but I'm stuck because I can't get it running.

This SO-Answer gave me some good inputs of how I can do custom sorting for multidimensional arrays but I need to sort them by the keys and don't have an "id" key where I can always look at.

Here is a fiddle as playground.


Solution

  • Iterate and modify-by-reference as you call uksort() on each row.

    Fallback to lesser prioritized criteria using the Elvis operator.

    If your PHP version doesn't yet offer arrow functions, you'll need to adjust the script to use long-handed function syntax.

    If your PHP version doesn't yet offer str_contains(), you'll need to swap these calls out for long-winded strpos() !== false checks.

    This kind of boolean-based sorting algorithm can trick developers that are new to sorting. When the spaceship operator compares two boolean values, it treats false as 0 and true as 1. In other words, if you use ASC sorting, then the false records come before the true records. If you want the opposite sorting direction, then you swap the position of $a and $b in the expression. $b <=> $a means DESC sorting.

    Code: (Demo)

    foreach ($array as &$row) {
        uksort($row, fn($a, $b) => 
            ($b === 'INFO') <=> ($a === 'INFO')
            ?: ($b === 'BEZ') <=> ($a === 'BEZ')
            ?: ($b === 'WET_TOTAL') <=> ($a === 'WET_TOTAL')
            ?: ($b === 'DRY_TOTAL') <=> ($a === 'DRY_TOTAL')
            ?: ($a === 'ARRIVAL') <=> ($b === 'ARRIVAL')
            ?: str_contains($a, '_WET') <=> str_contains($b, '_WET')
            ?: str_contains($b, '_PROZ') <=> str_contains($a, '_PROZ')
            ?: str_contains($b, '_ABS') <=> str_contains($a, '_ABS')
            ?: $a <=> $b
        );
    }    
    var_export($array);
    

    Output:

    array (
      0 => 
      array (
        'INFO' => 'TEST',
        'BEZ' => 'TEST',
        'WET_TOTAL' => 500,
        'DRY_TOTAL' => 300,
        'V1_PROZ' => 4,
        'V2_PROZ' => 5,
        'V1_ABS' => 150,
        'V2_ABS' => 180,
        'V1_PROZ_WET' => 7,
        'V2_PROZ_WET' => 10,
        'V1_ABS_WET' => 200,
        'V2_ABS_WET' => 250,
        'ARRIVAL' => '2022-03-08',
      ),
      1 => 
      array (
        'INFO' => 'TEST',
        'BEZ' => 'TEST',
        'WET_TOTAL' => 500,
        'DRY_TOTAL' => 300,
        'V1_PROZ' => 4,
        'V2_PROZ' => 5,
        'V1_ABS' => 150,
        'V2_ABS' => 180,
        'V1_PROZ_WET' => 7,
        'V2_PROZ_WET' => 10,
        'V1_ABS_WET' => 200,
        'V2_ABS_WET' => 250,
        'ARRIVAL' => '2022-03-07',
      ),
      2 => 
      array (
        'INFO' => 'TEST',
        'BEZ' => 'TEST',
        'WET_TOTAL' => 500,
        'DRY_TOTAL' => 300,
        'V1_PROZ' => 4,
        'V2_PROZ' => 5,
        'V1_ABS' => 150,
        'V2_ABS' => 180,
        'V1_PROZ_WET' => 7,
        'V2_PROZ_WET' => 10,
        'V1_ABS_WET' => 200,
        'V2_ABS_WET' => 250,
        'ARRIVAL' => '2022-03-06',
      ),
    )