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