■配列のポインタ
各々の配列は、現在の要素を示すポインタ(イテレータ)を持っている。
- current()
- 現在のポインタが指している要素
- key()
- 現在の要素のキーを返す
- reset()
- ポインタを先頭に移動
- end()
- ポインタを配列の最後の要素に移動
- next()
- ポインタを1つ次に移動
- prev()
- ポインタを1つ前に移動
- each()
- 現在のキーと値を配列として返し、ポインタを次に移動
以下のように使用する。通常、whileはポインタの移動を行わないので、ポインタを移動させる関数の使用や、ループの終了条件の設定が不可欠である。また、foreachを使わないメリットは、配列のコピーを生成しないのでメモリを節約できる点である。
<?php $tmpAry = array('a' => 1, 'b' => 2, 'c' => 3); function display(&$array){ reset($array); while(key($array) !== null){ echo key($array) . ': ' . current($array) . PHP_EOL; next($array); } } display($tmpAry); ?>
以下のように配列の後方からループさせることもできる。
<?php $tmpAry = array('a' => 1, 'b' => 2, 'c' => 3); end($tmpAry); while(key($tmpAry) !== null){ echo key($array) . ': ' . current($array) . PHP_EOL; prev($array); } ?>
また、以下のようにforeachと同じような表現をすることが可能だ。この方法は大きな配列でメモリーを節約したい場合などに有効である。
<?php $tmpAry = array('a' => 1, 'b' => 2, 'c' => 3); reset($tmpAry); while(list($key, $value) = each($tmpAry)){ print("{$key}: {$value}" . PHP_EOL); } ?>
■foreach構文
上述のように、whileを使ったループコントロールはメモリ消費を抑制できるかわりに、コードが煩雑になる。従って、個人的には以下のようなforeachでのコントロールをたいていは用いる。
<?php $tmpAry = array('a' => 1, 'b' => 2, 'c' => 3); foreach($tmpAry as $key => $value){ echo $key . ': ' . $value; } ?>
但し、foreach文では配列のコピーが生成されるので、ループ内で配列の要素($tmpAry[$key]ではなく$value)に処理を加えても、元の配列には影響しない。もしも反映させたい場合は、以下のようにリファレンス(参照)を使う。
<?php $tmpAry = array('a' => 1, 'b' => 2, 'c' => 3); foreach($tmpAry as $key => &$value){ echo $key . ': ' . $value; $value++; } var_dump($tmpAry); /* array(3) { ["a"]=> ∫(2) ["b"]=> ∫(3) ["c"]=> ∫(4) } */ ?>
しかし、この方法には後述のような危険性が潜んでいる。
foreach構文におけるリファレンス使用の注意点
以下のように空のループを2回繰り返すと、配列の中身が予想外に変更されてしまう。
<?php $names = array('Nick', 'John', 'Mike'); foreach($names as $key => &$value){ } foreach($names as $key => $value){ } var_dump($names); /* array(3) { [0]=> string(4) "Nick" [1]=> string(4) "John" [2]=> &string(4) "John" } */ ?>
原因
- 1回目のループ:&$valueは配列の各要素の住所(リファレンス)となる。
- 1回目のループ:&$valueは配列の最終要素のリファレンス(&$names[2])となる。
- 2回目のループ:各要素が既にassignされている$value(&$names[2])に代入されていく。
つまり、$names[2] = $valueがループごとに起こる。
- 2回目のループ(1):$names[2]にNickが代入される。
- 2回目のループ(2):$names[2]にJohnが代入される。
- 2回目のループ(3):$names[2]に既に2回目のループ(2)で代入されているJohnにJohnが代入される。
結果、Johnが重複する。それを防ぐ方法は以下のように、unsetを使って変数の割り当てを解除する事である。
<?php $names = array('Nick', 'John', 'Mike'); foreach($names as $key => &$value){ } unset($value) foreach($names as $key => $value){ } var_dump($names); ?>
個人的には度々この手法を使用するが、unsetを書き忘れた場合に極めてデバッグしづらい事も考えられる。従って、この手法を使わないという事も選択肢の1つだ。
■for構文
扱う配列のキーが数値のみであると分かっている場合、以下のようにする事ができる。for構文は配列のコピーを生成しない。
<?php $names = array('Nick', 'John', 'Mike'); for($i = 0; $i < count($names); $i++){ //code } ?>
■配列の要素に対する処理
以下のように、array_walkやarray_walk_recursive(子配列まで再帰的に処理)を使って各要素に任意の処理を加えることができる。
<?php $names = array( array('Nick', 38, 'driver'), array('John', 25, 'programmer'), array('Mike', 22, 'student') ); function setUpper(&$value, &$key){ $value = strtoupper($value); } array_walk_recursive($names, 'setUpper'); var_dump($names); /* array(3) { [0]=> &array(3) { [0]=> string(4) "NICK" [1]=> string(2) "38" [2]=> string(6) "DRIVER" } [1]=> &array(3) { [0]=> string(4) "JOHN" [1]=> string(2) "25" [2]=> string(10) "PROGRAMMER" } [2]=> &array(3) { [0]=> string(4) "MIKE" [1]=> string(2) "22" [2]=> string(7) "STUDENT" } } */ ?>
但し、個人的には関数名を文字列で渡す行為が嫌いなのもあり、イマイチこの方法は使わず単純に(再帰処理する関数を使って)ループさせることが多い。しかも、戻り値としては処理を加えた配列を返さない為、リファレンスを使う必要があり、コードも見づらい。このような点はJavaScriptの方が好きなところだ。
array_combine関数(おまけ)
以下のように、一方の配列をキー、もう一方の配列を値として、新しい配列を生成する。要素数が一致しない場合、エラーが起こる。
<?php $a = array('php', 'js'); $b = array( array('print()', 'array()'), array('document.write()', 'new Array()') ); $c = array_combine($a, $b); var_dump($c); /* array(2) { ["php"]=> array(2) { [0]=> string(7) "print()" [1]=> string(7) "array()" } ["js"]=> array(2) { [0]=> string(16) "document.write()" [1]=> string(11) "new Array()" } } */ ?>
個人的には、この関数の有用性が分からない。PHPは関数が豊富なのが魅力的の一つだが、無駄な関数もあるのではとか思ってしまうような1つである。
各関数について
- bool array_walk(array &$array, string $callbackname[, mixed $user_data])
- 配列の全ての要素にユーザ関数を適用する。ユーザ関数は第一引数に配列の値をとり、第二引数に配列のキーといった形式になる。
- bool array_walk_recursive(array &$array, string $callbackname[, mixed $user_data])
- 配列の全要素にユーザ関数を適用する(配列の)。ユーザ関数は第一引数に配列の値、第二引数に配列のキーといった形式になる。
- array array_combine(array $keys, array $values)
- 一方の配列をキーとし他方を値として配列を生成する。