对数据行进行分组,并在每组中对一列求和并连接另一列

Group rows of data and in each group sum one column and concatenate another column

在 php 中,我有一个关联行的索引数组,如下所示:

$the_array = [
   ['id' => 1, 'value' => 10, 'name' => 'apple'],
   ['id' => 1, 'value' => 20, 'name' => 'orange'],
   ['id' => 1, 'value' => 30, 'name' => 'banana'],
   ['id' => 2, 'value' => 100, 'name' => 'car'], 
   ['id' => 2, 'value' => 200, 'name' => 'bicycle'],
];

并且我想通过对 id 值进行分组来重组它,并且在每个组中我想对 value 值求和并制作一个逗号分隔的字符串 name 值。

[
    ['id' => 1, 'value' => 60,  'name' => 'apple,orange,banana'],
    ['id' => 2, 'value' => 300, 'name' => 'car,bicycle']
]

这是我试过的:

function group_by($key, $data) {
    $result = array();
    foreach($data as $val) {
        if(array_key_exists($key, $val)){
            $result[$val[$key]][] = $val;
        }else{
            $result[""][] = $val;
        }
    }
    return $result;
}

它不起作用,结果是 wrong/incomplete。

我会创建一个中间数组,它首先按 id 分组到数组键中,然后使用它来调用 array_column() with array_sum()implode() 的组合来产生你的总和 value 并合并 name 字符串。

$temp_array = [];
foreach ($the_array as $init) {
  // Initially, group them on the id key as sub-arrays
  $temp_array[$init['id']][] = $init;
}

$result = [];
foreach ($temp_array as $id => $arr) {
  // Loop once for each id ($temp_array has 2 elements from your sample)
  // And add an element to $result
  $result[] = [
    'id' => $id,
    // Sum the value subkeys
    // array_column() returns just the key 'value' as an array
    // array_sum() adds them
    'value' => array_sum(array_column($arr, 'value')),
    // implode the name subkeys into a string
    'name' => implode(',', array_column($arr, 'name'))
  ];
}

print_r($result);
Array
(
    [0] => Array
        (
            [id] => 1
            [value] => 60
            [name] => apple,orange,banana
        )

    [1] => Array
        (
            [id] => 2
            [value] => 300
            [name] => car,bicycle
        )

)

虽然这个问题已经得到解答,但这里有另一种方法可以在一个循环中完成所有这些。

如果您跟踪将原始 ID 映射到各自数组索引的小地图。

$result = [];
$map = [];

foreach ($the_array as $subarray) {
    $id = $subarray['id'];

    // First time we encounter the id thus we can safely push it into result
    if (!key_exists($id, $map)) {
        // array_push returns the number of elements
        // since we push one at a time we can directly get the index.
        $index = array_push($result, $subarray) - 1;
        $map[$id] = $index;

        continue;
    }

    // If the id is already present in our map we can simply
    // update the running sum for the values and concat the
    // product names.
    $index = $map[$id];
    $result[$index]['value'] += $subarray['value'];
    $result[$index]['name'] .= ",{$subarray['name']}";
}

echo '<pre>';
print_r($result);
echo '</pre>';

结果:

Array
(
    [0] => Array
        (
            [id] => 1
            [value] => 60
            [name] => apple,orange,banana
        )
    [1] => Array
        (
            [id] => 2
            [value] => 300
            [name] => car,bicycle
        )
)

不必编写多个循环或保留索引映射。最精简、直接的方法是根据分组依据的值使用临时一级键。

下面的两个片段生成相同的输出并具有相同的内部处理。两个片段之间的唯一区别是一个使用语言结构迭代输入数据,另一个使用函数迭代。

当您循环遍历数据时,根据每一行的 id 值将临时键应用于结果数组。如果第一次遇到给定的id,则将整行数据存入组中。如果不是第一次遇到给定的 id,则将 value 值添加到先前的 value 值并将新的 name 值连接到先前存储的 name组中的值。

完成后,您可能希望通过调用 array_values() 重新索引第一级。

经典foreach():(Demo)

$result = [];
foreach ($the_array as $row) {
    if (!isset($result[$row['id']])) {
        $result[$row['id']] = $row;
    } else {
        $result[$row['id']]['value'] += $row['value'];
        $result[$row['id']]['name'] .= ",{$row['name']}";
    }
}
var_export(array_values($result));

array_reduce() 的函数式风格:(Demo)

var_export(
    array_values(
        array_reduce(
            $the_array,
            function ($carry, $row) {
                if (!isset($carry[$row['id']])) {
                    $carry[$row['id']] = $row;
                } else {
                    $carry[$row['id']]['value'] += $row['value'];
                    $carry[$row['id']]['name'] .= ",{$row['name']}";
                }
                return $carry;
            }
        )
    )
);