MarkyMark
MarkyMark

Reputation: 7

How to merge subarrays using keys and sum the values?

I'm fairly new to PHP and I'm having some trouble with arrays and combining data. I have the following array which has been created from a foreach loop:

array(1) {
    [36868]=> int(3)
}
array(1) {
    [2112]=> int(3)
} 
array(1) { 
    [35901]=> int(3) 
} 
array(1) { 
    [6496]=> int(3) 
} 
array(1) { 
    [87]=> int(3) 
} 
array(1) { 
    [36868]=> int(3) 
} 
array(1) { 
    [68]=> int(3) 
} 
array(1) { 
    [9068]=> int(3) 
} 
array(1) { 
    [47]=> int(3) 
}

The key in each array is a user ID, so I need to preserve this, but I only want one instance of each key and where there are duplicate keys, sum the values. Like so:

array(1) {
    [36868]=> int(6)
}
array(1) {
    [2112]=> int(3)
} 
array(1) { 
    [35901]=> int(3) 
} 
array(1) { 
    [6496]=> int(3) 
} 
array(1) { 
    [87]=> int(3) 
} 
array(1) { 
    [68]=> int(3) 
} 
array(1) { 
    [9068]=> int(3) 
} 
array(1) { 
    [47]=> int(3) 
}

The I've tried looping through the array:

foreach ($array as $key => &$value) {
    if ($value[0] == $value[1]) {
        $value[1] += $value[1];
    }
}

But with no luck. I've also tried rendering the arrays differently i.e. [userid]=>1,[score]=>3 and I feel like I'm going round in circles a bit, so any help would be hugely appreciated.

Upvotes: 0

Views: 133

Answers (2)

mickmackusa
mickmackusa

Reputation: 47864

Here is a clean method that will not produce Notices. When merge-summing array data the efficient method is to generate temporary keys and use the very fast isset() function. I could have used current() and key() to access the lone subarray element, but the second foreach control structure is actually faster and more compact. (Ref: https://stackoverflow.com/a/21219594/2943403 )

Code: (Demo)

$array = [
    [36868 => 3],
    [2112 => 3],
    [35901 => 3],
    [6496 => 3],
    [87 => 3],
    [36868 => 3],
    [68 => 3],
    [9068 => 3],
    [47 => 3]
];

$result = [];
foreach ($array as $subarray) {
    foreach ($subarray as $k => $v) {
        if (!isset($result[$k])) {
            $result[$k] = $subarray;
        } else {
            $result[$k][$k] += $v;
        }
    }
}
var_export(array_values($result));

Output:

array (
  0 => 
  array (
    36868 => 6,
  ),
  1 => 
  array (
    2112 => 3,
  ),
  2 => 
  array (
    35901 => 3,
  ),
  3 => 
  array (
    6496 => 3,
  ),
  4 => 
  array (
    87 => 3,
  ),
  5 => 
  array (
    68 => 3,
  ),
  6 => 
  array (
    9068 => 3,
  ),
  7 => 
  array (
    47 => 3,
  ),
)

Upvotes: 0

dbrumann
dbrumann

Reputation: 17166

$data <-- this is your original array

$result = array_reduce(
    $data,
    function($carry, $item) {
        foreach ($item as $id => $score) {
            if (array_key_exists($id, $carry)) {
                $carry[$id] += $score;
            } else {
                $carry[$id] = $score;
            }
        }

        return $carry;
    },
    []
);

If you are sure that each item only contains 1 entry you could also simplify the callback to not use foreach:

$result = array_reduce(
    $data,
    function ($carry, $item) {
        $score = reset($item); 
        $id = key($item);

        if (array_key_exists($id, $carry)) {
            $carry[$id] += $score;
        } else {
            $carry[$id] = $score;
        }

        return $carry;
    },
    []
);

You could also keep using foreach instead:

/** foreach to create a $data array like described below and afterwards do this: **/
$result = [];
foreach($data as $row) {
    $score = reset($row);
    $id = key($row);

    if (array_key_exists($id, $result)) {
        $result[$id] += $score;
    } else {
        $result[$id] = $score;
    }
}

This will take an array $data like this:

array(
    array('1' => 3),
    array('1' => 3),
    array('2' => 3),
);

and creates the variable $result like this:

array(
    '1' => 6,
    '2' => 3,
);

Upvotes: 2

Related Questions