4
1

More than 1 year has passed since last update.

標準関数で配列操作

Last updated at Posted at 2022-09-09

見ました。要は正規化されていないデータ構造を結合しようという問題。

自分ならどう書くだろうか。

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 に追記
    • $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'] = []; のような初期化はしなくても動きます。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1