@blog.justoneplanet.info

日々勉強

PHP Array Iteration(配列の繰り返し)

■配列のポインタ

各々の配列は、現在の要素を示すポインタ(イテレータ)を持っている。

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. 1回目のループ:&$valueは配列の各要素の住所(リファレンス)となる。
  2. 1回目のループ:&$valueは配列の最終要素のリファレンス(&$names[2])となる。
  3. 2回目のループ:各要素が既にassignされている$value(&$names[2])に代入されていく。

つまり、$names[2] = $valueがループごとに起こる。

  1. 2回目のループ(1):$names[2]にNickが代入される。
  2. 2回目のループ(2):$names[2]にJohnが代入される。
  3. 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)
一方の配列をキーとし他方を値として配列を生成する。

コメントはまだありません»

No comments yet.

RSS feed for comments on this post.TrackBack URL

Leave a comment