tohokuaikiのチラシの裏

技術的ネタとか。

再帰処理でハマった話

再帰処理を書こうとするといつも悩みます。
「こんなにループさせていいんかいな?」と。

そう思ったのがあだになった例を下記に記します。

親子関係のある配列をソートさせたい場合、下記のコードでは、pidが親IDになります。

で、これを再帰関数を使って親子関係のツリー状にしたかったのです。

<?php
$eto = array(
    array(
        'id' => 1,
        'pid' => 0,
        'title' => '',
        ),
    array(
        'id' => 2,
        'pid' => 4,
        'title' => 'ねずみ',
        ),
    array(
        'id' => 3,
        'pid' => 2,
        'title' => 'うし',
        ),
    array(
        'id' => 4,
        'pid' => 0,
        'title' => 'とら',
        ),
    array(
        'id' => 5,
        'pid' => 4,
        'title' => 'りゅう',
        ),
    array(
        'id' => 6,
        'pid' => 8,
        'title' => 'うさぎ',
        ),
    array(
        'id' => 7,
        'pid' => 1,
        'title' => 'ひつじ',
        ),
    array(
        'id' => 8,
        'pid' => 2,
        'title' => 'うま',
        ),
    array(
        'id' => 9,
        'pid' => 4,
        'title' => 'とり',
        ),
    );

function sortrecursive(&$source, $tree = array(), $pid = 0)
{
    foreach ($source as $k=>$s){
        if ($s['pid'] == $pid){
            $tree[] = $s;
            unset($source[$k]);
            $tree = sortrecursive($source, $tree, $s['id']);
            $depth = 0;
        }
    }
    return $tree;
}

$sorted = sortrecursive($eto);
var_export($sorted);

ところが上記の処理だと、

<?php
array (
  0 =>
  array (
    'id' => 1,
    'pid' => 0,
    'title' => '',
  ),
  1 =>
  array (
    'id' => 7,
    'pid' => 1,
    'title' => 'ひつじ',
  ),
)

だけしかでてきません。!?かなりハマりました。

原因は、sortrecursiveの第一引数を参照渡しにしているためです。これは一回Treeに入ってしまえばそれ以上回す必要がないため、ループから外したいと思ってした処理です。


foreachの途中で参照渡しにしてしまっているため、処理中の配列のポインタまで渡されてしまって、渡された再帰関数の先でforeachで処理される頭の位置がずれてしまっているのでようか?

foreachする前に、reset($source)すればいいんじゃないかと思ったのですが、駄目でした。なんだかよくわかりません。とりあえず、再帰時の参照渡しをやめたらうまくいきました。