PHP で配列の先頭要素の値を取得するきれいな方法を考える
あすで東日本大震災からまる4年ですね。当日の発災時刻には山形県山形市の JR 北山形駅というところに偶然いた kagata です。何もないときに旅行に訪れたいとかねがね思っているのですが、まだ実現できずにいます。
さて、今回は前回の続編です。配列の先頭要素の値をとってきたいなら array_shift()
よりも current()
のほうが処理速度が速い…ということを前回確かめました。ただ、どちらの方法もあまりきれいではないし、どんな場面にでも使えるというわけにはいかなさそうです。
ということで、配列の先頭要素の値を取得するできるだけきれいな方法をねちこく考えてみました。
おさらい
前回の記事では、配列の先頭要素の値を取得する方法を2つご紹介しました。さらに、読者の方からさらにもう1つの方法をご提案いただいています。
それぞれの方法について、簡単に振り返ります。
array_shift()
関数 array_shift()
は PHP マニュアルで「array
の最初の値を取り出して返します」と紹介されています。これだけ見ると、今回求めている方法そのものずばりのように思われます。
ところが、この関数には「配列の先頭要素を削除して数値添字を振りなおす」という別の作用があります。よって、配列の中身を保持したい場面では使えません。また、もとの配列を捨ててしまってよいような場合でも、数値添字の振りなおしがオーバーヘッドとなってしまいます(これが前回のお話)。
current()
PHP の配列は「内部ポインタ」を持っており、初期状態では配列の先頭要素を差しています。そこで、関数 current()
で内部ポインタの差す要素の値を取得すれば、array_shift()
のような配列の中身の改変なしに配列の先頭要素の値を取得できる…という寸法です。
array_shift()
による方法の問題点は解決できますが、配列の内部ポインタが動かされていれば先頭でない要素の値が返ってきてしまいます。
reset()
current()
で対応できない、配列の内部ポインタが動かされている場合にでも問題なく対応できる方法です。関数 reset()
は配列の内部ポインタを先頭要素まで戻し、その上で内部ポインタが差している要素の値(つまり先頭要素の値)を返します。current()
の持つ問題点も解決できますし、内部ポインタを動かすとはいえ array_shift()
の数値添字の振りなおしほど重い処理にもならなさそうです。
しかし、今度は内部ポインタを動かしてはいけない場面ではこの方法が使えません。また、欲を言えば「配列の先頭要素の値を取得したい」という意図が明確に伝わるようなコードにしたいところです。”reset” だと、いかにも「内部ポインタを先頭に戻す」のが所望の作用みたいです…って細かすぎるでしょうか。
目標
以上を踏まえて、次の目標を立てました。
1)できるだけ副作用をなくす
できるだけもとの配列の状態を変えない、どんな場面でも何度使っても同じ値が返ってくる方法を目指します。不要な処理のせいで重くなるのも避けられるでしょう。
2)できるだけ一目で何をやっているかわかるようにする
「配列の先頭要素の値を取得しようとしている」ことが初見でも直感的にわかるようなコードを目指します。トリッキーなやつは避けます。しかし、コードが短くすんだらうれしいです。
案1:array_values() を使うワンライナー
tanaka がすてきなコードを教えてくれました。
$first = array_values($arr)[0];
関数 array_values()
は配列の値だけを取り出して数値添字の配列にして返します。よって、その返り値の配列で添字が0の要素を参照すれば、元の配列の添字の内容にかかわらずいつでも先頭の要素の値が得られるわけです。
関数の返り値をそのまま配列として扱う記法は PHP5.4で導入されました。今後は使える機会が増えそうです。
案2:配列を別の変数に受ける
残念ながら、案1の記法は PHP5.3以前ではエラーになってしまいます。PHP5.3(CentOS6とか)あるいは PHP5.1(CentOS5とか)なんて環境に出会うことも弊社案件ではまだまだあるのが実情です。そんなレガシーな環境でも使える方法を考えてみました。
$a = array_values($arr);
$first = $a[0];
単純に考えると、従来と同様に関数の結果をいったん変数に受けてから参照すればよさそうです。でも、それだけのために変数ができてしまうのは、なんだかうっとうしいかも…。
$first = get_first_element($arr);
function get_first_element($arr) {
$a = array_values($arr);
return $a[0];
}
変数 $a
がうっとうしいので関数にラップしてみました。それっぽい名前も付けられて、まずまずいい感じです。
ここまでやるうち、ある挙動に気づきました。こんなコードを書いてみます。
$arr = array(
'foo' => 'ほげ',
'bar' => 'ふが',
'baz' => 'ぴよ',
);
// 内部ポインタを動かしてみる
next($arr);
// 内部ポインタを動かした配列を別の変数に受けてみる
$a = $arr;
// それぞれの内部ポインタの位置は?
echo current($a) . "\n";
echo current($arr) . "\n"
実行結果は以下のとおり。
ほげ
ふが
配列を別の変数に代入する際、内部ポインタの位置は引き継がれないんですね。
だったらこれでいいじゃないか。
$first = get_first_element($arr);
function get_first_element($arr) {
// 引数として受け取った時点で常に内部ポインタは初期化されている!
return current($arr)
}
current()
をただラップするという、なんだか素朴な方法に落ち着いたのでした。
まとめ
- 最近の PHP はよくできてますね