見ました。要は正規化されていないデータ構造を結合しようという問題。
自分ならどう書くだろうか。
foreachを使わない場合
PHPの標準関数にgroup_by的なものがあればもうちょっと簡潔に書ける気がしないでもないが、なくても難しくはない。
<?php
$data = [
['id' => 1, 'name' => 'イワーク', 'place' => '渓谷,洞窟', 'level' => 10, 'technique' => '岩雪崩'],
['id' => 1, 'name' => 'イワーク', 'place' => '砂漠,山頂', 'level' => 20, 'technique' => '岩石砲'],
['id' => 2, 'name' => 'ハガネール', 'place' => '鉱山,地中', 'level' => 10, 'technique' => 'メテオドライブ'],
['id' => 2, 'name' => 'ハガネール', 'place' => '丘陵,窪地', 'level' => 20, 'technique' => 'アイアンローラー'],
];
$init = array_map(
fn($a) => ['id' => $a['id'], 'name' => $a['name'], 'place_list' => [], 'lv_list' => []],
array_column($data, null, 'id'))
);
$result = array_reduce($data, function ($carry, $item) {
['id' => $id, 'level' => $level, 'technique' => $technique] = $item;
array_push($carry[$id]['place_list'], ...explode(',', $item['place']));
$carry[$id]['lv_list'][$level] = compact('level', 'technique');
return $carry;
}, $init);
動作確認: https://3v4l.org/gPFQF#v8.2rc1
-
array_reduce()の初期値のための$init配列を作る-
array_column($data, null, 'id'))で、idごとにユニークな配列に変換する[['id' => 1, 'name' => 'イワーク', ...], ['id' => 2, 'name' => 'ハガネール', ...]]- 同じIDのものが複数あれば後のものが残る
-
array_map()で、それぞれのid,name,place_list,lv_listを初期化する[['id' => 1, 'name' => 'イワーク', 'place_list' => [], 'lv_list' => []], ...]
-
-
array_reduceで最終結果となる$result配列を作る-
$dataの要素についてarray_reduce()で無名関数を再帰的に適用する- つまり、無名関数は4回呼び出される
- 無名関数では結果となる配列を順次追記する形で組み上げていく
-
['id' => $id, 'level' => $level, 'technique' => $technique] = $item;- 配列の要素を分解して、それぞれローカル変数に代入する
-
$id = $item['id']; $level = $item['level']; ...と同じことを短く書ける
-
array_push($carry[$id]['place_list'], ...explode(',', $item['place']));- 場所リストに追加する
-
explode(',', $item['place']))で分解したものを$carry[$id]['place_list']にpushする -
foreach (explode(',', $item['place'])) as $place) $carry[$id]['place_list'][] = $place;と同じ
-
$carry[$id]['lv_list'][$level] = compact('level', 'technique')- わざレベルリストにレベルをキーにして追加する
-
-
- 完成!
foreach
array_reduce() で書けるものは大概foreachで書いた方が簡潔に書けると相場が決まってるので書きます。
<?php
$data = [
['id' => 1, 'name' => 'イワーク', 'place' => '渓谷,洞窟', 'level' => 10, 'technique' => '岩雪崩'],
['id' => 1, 'name' => 'イワーク', 'place' => '砂漠,山頂', 'level' => 20, 'technique' => '岩石砲'],
['id' => 2, 'name' => 'ハガネール', 'place' => '鉱山,地中', 'level' => 10, 'technique' => 'メテオドライブ'],
['id' => 2, 'name' => 'ハガネール', 'place' => '丘陵,窪地', 'level' => 20, 'technique' => 'アイアンローラー'],
];
$result = [];
foreach ($data as ['id' => $id, 'name' => $name, 'place' => $places, 'level' => $level, 'technique' => $technique]) {
$result[$id] ??= compact('id', 'name');
$place_list = $result[$id]['place_list'] ?? [];
$result[$id]['place_list'] = array_merge($place_list, explode(',', $places));
$result[$id]['lv_list'][$level] = compact('level', 'technique');
}
var_dump($result);
動作確認: https://3v4l.org/pRcC8#v8.2rc1
- 結果を蓄積する配列を
$result = [];として初期化する -
$dateの内容をイテレーションする-
$data as ['id' => $id, 'name' => $name, ...]で配列の中身をそれぞれのローカル変数に分解できる -
$result[$id] ??= compact('id', 'name')-
$result[$id]が未定義ならば['id' => $id, 'name' => $name]で初期化する
-
-
$place_list = $result[$id]['place_list'] ?? [];-
$resultから$result[$id]['place_list']が既に入っていたら取り出し、なかったら[]
-
-
$result[$id]['place_list'] = array_merge($place_list, explode(',', $places));- 取り出したplace_list(または初期状態の空配列)と場所文字列(
'渓谷,洞窟')の分解結果をマージして$resultに追記
- 取り出したplace_list(または初期状態の空配列)と場所文字列(
-
$result[$id]['lv_list'][$level] = compact('level', 'technique')-
$result[$id]['lv_list']に、レベルをキーにして['level' => $level, 'technique' => $technique]を追記
-
-
- 完成!
すっきりしましたね。 if (!isset($result[$id]['lv_list'])) $result[$id]['lv_list'] = []; のような初期化はしなくても動きます。