0

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
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
  • Please never, ever present input data as an image -- this format is not helpful (discriminatory, actually) to Stack Overflow users/readers/contributors who use assistive technologies like screen readers. Additionally, web crawlers do not do a good job or parsing images of data either. When providing array data for a PHP question, always use `var_export()` then copy-paste that text into your question -- this will allow contributors to instantly use your actual sample data when they test their solutions and post an answer. – mickmackusa Aug 15 '22 at 03:11

3 Answers3

0

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
        )

)
IT goldman
  • 14,885
  • 2
  • 14
  • 28
  • Declaring the parent element with `if (!isset($carry[$key])) { $carry[$key] = []; }` is not necessary at all. You could have avoided all of that repeated explicit string-typing by declaring `$value = (string) $item[1];` – mickmackusa Aug 15 '22 at 03:51
0

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

Chris Haas
  • 53,986
  • 12
  • 141
  • 274
  • And, feel free to substitute array_key_exists with isset – Chris Haas Aug 14 '22 at 14:14
  • what is $item here? – Yaseera Irfan Aug 22 '22 at 15:42
  • If you look at the demo link, the variable you called `$array` I called `$items`, and while iterating over those items I have a variable called `$item` – Chris Haas Aug 22 '22 at 15:45
  • you have used 2 loops here ?is it can be done only in one loop? – Yaseera Irfan Aug 22 '22 at 15:50
  • There's just one loop for creating the array, with two different ways to do it inside. I then use another loop for printing, same as the other answers. (One uses `array_reduce` which internally loops.) Unless you have a **lot** of data, I don't think you'll notice any much difference between these, and if you did have a lot of data I'd think a database would be able to better solve this with a `GROUP BY` and `COUNT`. – Chris Haas Aug 22 '22 at 16:56
  • It might be possible to perform your task with a single loop, but I think it would also involve a lot of random internal seeking, decreasing performance, or would require other functions which loop internally. If that's what you want, then the `array_reduce` version does that. – Chris Haas Aug 22 '22 at 16:58
0

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