fraktal12
fraktal12

Reputation: 101

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
                        )    
                )
        )
)

Upvotes: 1

Views: 327

Answers (2)

mickmackusa
mickmackusa

Reputation: 47992

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.

Upvotes: 2

slevy1
slevy1

Reputation: 3820

As it turns out, there is a way to do what the OP seeks using a combination of usort() and array_walk(), each of which takes a callback, as follows:

<?php
// median code: 
//http://www.mdj.us/web-development/php-programming/calculating-the-median-average-values-of-an-array-with-php/

function calculate_median($arr) {
    sort($arr);
    $count = count($arr); //total numbers in array
    $middleval = floor(($count-1)/2); // find the middle value, or the lowest middle value
    if($count % 2) { // odd number, middle is the median
        $median = $arr[$middleval];
    } else { // even number, calculate avg of 2 medians
        $low = $arr[$middleval];
        $high = $arr[$middleval+1];
        $median = (($low+$high)/2);
    }
    return $median;
}


$a = [];
$a["2017-05-01"] = ["DC"];

$a["2017-05-01"]["DC"]["IT"] = [90,0];
$a["2017-05-01"]["DC"]["DE"] = [18,315,40,"",69];
$a["2017-05-01"]["DC"]["Other"] = [107,46,"","",27,22];


function sort_by_order ($a, $b)
{
     if ($a == "") $a = 0;
     if ($b == "") $b = 0;
     return $a - $b;
}

function test($item,$key){
    echo $key," ";
    if (is_array($item)) {
       echo array_keys($item)[1],"\n";
       $popped = array_pop($item);
       foreach ($popped as $key => $arr) {
          usort($arr, 'sort_by_order');
          echo "Median ($key): ",calculate_median( $arr ),"\n";
        }
     }
}

array_walk($a, 'test');

See demo here. Also, see this example based on the OP's sandbox.

Although the OP's code does not show the array keys as quoted, beware they should be in the actual code, otherwise PHP will do math with 2017-05-01 and you'll see a key of 2011. Interesting read here about usort.

The median code I extracted from here.

Interestingly, the conventional wisdom about sorting numbers to determine the median is not necessarily the only way to obtain that result. Apparently, it can also be done and perhaps more efficiently by finding a pivot number and dividing the series of numbers into three parts (see this response).

Upvotes: 2

Related Questions