I want to group and sum some rows of data based on two column values.
My input is:
$array = [
['FA',12.9],
['FA',12.9],
['FB',12.2],
['FC',12.3],
['FA',12.9],
['FB',12.9],
['FA',12.4],
];
I want to print the grouped row values as a string then an x
and the total number of occurrences like this:
FA 12.9x3
FB 12.2x3
I have written code to count the occurrence of values in each group, but I'm unable to figure out how to print it in this format:
$new = [];
foreach ($array as $key=> $value) {
if (!array_key_exists($value[0],$new)) {
$new[$value[0]]=[strval($value[1])=>1];
}
else {
if (!array_key_exists(strval($value[1]),$new[$value[0]])) {
$new[$value[0]][strval($value[1])]=1;
// $no =1;
}
else {
$count= $new[$value[0]];
$count=$count[strval($value[1])];
$count =1;
$new[$value[0]][strval($value[1])]=$count;
}
}
}
Can this be optimized and printed in the correct format?
Desired output:
FA 12.9x3
FB 12.2x1
FC 12.3x1
FB 12.9x1
FA 12.4x1
CodePudding user response:
Using array_reduce
in a special yet useful way, we can group items by their name. Then again group by value counting each. The idea is passing along an array that accumulates the values as keys.
$g = array($a, $b, $c, $d, $e, $f, $h);
$result = array_reduce($g, function ($carry, $item) {
$key = $item[0];
$value = $item[1];
if (!isset($carry[$key])) {
$carry[$key] = [];
}
if (!isset($carry[$key][(string) $value])) {
$carry[$key][(string) $value] = 0;
}
$carry[$key][(string) $value] ;
return $carry;
}, []);
print_r($result);
Output:
Array
(
[FA] => Array
(
[12.9] => 3
[12.4] => 1
)
[FB] => Array
(
[12.2] => 1
[12.9] => 1
)
[FC] => Array
(
[12.3] => 1
)
)
CodePudding user response:
I’m not 100% sure what your desired output is but if you collect your counts in a specific way a simple for
loop might suffice.
In the below code I’m counting things in two different ways, one short version that relies on @
to suppress a warning, and one slightly longer version that explicitly looks for an initial key and sets it to 0
before incrementing.
// Count in two different but functionally identical ways
$counts_a = [];
$counts_b = [];
foreach($items as $item) {
// Suppress the warning for missing key
@$counts_a[$item[0] . ' ' . $item[1]] ;
// Actually look for the key and set to zero if not found
$key = $item[0] . ' ' . $item[1];
if(!array_key_exists($key, $counts_b)){
$counts_b[$key] = 0;
}
$counts_b[$key] ;
}
Both of those versions produce the same output which can then be looped over:
foreach($counts_a as $key => $count) {
echo $key . 'x' . $count, PHP_EOL;
}
Demo: https://3v4l.org/hfQCK
CodePudding user response:
You must not try to use the float values as keys while grouping because that will cause modern versions of PHP to complain about data loss while converting floats to integers. Furthermore, it doesn't make sense to group the data into a multi-level structure because that will only complicate your task of printing the data.
Do not use the "STFU operator" @
to silence PHP's notices/warnings. It is important/professional to declare variables before trying to access them.
Instead, use a "composite key" (a string key that is composed of multiple identifying data points). While grouping, keep tally of the counts per group in a new, third element.
When finished grouping, use vsprintf()
to elegantly present the data in a loop. %s
is a placeholder for a string value, %0.1f
for a float truncated to 1 decimal place, and %d
for an integer.
Code: (Demo)
$array = [
['FA',12.9],
['FA',12.9],
['FB',12.2],
['FC',12.3],
['FA',12.9],
['FB',12.9],
['FA',12.4],
];
$grouped = [];
foreach ($array as $row) {
$compositeKey = implode($row);
if (!isset($grouped[$compositeKey])) {
$grouped[$compositeKey] = array_merge($row, [1]);
} else {
$grouped[$compositeKey][2];
}
}
foreach ($grouped as $row) {
vprintf("%s %0.1fx%d\n", $row);
}
Output:
FA 12.9x3
FB 12.2x1
FC 12.3x1
FB 12.9x1
FA 12.4x1