こんな配列をGroup By
したい。
GroupByArray.php
/** @var array 温泉地テーブル. */
private const TABLE = [
['kuni' => '日本', 'todofuken' => '群馬県', 'shi' => '吾妻郡', 'onsen' => '草津'],
['kuni' => '日本', 'todofuken' => '群馬県', 'shi' => '渋川市', 'onsen' => '伊香保'],
['kuni' => '日本', 'todofuken' => '神奈川県', 'shi' => '足柄下郡', 'onsen' => '湯河原'],
['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '観海寺'],
['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '浜脇'],
['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '堀田'],
['kuni' => 'ドイツ', 'todofuken' => 'ヴュルテンベルク州', 'shi' => 'バーデン=バーデン', 'onsen' => 'フリードリヒス'],
['kuni' => 'ドイツ', 'todofuken' => 'ヴュルテンベルク州', 'shi' => 'バーデン=バーデン', 'onsen' => 'カラカラ'],
['kuni' => 'ドイツ', 'todofuken' => 'ヘッセン州', 'shi' => 'ヴィースバーデン', 'onsen' => 'アウカムタル'],
['kuni' => 'ドイツ', 'todofuken' => 'ヘッセン州', 'shi' => 'ヴィースバーデン', 'onsen' => 'カイザーフリードリヒ'],
];
foreach
でgroup by
キーでグループ化する - Qiita を参考にさせていただきました。
GroupByArray.php
// 省略
/**
* 2つの項目でGROUP BYする.
* @param string $key1 1番目のグループ化する項目の名前.
* @param string $key2 2番目のグループ化する項目の名前.
*/
public function groupByTwoKey(string $key1, string $key2): void
{
$grouped = [];
foreach (self::TABLE as $row) {
$key = $row[$key1].$row[$key2];
$grouped[$key][] = $row;
}
return $grouped;
}
// 省略
$groupBy = new GroupByArray();
var_dump($groupBy->groupByTwoKey('kuni','todofuken'));
キーにしたカラムが残ってちょっとうざい
$ php GroupByArray.php
array(5) {
["日本群馬県"]=>
array(2) {
[0]=>
array(4) {
["kuni"]=>
string(6) "日本"
["todofuken"]=>
string(9) "群馬県"
["shi"]=>
string(9) "吾妻郡"
["onsen"]=>
string(6) "草津"
}
[1]=>
array(4) {
["kuni"]=>
string(6) "日本"
["todofuken"]=>
string(9) "群馬県"
["shi"]=>
string(9) "渋川市"
["onsen"]=>
string(9) "伊香保"
}
}
["日本神奈川県"]=>
array(1) {
[0]=>
array(4) {
["kuni"]=>
string(6) "日本"
["todofuken"]=>
string(12) "神奈川県"
["shi"]=>
string(12) "足柄下郡"
["onsen"]=>
string(9) "湯河原"
}
}
["日本大分県"]=>
array(3) {
[0]=>
array(4) {
["kuni"]=>
string(6) "日本"
["todofuken"]=>
string(9) "大分県"
["shi"]=>
string(9) "別府市"
["onsen"]=>
string(9) "観海寺"
}
[1]=>
array(4) {
["kuni"]=>
# 省略
group by
のキーにしたカラムはいらない場合
GroupByArray->groupByTwoKey
// 省略
foreach (self::TABLE as $row) {
/** @var array $notKeys キーにする要素を削除した配列. */
$notKeys = array_filter($row, function($key) use ($key1, $key2) {
return $key !== $key1 && $key !== $key2;
}, ARRAY_FILTER_USE_KEY);
$key = $row[$key1].$row[$key2];
$grouped[$key][] = $notKeys;
// 省略
$ php GroupByArray.php
array(5) {
["日本群馬県"]=>
array(2) {
[0]=>
array(2) {
["shi"]=>
string(9) "吾妻郡"
["onsen"]=>
string(6) "草津"
}
[1]=>
array(2) {
["shi"]=>
string(9) "渋川市"
["onsen"]=>
string(9) "伊香保"
}
}
["日本神奈川県"]=>
array(1) {
[0]=>
array(2) {
["shi"]=>
string(12) "足柄下郡"
["onsen"]=>
string(9) "湯河原"
}
}
["日本大分県"]=>
array(3) {
[0]=>
array(2) {
["shi"]=>
string(9) "別府市"
["onsen"]=>
string(9) "観海寺"
}
[1]=>
array(2) {
["shi"]=>
string(9) "別府市"
["onsen"]=>
# 省略
group by
のキーにしたカラム毎に階層を作りたい場合
GroupByArray->groupByTwoKey
// 省略
/** @var array $notKeys キーにする要素を削除した配列. */
$notKeys = array_filter($row, function($key) use ($key1, $key2) {
return $key !== $key1 && $key !== $key2;
}, ARRAY_FILTER_USE_KEY);
$grouped[$row[$key1]][$row[$key2]][] = $notKeys;
// 省略
$ php GroupByArray.php
array(2) {
["日本"]=>
array(3) {
["群馬県"]=>
array(2) {
[0]=>
array(2) {
["shi"]=>
string(9) "吾妻郡"
["onsen"]=>
string(6) "草津"
}
[1]=>
array(2) {
["shi"]=>
string(9) "渋川市"
["onsen"]=>
string(9) "伊香保"
}
}
["神奈川県"]=>
array(1) {
[0]=>
array(2) {
["shi"]=>
string(12) "足柄下郡"
["onsen"]=>
string(9) "湯河原"
# 省略
array_reduce
でgroup by
集計するとき、array_reduceと無名関数が便利だった! - maeharinの日記を参考にさせていただきました。
使い方は、ドキュメントを見ました(array_reduce — コールバック関数を用いて配列を普通の値に変更することにより、配列を再帰的に減らす)
GroupByArray.php
/**
* 3つの項目でGROUP BYする.
* @param string $key1 1番目のグループ化する項目の名前.
* @param string $key2 2番目のグループ化する項目の名前.
* @param string $key3 3番目のグループ化する項目の名前.
*/
public function groupByTreeKey(string $key1, string $key2, string $key3): void
{
$grouped = array_reduce(self::TABLE, function($grouped, $row) use ($key1, $key2, $key3) {
$key = $row[$key1].$row[$key2].$row[$key3];
$grouped[$key][] = $row;
return $grouped;
});
return $grouped;
}
// 省略
$groupBy = new GroupByArray();
var_dump($groupBy->groupByTreeKey('kuni','todofuken', 'shi'));
とてもうざい
$ php GroupByArray.php
array(6) {
["日本群馬県吾妻郡"]=>
array(1) {
[0]=>
array(4) {
["kuni"]=>
string(6) "日本"
["todofuken"]=>
string(9) "群馬県"
["shi"]=>
string(9) "吾妻郡"
["onsen"]=>
string(6) "草津"
}
}
["日本群馬県渋川市"]=>
array(1) {
[0]=>
array(4) {
["kuni"]=>
string(6) "日本"
["todofuken"]=>
string(9) "群馬県"
["shi"]=>
string(9) "渋川市"
["onsen"]=>
string(9) "伊香保"
}
}
["日本神奈川県足柄下郡"]=>
array(1) {
[0]=>
array(4) {
["kuni"]=>
string(6) "日本"
["todofuken"]=>
string(12) "神奈川県"
["shi"]=>
string(12) "足柄下郡"
["onsen"]=>
string(9) "湯河原"
}
}
["日本大分県別府市"]=>
array(3) {
[0]=>
array(4) {
["kuni"]=>
string(6) "日本"
# 省略
group by
のキーにしないカラムを連結したい場合
GroupByArray->groupByTreeKey
/** @var string カンマ. */
private const COMMA = ',';
// 省略
$grouped = array_reduce(self::TABLE, function($grouped, $row) use ($key1, $key2, $key3) {
$notKeys = array_filter($row, function($key) use($key1, $key2, $key3) {
return $key !== $key1 && $key !== $key2 && $key !== $key3;
}, ARRAY_FILTER_USE_KEY);
$notKey = implode(self::COMMA, $notKeys);
$key = $row[$key1].$row[$key2].$row[$key3];
if (array_key_exists($key, $grouped)) {
$grouped[$key] .= self::COMMA.$notKey;
} else {
$grouped[$key] = $notKey;
}
return $grouped;
}, array());
// 省略
$ php GroupByArray.php
array(6) {
["日本群馬県吾妻郡"]=>
string(6) "草津"
["日本群馬県渋川市"]=>
string(9) "伊香保"
["日本神奈川県足柄下郡"]=>
string(9) "湯河原"
["日本大分県別府市"]=>
string(23) "観海寺,浜脇,堀田"
["ドイツヴュルテンベルク州バーデン=バーデン"]=>
string(34) "フリードリヒス,カラカラ"
["ドイツヘッセン州ヴィースバーデン"]=>
string(49) "アウカムタル,カイザーフリードリヒ"
}
group by
してフォーマットに埋め込む
文字列リテラルに式展開 - Qiitaで思いつきました。
素晴らしく汎用性のないものとなりました。
GroupByArray
/**
* group byした結果をフォーマットに埋め込む.
* @return string フォーマットした文字列.
*/
public function groupByAndFormat(): string
{
$key1 = 'kuni';
$key2 = 'todofuken';
$key3 = 'other';
$format = "{$key1}の{$key2}の温泉には、{$key3}があります。";
/** @var array $groups group byした結果. */
$groups = self::groupByTwoKey($key1, $key2);
/** @var array $formated フォーマットした文字を格納する配列. */
$formated = array();
/** @var string $kuni kuniカラムの値. */
foreach ($groups as $kuni => $todofukens) {
/** @var string $todofuken todofukenカラムの値. */
foreach ($todofukens as $todofuken => $others) {
/** @var string $other group byのキーにしなかった要素を「と」でつないだ文字列. */
$other = '';
foreach ($others as $otherParts) {
/** @var string $otherPart group byのキーにしなかった要素をくっつけた文字列. */
$otherPart = implode($otherParts);
if (strlen($other) !== 0) {
$other .= 'と';
}
$other .= $otherPart;
}
/** @var array 置換する文字のセット. */
$replacePairs = [$key1 => $kuni, $key2 => $todofuken, $key3 => $other];
$formated[] = strtr($format, $replacePairs);
}
}
// 改行でつなげて1つの文字列にする.
return implode(PHP_EOL, $formated);
}
// 省略
$groupBy = new GroupByArray();
var_dump($groupBy->groupByAndFormat());
$ php GroupByArray.php
string(607) "日本の群馬県の温泉には、吾妻郡草津と渋川市伊香保があります。
日本の神奈川県の温泉には、足柄下郡湯河原があります。
日本の大分県の温泉には、別府市観海寺と別府市浜脇と別府市堀田があります。
ドイツのヴュルテンベルク州の温泉には、バーデン=バーデンフリードリヒスとバーデン=バーデンカラカラがあります。
ドイツのヘッセン州の温泉には、ヴィースバーデンアウカムタルとヴィースバーデンカイザーフリードリヒがあります。"
全貌
GroupByArra.php
<?php
class GroupByArray
{
/** @var array 温泉地テーブル. */
private const TABLE = [
['kuni' => '日本', 'todofuken' => '群馬県', 'shi' => '吾妻郡', 'onsen' => '草津'],
['kuni' => '日本', 'todofuken' => '群馬県', 'shi' => '渋川市', 'onsen' => '伊香保'],
['kuni' => '日本', 'todofuken' => '神奈川県', 'shi' => '足柄下郡', 'onsen' => '湯河原'],
['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '観海寺'],
['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '浜脇'],
['kuni' => '日本', 'todofuken' => '大分県', 'shi' => '別府市', 'onsen' => '堀田'],
['kuni' => 'ドイツ', 'todofuken' => 'ヴュルテンベルク州', 'shi' => 'バーデン=バーデン', 'onsen' => 'フリードリヒス'],
['kuni' => 'ドイツ', 'todofuken' => 'ヴュルテンベルク州', 'shi' => 'バーデン=バーデン', 'onsen' => 'カラカラ'],
['kuni' => 'ドイツ', 'todofuken' => 'ヘッセン州', 'shi' => 'ヴィースバーデン', 'onsen' => 'アウカムタル'],
['kuni' => 'ドイツ', 'todofuken' => 'ヘッセン州', 'shi' => 'ヴィースバーデン', 'onsen' => 'カイザーフリードリヒ'],
];
/** @var string カンマ. */
private const COMMA = ',';
/**
* 2つの項目でGROUP BYする.
* @param string $key1 1番目のグループ化する項目の名前.
* @param string $key2 2番目のグループ化する項目の名前.
*/
public function groupByTwoKey(string $key1, string $key2): array
{
$grouped = [];
foreach (self::TABLE as $row) {
/** @var array $notKeys キーにする要素を削除した配列. */
$notKeys = array_filter($row, function($key) use ($key1, $key2) {
return $key !== $key1 && $key !== $key2;
}, ARRAY_FILTER_USE_KEY);
$grouped[$row[$key1]][$row[$key2]][] = $notKeys;
}
return $grouped;
}
/**
* 3つの項目でGROUP BYする.
* @param string $key1 1番目のグループ化する項目の名前.
* @param string $key2 2番目のグループ化する項目の名前.
* @param string $key3 3番目のグループ化する項目の名前.
*/
public function groupByTreeKey(string $key1, string $key2, string $key3): array
{
$grouped = array_reduce(self::TABLE, function($grouped, $row) use ($key1, $key2, $key3) {
$notKeys = array_filter($row, function($key) use($key1, $key2, $key3) {
return $key !== $key1 && $key !== $key2 && $key !== $key3;
}, ARRAY_FILTER_USE_KEY);
$notKey = implode(self::COMMA, $notKeys);
$key = $row[$key1].$row[$key2].$row[$key3];
if (array_key_exists($key, $grouped)) {
$grouped[$key] .= self::COMMA.$notKey;
} else {
$grouped[$key] = $notKey;
}
return $grouped;
}, array());
return $grouped;
}
/**
* group byした結果をフォーマットに埋め込む.
* @return string フォーマットした文字列.
*/
public function groupByAndFormat(): string
{
$key1 = 'kuni';
$key2 = 'todofuken';
$key3 = 'other';
$format = "{$key1}の{$key2}の温泉には、{$key3}があります。";
/** @var array $groups group byした結果. */
$groups = self::groupByTwoKey($key1, $key2);
/** @var array $formated フォーマットした文字を格納する配列. */
$formated = array();
/** @var string $kuni kuniカラムの値. */
foreach ($groups as $kuni => $todofukens) {
/** @var string $todofuken todofukenカラムの値. */
foreach ($todofukens as $todofuken => $others) {
/** @var string $other group byのキーにしなかった要素を「と」でつないだ文字列. */
$other = '';
foreach ($others as $otherParts) {
/** @var string $otherPart group byのキーにしなかった要素をくっつけた文字列. */
$otherPart = implode($otherParts);
if (strlen($other) !== 0) {
$other .= 'と';
}
$other .= $otherPart;
}
/** @var array 置換する文字のセット. */
$replacePairs = [$key1 => $kuni, $key2 => $todofuken, $key3 => $other];
$formated[] = strtr($format, $replacePairs);
}
}
// 改行でつなげてⅠつの文字列にする.
return implode(PHP_EOL, $formated);
}
}
$groupBy = new GroupByArray();
var_dump($groupBy->groupByTwoKey('kuni','todofuken'));
var_dump($groupBy->groupByTreeKey('kuni','todofuken', 'shi'));
var_dump($groupBy->groupByAndFormat());