Kristian Vitozev
Kristian Vitozev

Reputation: 5971

Group items (containing differing structures) of a multidimensional array and sum column values per group

I have the following input:

$data = [
    [
        'date' => '2014-01-01',
        'sales' => 1,
        'price' => ['usd' => 1, 'eur' => 100],
    ],
    [
        'date' => '2014-01-05',
        'sales' => 1,
        'price' => ['usd' => 1, 'eur' => 100, 'gbp' => 500]
    ],
    [
        'date' => '2016-03-27',
        'sales' => 5,
        'age' => 50
    ],
    [
        'date' => '2016-03-28',
        'sales' => 10
    ]
];

I need to sum the sales column data and the child elements of the optionally occurring price column per group to produce the following result:

$final = array(
    'March 2016' => array(
        'sales' => 15
    ),
    'January 2014' => array(
        'sales' => 2,
        'price' => array(
            'usd' => 2,
            'eur' => 200,
            'gbp' => 500
        )
    )
);

What I've done so far:

$monthlyData = array();
        
foreach ($dailyData as $day)
{
    $key = date('M y', strtotime($day['date']));

    if (!isset($monthlyData[$key]))
    {
        $monthlyData[$key] = $day;
        continue;
    }

    foreach ($day as $metric => $value)
    {
        if(!empty($value)) 
        {
            $monthlyData[$key][$metric] += $value;
        }
    }
}

I know that we can use good ol' foreach (with recursive calls) in order to get the right result, but I'm looking for some more elegant solution.

Upvotes: 2

Views: 110

Answers (2)

Devon Bessemer
Devon Bessemer

Reputation: 35367

You really just need one more condition and loop for this specific example.

foreach ($day as $metric => $value)
{
    /* Added Condition */
    if(is_array($value))
    {
        foreach($value as $nestedMetric => $nestedValue) {
            $monthlyData[$key][$metric][$nestedMetric] += $nestedValue;
        }
    }
    elseif(!empty($value)) 
    {
        $monthlyData[$key][$metric] += $value;
    }
}

Output:

Warning: A non-numeric value encountered

Warning: A non-numeric value encountered

Warning: Undefined array key "gbp"

Warning: A non-numeric value encountered

Warning: A non-numeric value encountered
array (
  'Jan 14' => 
  array (
    'date' => 4028,
    'sales' => 2,
    'price' => 
    array (
      'usd' => 2,
      'eur' => 200,
      'gbp' => 500,
    ),
  ),
  'Mar 16' => 
  array (
    'date' => 4032,
    'sales' => 15,
    'age' => 50,
  ),
)

However, I'd probably do it differently by handling the calculation based on the metric, not just treating every metric dynamically.

Upvotes: 1

mickmackusa
mickmackusa

Reputation: 48070

While iterating over the variably-populated items, reformat the date and remove the day-portion. Then push the sales data into its designated group in the result array; if re-encountering the group, add the current value to the stored value.

If the price element has children, iterate over them and push them (or add them) to their designated element in the same fashion as described earlier.

Code: (Demo)

$result = [];
foreach ($data as $set) {
    $group = (new DateTime($set['date']))->format('M Y');
    $result[$group]['sales'] = ($result[$group]['sales'] ?? 0) + $set['sales'];
    foreach ($set['price'] ?? [] as $currency => $amount) {
        $result[$group]['price'][$currency] = ($result[$group]['price'][$currency] ?? 0) + $amount;
    }
}
var_export($result);

Upvotes: 0

Related Questions