再帰の伴わない書き替え
問題
一次元配列$a
の要素の値に全て1を加えたものを$b
に代入せよ。
入出力例
[1, 3, 2, 8, 99]
[2, 4, 3, 9, 100]
解答例
一つずつ新しい配列に代入する
誰もが思いつくforeach
構文を用いる方法です。
$b = [];
foreach ($a as $key => $value) {
$b[$key] = $value + 1;
}
この用途ではarray_walk関数を使うメリットはいまいち無さそう…
$b = [];
array_walk($a, function ($value, $key) use (&$b) {
$b[$key] = $value + 1;
});
全体をコピーした後、1つずつ書き替える
PHPのforeach
構文にはブロックスコープは存在しないため、ループを抜けた後に自分でunsetを実行しないと最後の要素に対するリファレンスがずっと続いてしまいます。
$b = $a;
foreach ($b as &$value) {
++$value;
}
unset($value);
最後に外側でunsetを呼ぶのが不恰好だと感じる場合、array_walk関数で解決です。
$b = $a;
array_walk($b, function (&$value) {
++$value;
});
コールバック関数を適用する
関数脳の人ならこれ一択でしょう。私も大好きです。
$b = array_map(function ($value) {
return $value + 1;
}, $a);
再帰の伴う書き替え
問題
任意の多次元配列$a
の葉要素の値に全て1を加えたものを$b
に代入せよ。
入出力例
[[1], [[3]], [[[2]]], [8], 99]
[[2], [[4]], [[[3]]], [9], 100]
解答例
is_array関数での判定を交えて手作業で再帰させる解答は省略します。ここではPHPの機能によって再帰させる方法のみを紹介します。
全体をコピーした後、1つずつ書き替える
array_walk関数はarray_walk_recursive関数の存在への布石としての意味合いが強いです。前者には 「それforeach
で良くね?」 という疑問が沸きそうな感じはありましたが、後者には明確な再帰という目的があります。
$b = $a;
array_walk_recursive($b, function (&$value) {
++$value;
});
コールバック関数を適用する
「array_map_recursive
とかねーじゃん!?自分で作らないといけないんじゃないの?」 と思ったそこのあなた。実は…
**filter_var**関数の出番なんです。
物凄い種類の「何か」を識別する最強の呪文 "filter_var"
— NaOHaq(仮性ソーダ) (@NaOHaq) 2015, 2月 12
まさかこんなところに出現するとは…
$b = filter_var($a, FILTER_CALLBACK, ['options' => function ($value) {
return $value + 1;
}]);
これを紹介するためだけにこの記事を書きました(笑
重要な追記
上記の例では+1
しているため問題ありませんでしたが,$value
として渡されてくる値は全てオプション無しのfilter_var
を個別に通したような値になっています.すなわち,string
にキャストされるかfalse
になっています.
平坦化
問題
任意の多次元配列$a
を平坦化して$b
に代入せよ。キーは0
からの連番整数を振りなおすものとする。
入出力例
[[1], [[3]], [[[2]]], [8], 99]
[1, 3, 2, 8, 99]
解答例
is_array関数での判定を交えて手作業で再帰させる解答は省略します。ここではPHPの機能によって再帰させる方法のみを紹介します。
一つずつ新しい配列に代入する
array_walk_recursive関数を使う方法が一番理解しやすいと思います。もちろんfilter_varでも実現することは出来ますが、今回はその返り値は使わずに逐次代入するだけなので、わざわざfilter_varを使う必要は無いでしょう。
$b = [];
array_walk_recursive($a, function ($value) use (&$b) {
$b[] = $value;
});
但し、以下のような目的がある場合はfilter_varのほうは配列もスカラー値も一貫して処理したい (配列でラップする必要がない)
再帰イテレータを用いる
RecursiveArrayIteratorクラスとRecursiveIteratorIteratorクラスを用いて再帰イテレータを作成した後、それをiterator_to_array関数を用いて配列に変換します。
$b = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($a)), false);
ちなみにこれらに関しても非再帰版のArrayIteratorクラスとIteratorIteratorクラスが再帰版への布石として存在します。