phparrayssortingmultidimensional-arraymedian

How to find the median of deepest subarrays of multidimensional array?


I have a four-level multidimensional array. I need to sort in ascending order (ASC) the numeric "leaves" in order to calculate the median of the values.

I tried array_walk_recursive(), array_multisort(), usort(), etc. but was unable to find a working solution.

Here's a schematic of the array:

(
    [2017-05-01] => Array
        (
            [DC] => Array
                (
                    [IT] => Array
                        (
                            [0] => 90
                            [1] => 0
                        )    
                    [DE] => Array
                        (
                            [0] => 18
                            [1] => 315
                            [2] => 40
                            [3] => 
                            [4] => 69
                        )    
                    [Other] => Array
                        (
                            [0] => 107
                            [1] => 46
                            [2] => 
                            [3] => 
                            [4] => 27
                            [5] => 22
                        )    
                )
        )
)

Solution

  • This will output the deepest subarrays' median values using the input array's structure.

    I'm including hard-casting of median values (one or both in a subset) as integers in the event that the value(s) are empty strings. I'll also assume that you will want 0 as the output if a subset is empty.

    Create a new array in a functional style: Demo

    var_export(
        array_map(
            fn($dateSet) => array_map(
                fn($dcSet) => array_map(
                    function ($set) {
                        if (!$set) {
                            return 0;
                        }
                        sort($set);
                        $count = count($set);
                        $mid = intdiv($count, 2);
                        if ($count & 1) {
                            return $set[$mid];
                        }
                        return ((int)$set[$mid - 1] + (int)$set[$mid]) / 2;
                    },
                    $dcSet
                ),
                $dateSet
            ),
            $array
        )
    );
    

    Or modify the input array by reference in a classic loop. Demo

    foreach ($array as &$dateSet) {
        foreach ($dateSet as &$dcSet) {
            foreach ($dcSet as &$set) {
                if (!$set) {
                    $set = 0;
                    continue;
                }
                sort($set);
                $count = count($set);
                $mid = intdiv($count, 2);
                if ($count & 1) {
                    $set = $set[$mid];
                    continue;
                }
                $set = ((int)$set[$mid - 1] + (int)$set[$mid]) / 2;
            }
        }
    }
    var_export($array);
    

    Or modify the input array by reference with array_walk(). Demo

    array_walk(
        $array,
        fn(&$dateSet) => array_walk(
            $dateSet,
            fn(&$dcSet) => array_walk(
                $dcSet,
                function (&$set) {
                    if (!$set) {
                        $set = 0;
                        return;
                    }
                    sort($set);
                    $count = count($set);
                    $mid = intdiv($count, 2);
                    if ($count & 1) {
                        $set = $set[$mid];
                        return;
                    }
                    $set = ((int)$set[$mid - 1] + (int)$set[$mid]) / 2;
                }
            )
        )
    );
    var_export($array);
    

    *for the record, $count & 1 is a bitwise comparison that determines if the value is odd without performing arithmetic (and is the most efficient way of performing this check within php).

    *also, if you wanted to simply overwrite the values of the input array, you could modify by reference by writing & before $lv1, $lv2, and $lv3 in the foreach declarations then save the median value to $lv3. Demo The benefit in doing so removes key declarations and making your code more brief.