phparrayssortingmultidimensional-array

Sort each subset in a 3-level array by four column values


I have the following multi-dimensional array that I want to sort.

I want to sort the innermost arrays by total_points, then tiebraker1, 2 and 3.

Example:

[
    1 => [
        1 => [
            'userid' => 17,
            'total_points' => 16,
            'tiebraker1' => 1,
            'tiebraker2' => 2,
            'tiebraker3' => 1
        ],
        2 => [
            'userid' => 29,
            'total_points' => 16,
            'tiebraker1' => 1,
            'tiebraker2' => 2,
            'tiebraker3' => 9
        ]
    ],
    2 => [
        1 => [
            'userid' => 26,
            'total_points' => 26,
            'tiebraker1' => 2,
            'tiebraker2' => 2,
            'tiebraker3' => 4
        ],
        2 => [
            'userid' => 17,
            'total_points' => 26,
            'tiebraker1' => 3,
            'tiebraker2' => 2,
            'tiebraker3' => 4
       ]
    ]
]

Desired result:

[
    1 => [
        1 => [
            'userid' => 29,
            'total_points' => 16,
            'tiebraker1' => 1,
            'tiebraker2' => 2,
            'tiebraker3' => 9
        ],
        2 => [
            'userid' => 17,
            'total_points' => 16,
            'tiebraker1' => 1,
            'tiebraker2' => 2,
            'tiebraker3' => 1
        ]
    ],
    2 => [
        1 => [
            'userid' => 17,
            'total_points' => 26,
            'tiebraker1' => 3,
            'tiebraker2' => 2,
            'tiebraker3' => 4
        ],
        2 => [
            'userid' => 26,
            'total_points' => 26,
            'tiebraker1' => 2,
            'tiebraker2' => 2,
            'tiebraker3' => 4
       ]
    ]
]

I tried using array_multisort but I can't configure it correctly.


Solution

  • To use array_multisort you would need a different structure for your data. Specifically you would need to group by "score type" (or expressed mathematically, transpose the array). E.g. like this using your first example:

    array(5) {
        // $userid
        [0] => array(2) {
            [0] => 17
            [1] => 29
        }
    
        // $total_points
        [1] => array(2) {
            [0] => 16
            [1] => 16
        }
    
        // $tiebreaker1
        [2] => array(4) {
            [0] => 1
            [1] => 1
        }
    
        // $tiebreaker2
        [3] => array(2) {
            [0] => 2
            [1] => 2
        }
    
        // $tiebreaker3
        [4] => array(2) {
            [0] => 1
            [1] => 9
        }
    }
    

    Then you could use array_multisort() as follows:

    array_multisort($ar[1], SORT_DESC, SORT_NUMERIC,
                    $ar[2], SORT_DESC, SORT_NUMERIC,
                    $ar[3], SORT_DESC, SORT_NUMERIC,
                    $ar[4], SORT_DESC, SORT_NUMERIC,
                    $ar[0], SORT_ASC, SORT_NUMERIC);
    

    If you cannot change the structure of the array, you could use usort() instead and define the comparision criteria manually.

    function cmp($a, $b)
    {
        if ($a['total_points'] != $b['total_points']) {
            return ($a['total_points'] > $b['total_points']) ? -1 : 1;
        } elseif ($a['tiebreaker1'] != $b['tiebreaker1']) {
            return ($a['tiebreaker1'] > $b['tiebreaker1']) ? -1 : 1;   
        } elseif ($a['tiebreaker2'] != $b['tiebreaker2']) {
            return ($a['tiebraker2'] > $b['tiebreaker2']) ? -1 : 1;   
        } elseif ($a['tiebreaker3'] != $b['tiebreaker3']) {
            return ($a['tiebreaker3'] > $b['tiebreaker3']) ? -1 : 1;   
        } else {
            return 0;
        }
    }
    
    usort($array, "cmp");
    

    Disclaimer: I do not claim that my implementation of cmp is the most elegant one. But it should do the trick. :)