chloe
chloe

Reputation: 115

Group 2d array by one column and sum multiple columns within each group

I need to group the row data in my 2d array by one column and sum the other columns individually to produce a new 2d array with potentially fewer rows.

In my case, I need to calculate the sums of [num] and [price] by each [group].

[
    ['group' => 'Apple', 'num' => 5, 'price' => 10],
    ['group' => 'Apple', 'num' => 2, 'price' => 8],
    ['group' => 'Orange', 'num' => 4, 'price' => 6],
    ['group' => 'Orange', 'num' => 12, 'price' => 24],
]

And the result would be like:

[
    ['group' => 'Apple', 'num' => 7, 'price' => 18],
    ['group' => 'Orange', 'num' => 16, 'price' => 30],
]

Upvotes: -1

Views: 768

Answers (3)

moonwave99
moonwave99

Reputation: 22817

Using array_reduce():

$reduced = array_reduce($array, function(&$result, $item){
    $result[$item['group']]['num'] += $item['num'];
    $result[$item['group']]['price'] += $item['price'];
    return $result;
});

Output: Demo

Warning: {closure}(): Argument #1 ($result) must be passed by reference, value given
   
Warning: Undefined array key "Apple"
   
Warning: Undefined array key "num"
   
Warning: Undefined array key "price"
   
Warning: {closure}(): Argument #1 ($result) must be passed by reference, value given
   
Warning: {closure}(): Argument #1 ($result) must be passed by reference, value given
   
Warning: Undefined array key "Orange"
   
Warning: Undefined array key "num"
   
Warning: Undefined array key "price"
   
Warning: {closure}(): Argument #1 ($result) must be passed by reference, value given
array (
  'Apple' => 
  array (
    'num' => 7,
    'price' => 18,
  ),
  'Orange' => 
  array (
    'num' => 16,
    'price' => 30,
  ),
)

Upvotes: 0

mickmackusa
mickmackusa

Reputation: 47894

By pushing reference variables into the result array for each unique group, it will not be necessary to re-index the result array after looping.

Code: (Demo)

$result = [];
foreach ($array as $row) {
    if (!isset($ref[$row['group']])) {
        $ref[$row['group']] = $row;
        $result[] =& $ref[$row['group']];
        continue;
    }
    $ref[$row['group']]['num'] += $row['num'];
    $ref[$row['group']]['price'] += $row['price'];
}
var_export($result);

As an alternative implementation of @Peter's solution, individual values from each row can be pushed individually into the result array before calling array_values() (running another interal loop) to re-index the result.

Code: (Demo)

$result = [];
foreach ($array as ['group' => $g, 'num' => $n, 'price' => $p]) {
    $result[$g]['group'] = $g;
    $result[$g]['num'] = ($result[$g]['num'] ?? 0) + $n;
    $result[$g]['price'] = ($result[$g]['price'] ?? 0) + $p;
}
var_export(array_values($result));

Finally, any working implementation with foreach() loops on this page can be appropriately transcribed to a functional style using array_reduce(). This is a little more verbose, but eliminates adding adding temporary variables to the global scope.

Code: (Demo)

var_export(
    array_values(
        array_reduce(
            $array,
            function ($result, $row) {
                if (!isset($result[$row['group']])) {
                    $result[$row['group']] = $row;
                } else {
                    $result[$row['group']]['num'] += $row['num'];
                    $result[$row['group']]['price'] += $row['price'];
                }
                return $result;
            }
        )
    )
);

Upvotes: 0

Peter
Peter

Reputation: 16923

Simple loop and assocative arrays will do the job:

$result = Array();
foreach($array as $row) {
   if(!isset($result[ $row['group'] ])) {
       $result[ $row['group'] ] = $row;
       continue ;
   }
   $result[ $row['group'] ]['num'] += $row['num'];
   $result[ $row['group'] ]['price'] += $row['price'];
}
$result = array_values($result);

Upvotes: 2

Related Questions