11

配列が PHP で再帰的かどうかを確認する最良の方法は何ですか?

次のコードがあるとします。

<?php 
$myarray = array('test',123); 
$myarray[] = &$myarray; 
print_r($myarray); 
?> 

PHPマニュアルから:

print_r() は、配列の 3 番目の要素に到達するとRECURSIONを表示します。

再帰参照のために配列をスキャンする他の方法はないようです。そのため、再帰参照を確認する必要がある場合は、2 番目のパラメーターを指定して print_r() を使用して出力をキャプチャし、RECURSIONという単語を探す必要があります。 .

よりエレガントなチェック方法はありますか?

PS。これは、正規表現とprint_r()を使用して再帰配列キーをチェックして取得する方法です

$pattern = '/\n            \[(\w+)\] => Array\s+\*RECURSION\*/';
preg_match_all($pattern, print_r($value, TRUE), $matches);
$recursiveKeys =  array_unique($matches[1]);

ありがとう

4

5 に答える 5

6

「無理」な問題を解いてみるのはいつだって楽しい!

再帰が最上位で発生した場合に再帰配列を検出する関数を次に示します。

function is_recursive(array &$array) {
    static $uniqueObject;
    if (!$uniqueObject) {
        $uniqueObject = new stdClass;
    }

    foreach ($array as &$item) {
        if (!is_array($item)) {
            continue;
        }

        $item[] = $uniqueObject;
        $isRecursive = end($array) === $uniqueObject;
        array_pop($item);
        if ($isRecursive) {
            return true;
        }
    }

    return false;
}

実際に見てください

任意のレベルで再帰を検出するのは明らかに難しいですが、それが実行可能であることに同意できると思います。

アップデート

そして、任意のレベルで再帰を検出する再帰的 (しゃれは意図していませんが、それでも楽しい) ソリューションは次のとおりです。

function is_recursive(array &$array, array &$alreadySeen = array()) {
    static $uniqueObject;
    if (!$uniqueObject) {
        $uniqueObject = new stdClass;
    }

    $alreadySeen[] = &$array;

    foreach ($array as &$item) {
        if (!is_array($item)) {
            continue;
        }

        $item[] = $uniqueObject;
        $recursionDetected = false;
        foreach ($alreadySeen as $candidate) {
            if (end($candidate) === $uniqueObject) {
                $recursionDetected = true;
                break;
            }
        }

        array_pop($item);

        if ($recursionDetected || is_recursive($item, $alreadySeen)) {
            return true;
        }
    }

    return false;
}

実際に見てください

もちろん、スタックを手動で保持することにより、再帰ではなく反復で動作するようにこれを記述することもできます。これは、非常に大きな再帰レベルが問題になる場合に役立ちます。

于 2013-02-07T13:25:01.543 に答える
2

しばらく前にこれを深く掘り下げましたが、PHP 配列の再帰を検出するための有用なメカニズムを見つけることができませんでした。

問題は、2 つの PHP 変数が同じものへの参照であるかどうかを判断できるかどうかということです。

配列ではなくオブジェクト (または配列内のオブジェクト) を操作している場合は、 を使用して 2 つのオブジェクトが同じ参照であるかどうかを確認できるため、可能spl_object_hash()です。したがって、構造内にオブジェクトがある場合は、ツリーをたどってオブジェクトを比較することで再帰を検出できます。

ただし、通常の変数 (つまり、非オブジェクト) の場合、標準の PHP を使用してこれを簡単に検出することはできません。

回避策はprint_r()(既にご存じのとおり) またはを使用することですvar_dump()が、どちらも特に洗練されたソリューションではありません。

また、xDebug が提供する機能も役立ちますがxdebug_debug_zval()、これは明らかに xDebug がインストールされている場合にのみ利用可能であり、運用システムでは推奨されません。

詳細なアドバイスと提案はこちらから入手できます

于 2013-02-07T13:14:33.670 に答える
1

確認できないと思います。参照の詳細については、参照ドキュメントを参照してください。

RECURSION をチェックする関数を次に示します (PHP doc コメントから)。ただし、非常に遅いようです (お勧めしません)。

  function is_array_reference ($arr, $key) {
        $isRef = false;
        ob_start();
        var_dump($arr);
        if (strpos(preg_replace("/[ \n\r]*/i", "", preg_replace("/( ){4,}.*(\n\r)*/i", "", ob_get_contents())), "[" . $key . "]=>&") !== false)
            $isRef = true;
        ob_end_clean();
        return $isRef;
    }
于 2013-02-07T13:06:20.777 に答える
0

SO で見つかる解決策の多くは壊れています (以下の説明を参照)。私が提案する関数は、すべての配列に対して機能し、以下よりもはるかに効率的ですprint_r

function is_cyclic(&$array) {
    $isRecursive = false;
    set_error_handler(function ($errno, $errstr) use (&$isRecursive) {
        $isRecursive = $errno === E_WARNING && mb_stripos($errstr, 'recursion');
    });
    try {
        count($array, COUNT_RECURSIVE);    
    } finally {
        restore_error_handler();
    }
    return $isRecursive;
}

このcount関数は、再帰的にカウント$modeする定数に設定できる 2 番目のパラメーターを取ります ( docs を参照)。再帰配列が に渡されると、トラップしてチェックできる警告が出力されます。このソリューションについては、ブログで詳しく説明しています。テストとベンチマークはgithubにあります。COUNT_RECURSIVEcount

ほとんどのソリューションが壊れているのはなぜですか?

マーカーを配列に追加し、後でこれらのマーカーの存在をチェックする実装は、すべての入力に対して機能するとは限りません。具体的には、配列が以前に値によって割り当てられている場合 (たとえば、関数によって返された場合) に再帰を検出できません。これは、 PHP 言語仕様 の第 4 章で説明されているように、PHP が配列の値の割り当てを処理する方法によるものです。これについては、ブログで詳しく説明しています。

于 2021-01-20T23:53:20.523 に答える