0

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.

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
MarkyMark
  • 7
  • 4

2 Answers2

2
$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,
);
dbrumann
  • 16,803
  • 2
  • 42
  • 58
  • Thanks dbrumann, I've given this a go but all that I get returned is 9 x NULL ` foreach($results as $key => $value) { $data = array($value['id'] => 3); array_reduce( $data, function($carry, $item) { foreach ($item as $id => $score) { $carry[$id] += $score; } return $carry; }, [] ); var_dump($carry); }` – MarkyMark Jan 20 '18 at 13:35
  • Sorry I didn't realise you couldn't format in the comments :/ – MarkyMark Jan 20 '18 at 13:36
  • I updated the example. You have to fetch the final result from the function, instead of reusing `$carry`. Also you don't need a foreach. Just pass in the original array as first argument to `array_reduce()` (replace `$data` with `$results`) – dbrumann Jan 20 '18 at 13:49
  • I don't think it's passing my $data array in to the array_reduce because I can't dump anything out at any point through the each line of code to see what it's doing, it's always NULL or empty. Should I not be able to see at least some data at one of those points? – MarkyMark Jan 20 '18 at 14:06
  • Maybe it helps when I write it as foreach instead of using array_reduce. I will update the answer again. – dbrumann Jan 20 '18 at 14:11
  • If this still does not work please show how you assemble the original array or make a `var_dump($data)` and show this, so I can check if maybe the expected array does not look like expected. – dbrumann Jan 20 '18 at 14:15
  • 1
    I've changed my $data = array($value['id'] => 3); to $data[] = $value['id']; and that's sorted out my issue. Thanks for your patience dbrumann and for taking the time to help me with this! – MarkyMark Jan 20 '18 at 15:47
  • Glad, I could help – dbrumann Jan 20 '18 at 18:08
  • This method generates Notices and is not a professional-grade answer. https://3v4l.org/DQpkA please see my answer for a clean technique. – mickmackusa Jan 20 '18 at 21:35
  • Fair enough. I have updated the answer with a simple way to prevent the notices by making sure the key exists. – dbrumann Jan 21 '18 at 00:02
0

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,
  ),
)
mickmackusa
  • 43,625
  • 12
  • 83
  • 136