アナログCPU:5108843109

ゲームと音楽とプログラミング(酒と女とロックンロールのノリで)

('ω') < イザユケエンジニャー

連想配列で検索したい!!!

(2018-09-13:書いてるとおりには動いてたけど、実用を考えると破綻してたので、まるっと書き直しました)

array_columnを使えないバージョンを使わざるを得ない時用。
使えるならこちらをご参照ください。

今回は以下のような連想配列から検索することを考えます。

<?php
$data_list = array(
    1 => array('hoge' => "a"  , 'fuga' => "A"  ),
    2 => array('hoge' => "b"  , 'fuga' => "B"  ),
    3 => array('hoge' => "a"  , 'fuga' => "C"  ), // hogeが1と重複していることに注意
    4 => array('hoge' => null , 'fuga' => "D"  ),
    5 => array('hoge' => false, 'fuga' => "E"  ),
    6 => array('hoge' => "f"  , 'fuga' => null ),
    7 => array('hoge' => "g"  , 'fuga' => false),
);

検索パターンは次の3通り。

  • キーからhogeの値を求める
  • hogeからキーの値を求める
  • hogeからfugaの値を求める

これを全部まとめて関数化したい!と思うのですが、一つ問題が。

「検索結果が存在しないこと」と「検索結果がfalseであること」の戻り値を両立するの難しくない?

上記の配列で、「キーが7のfugaの値が欲しい!」となったとき、欲しい結果はもちろんfalseなんですが、
じゃあ「キーが8のfugaの値が欲しい!」と言われたら? 8は存在しないんだから、関数としてはfalseを返すべきじゃないの?

まあ、戻り値をこんな感じにすることもできますが

array(
    'not_found_flag' => 1,    // 見つからなかったよフラグ
    'result'         => null, // 検索結果
)

お手軽に検索したいから関数化するというのに、戻り値がいちいち配列なのはちょっとつらい。あとなんかかっこわるい。

連想配列の中身を検索し、キーを返す関数

ということで、そもそもひとつにまとめないパターンを考えてみました。
キーはfalseにならないのでこれなら関数としてもすっきり。

ただし、ここでは「hoge」やら「fuga」やらが存在してないケースまでは考慮してないので
必要に応じて更にバリデートを入れることになります。

あと、比較は厳密に行っています。場合によってはゆるい比較にしてもOK。

<?php
function getKey($list, $val, $key)
{
    foreach ($list as $k => $v)
    {
        if ($v[$key] === $val)
        {
            return $k;
        }
    }
    return false;
}

さっきの配列で検索してみるとこんな感じ。

<?php
var_dump( getKey($data_list, "a"  , "hoge") ); // 1 : 重複してると最初のキーを返す
var_dump( getKey($data_list, null , "hoge") ); // 4 : 値がnullの場合もOK
var_dump( getKey($data_list, false, "hoge") ); // 5 : 値がfalseの場合もOK
var_dump( getKey($data_list, "h"  , "hoge") ); // false : 存在しない
var_dump( getKey($data_list, "A"  , "fuga") ); // 1 : もちろんfugaからも検索可

あとは適宜必要に応じて戻り値がfalseかどうかをチェックすればOK。
検索結果が存在しないということはあり得ない、という場合は見なくていいし。

ということで実際に検索してみましょう。

さっきの検索したいパターンに当てはめると、

1. キーからhogeの値を求める

これはキーが分かっている状態なのでそもそもさっきの関数すら使わない。

<?php
// キー「1」の「hoge」を求める:$resは「a」
$res = $data_list[1]['hoge'];

レコードが存在しない可能性があるならこう。

<?php
if (isset($data_list[1]['hoge']))
{
    // 検索結果は $data_list[1]['hoge']
}
else 
{
    // 検索結果なし
}
2. hogeからキーの値を求める

これはつまりさっきの関数の戻り値。

<?php
// hogeが「a」のキーを求める:$resは「1」
$res = getKey($data_list, "a"  , "hoge");

レコードが存在しない可能性があるならこう。

<?php
$res = getKey($data_list, "a"  , "hoge");
if ($res !== false)
{
    // 検索結果は $res
}
else 
{
    // 検索結果なし
}
3. hogeからfugaの値を求める

1と2の合わせ技。

<?php
// hogeが「a」の「fuga」を求める:$resは「A」
$res = $data_list[getKey($data_list, "a"  , "hoge")]['fuga'];

レコードが存在しない可能性があるならこう。

<?php
$key = getKey($data_list, "a"  , "hoge");
if ($key !== false)
{
    // 検索結果は $data_list[$key]['fuga'];
}
else
{
    // 検索結果なし
}

falseのデータとかそもそもレコードが存在しないとかありえないから一発で済ませたい!!!

という場合はこちら。

キー「1」を元に、hogeの値「a」を求める

さっきと同じ。

<?php
// $resは「a」
$res = $data_list[1]['hoge'];
hogeの値「a」を元に、キーの値「1」を求める

ほい。

<?php
// $resは「1」
$res = key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)));

いきなりややこしくなりました。
ちょっと分解しましょう。

<?php
// 配列[hoge]が「a」ならtrueを返す無名関数
function($l){return $l['hoge'] === "a";}

// …をarray_mapにぶちこむ
array_map(function($l){return $l['hoge'] === "a";}, $data_list);

// その結果はtrueかfalseでできた一次元配列
//   array(1 => true, 2=> false, 3=> true)
// になるので、それをtrueでフィルタリングする
array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list));

// trueだけになった配列
//   array(1 => true, 3=> true)
// の、現在の…っつーか何もしてないのでつまりは先頭のキーを取得
key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)));

// 完成
$res = key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)));

ちなみに存在しない値で検索しようとした場合はNULLになります。
ただ、キーがNULLになることはあり得ないので(NULLに設定しようとすると勝手に空文字になります)
NULLなら検索結果なしと判断してOK。

hogeの値「a」を元に、fugaの値「A」を求める

上記2つの組み合わせ。

<?php
// $resは「A」
$res = $data_list[key(array_filter(array_map(function($l){return $l['hoge'] === "a";}, $data_list)))]['fuga'];

これも存在しない場合はNoticeが出ます。