Edited at

PHP:連想配列をキーでグループ化する

More than 1 year has passed since last update.

例えば、こういう食べ物の連想配列を

<?php

$foods = [
['name' => 'Apple', 'category' => 'fruits'],
['name' => 'Strawberry', 'category' => 'fruits'],
['name' => 'Tomato', 'category' => 'vegetables'],
['name' => 'Carot', 'category' => 'vegetables'],
['name' => 'water', 'category' => 'drink'],
['name' => 'beer', 'category' => 'drink'],
];
?>

カテゴリごとにグループ化して、こんな感じにしたい:

<?php

$expected = [
'fruits' => [
['name' => 'Apple', 'category' => 'fruits'],
['name' => 'Strawberry', 'category' => 'fruits'],
],
'vegetables' => [
['name' => 'Tomato', 'category' => 'vegetables'],
['name' => 'Carot', 'category' => 'vegetables'],
],
'drink' => [
['name' => 'water', 'category' => 'drink'],
['name' => 'beer', 'category' => 'drink'],
],
];
?>


実装案1: foreachを使ったよくある実装

<?php

function array_group_by(array $items, $keyName)
{
$groups = [];
foreach ($items as $item) {
$key = $item[$keyName];
if (array_key_exists($key, $groups)) {
$groups[$key][] = $item;
} else {
$groups[$key] = [$item];
}
}
return $groups;
}

assert(array_group_by($foods, 'category') === $expected);
?>

更に短縮すると:

function group_by(array $table, string $key): array

{
$groups = [];
foreach ($table as $row) {
$groups[$row[$key]][] = $row;
}
return $groups;
}


実装案2: イミュータブル風

<?php

function array_group_by2(array $items, $keyName)
{
$keys = array_unique(array_column($items, $keyName));
return array_combine($keys, array_map(function($key) use ($items, $keyName) {
return array_values(array_filter($items, function ($item) use ($key, $keyName) {
return $item[$keyName] == $key;
}));
}, $keys));
}

assert(array_group_by2($foods, 'category') === $expected);
?>

変数スコープとコレクションのAPI、辛すぎ・・・。やはりPHPはforeachを使うが良さそう。


実装案3: 先に入れ物を作ってforeachでミューテート

<?php

function array_group_by3(array $items, $keyName)
{
$groups = array_fill_keys(array_column($items, $keyName), []);
foreach ($items as $item) {
$groups[$item[$keyName]][] = $item;
}
return $groups;
}

assert(array_group_by3($foods, 'category') === $expected);
?>

実装案1と比べて、if-elseが無くなった。まずまずと言った感じ・・・?


実装案4: array_reduce

function group_by(array $table, string $key): array

{
return array_reduce($table, function (array $groups, array $row) use ($key) {
$groups[$row[$key]][] = $row;
return $groups;
}, []);
}