Группируйте строки данных и в каждой группе суммируйте один столбец и объединяйте другой столбец

#php #multidimensional-array #concatenation #grouping #array-sum

#php #многомерный-массив #объединение #группировка #array-sum

Вопрос:

В 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;
}
 

Это не работает, и результат неправильный / неполный.

Комментарии:

1. Ваш код даже не пытается выполнить какое-либо добавление и объединение, поэтому нормально, что он возвращает что-то отличное от того, что вы хотите.

2. Вы получаете данные из SQL? Может быть, лучше агрегировать данные на стороне БД?

Ответ №1:

Я бы создал промежуточный массив, который сначала группируется в ключи массива id , а затем использует его для вызова комбинаций array_column() с 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
        )

)
 

Ответ №2:

Хотя на этот вопрос уже дан ответ, вот альтернативный способ сделать все это за один цикл.

Если вы отслеживаете небольшую карту, которая отображает исходные идентификаторы в соответствующие индексы массива.

 $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
        )
)
 

Ответ №3:

Нет необходимости писать несколько циклов или сохранять карту индексов. Самый простой, прямой способ — использовать временные ключи первого уровня на основе значения, по которому группируется.

Оба приведенных ниже фрагмента генерируют одинаковые выходные данные и имеют одинаковую внутреннюю обработку. Единственное различие между двумя фрагментами состоит в том, что один повторяет входные данные, используя языковую конструкцию, другой использует функциональную итерацию.

По мере перебора данных применяйте временные ключи к результирующему массиву на основе id значения каждой соответствующей строки. Если первое столкновение задано id , тогда сохраните полную строку данных в группу. Если это не первое столкновение с данным id , затем добавьте value значение к предыдущему value значению И объедините новое name значение с ранее сохраненным name значением в группе.

По завершении вы можете повторно проиндексировать первый уровень, вызвав array_values() .

Классический foreach() : (Демо)

 $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() : (Демо)

 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;
            }
        )
    )
);