読者です 読者をやめる 読者になる 読者になる

再帰的なarray_searchを作った

どもども

すっかり春な気候になってきて、フレッシュな方々を目にする機会が多くなりました。 フレッシュな感じに羨望の眼差しを向けながら、ここ4年ぐらい、ず〜っとPHPをメインに扱っています。

う〜ん、もともとず〜っとJavaPerlな感じの人で、敷居が低いと噂のPHPを横目に見てましたが、ソシャゲを扱うようになってPHPも扱うようになったんですが、もはや何を作るにしても、もうPHPでよくね?みたいな脳みそになってしまったのが良かったのか悪かったのか。。
まぁ、アンチPHPな方もお見かけしますし、当然、僕自身もPHPのこの言語仕様はやだな〜って思ったりしたりすることもあります。 確かにレガシーなシステムを扱うときに、脳みそひねくり回さないとダメなときもあるし、PHPマニュアル 見てて、え、そうなの。。って思うときもあります。
でも地雷を踏まない能力ってエンジニアに必要な能力の一つだったりするのかなぁって今は思っています。 そういう能力を養うコストと、PHP の強力な文字列処理や配列処理の恩恵を受けるメリットを天秤にかけて、やっぱPHPで良いかなぁと思う次第です。。


個人的なコラムはどうでもいいとして、僕がPHPを気に入っている面の一つが配列処理です。 array_column とか usort とかは超気にいってますが、 array_search が単一階層の配列しか検索してくれない。。 配列の中に設定項目を連想配列にして詰め込んで、特定の設定項目を探し出すっていうケース、ソシャゲでは結構あったんですが、ソシャゲから離れた今、またそういう要件に対峙する時が来ました。 あれ、こういうパターンって確か共通関数にしてたなぁって思ったので、はてブから「そろそろブログ書かない?」メールも来たので、筆を取る気になりました。

想定している連想配列は以下のような構造です。

$event = array(
    array(
        'start_date' => '2017-01-01 00:00:00',
        'end_date' => '2017-03-31 23:59:59',
        'key' => 'event_1'
    ),
    array(
        'start_date' => '2017-02-01 00:00:00',
        'end_date' => '2017-04-31 23:59:59',
        'key' => 'event_2'
    ),
);

keyevent_2 の要素番号を取りたいといった場面がよくありました。 ここで、 array_search でって思うのですが、このような配列の中に配列がある多重配列構造の場合、思った通りの動作をしてくれません。 そこで、こんな関数を作ってみました。

function array_search_recursive($needle, $heystack)
{
    $keys = array();
    $func = function ($needle, $heystack) use (&$func, &$keys) {
        foreach ($heystack as $key => $val) {
            if (is_array($val)) {
                if ($func($needle, $val) !== false) {
                    $keys[] = $key;
                }
            }
            if ($needle == $val) {
                $keys[] = $key;
            }
        }
        return false;
    };
    $func($needle, $heystack);
    return $keys;
}

$needle に検索したいキーワード、 $heystack に検索対象の配列を指定すると、検索に引っかかる要素番号を配列で返します。 検索に引っかからなかった場合は、空配列を返します。

これ、同じことを考えておられた方がいて、こちらのQiitaの記事を参考にしました。

qiita.com

記事では検索キーワードに引っかかった最初の要素番号を返却していますが、仰っている通り、引っかかった全ての要素番号を返却した方が、汎用性が高くなりそうだったので、少し改良しています。 とは言え、クロージャに配列を参照渡しして、引っかかったら配列に詰めるってとこしか改良していませんが。。^^;


あと、 start_date2017-01-15 00:00:00 以降の要素番号とかを取りたいとかもありますよね。
そういう場合は、 array_search_recursive に関数を渡せるようにしてあげたら、もっとかっこよくなるかなぁ。 でも今回はそういう要件ではなかったので、脳みそ終了しました。


クロージャを代入した変数を参照渡しして、検索対象が配列だった場合、どんどん下層まで検索していくので、リークしないか気になるとこですが、そこそこ使えるのではないでしょうか
とは言え、 array_column で似たようなことできるよね〜とも思うのですが、実は開発環境は5.3みたいなとこあるのでは?

こういうの、Zephirで実装できたらかっこいいよねぇ

zephir-lang.com

最後に、いつぞやのPHPカンファレンスで見た尊敬するRasmus Lerdorfのインタビュー記事でも載せておこうかな〜

gihyo.jp