Проверка массивов для рекурсии

Каков наилучший способ проверить, является ли массив рекурсивным в PHP?

С учетом следующего кода:

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

Из Руководство по PHP:

В print_r() отобразится RECURSION, когда он попадет на третий элемент массива.

Кажется, нет другого способа сканирования массива для рекурсивные ссылки, поэтому, если вам нужно их проверить, вам придется используйте print_r() со своим вторым параметром для захвата вывода и просмотра для слова RECURSION.

Есть ли более элегантный способ проверки?

PS. Вот как я проверяю и получаю рекурсивные ключи массива с помощью regex и print_r()

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

Спасибо

Ответ 1

Всегда интересно попробовать "невозможные" проблемы!

Здесь функция, которая будет обнаруживать рекурсивные массивы, если рекурсия происходит на верхнем уровне:

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;
}

Посмотрите на действие.

Обнаружение рекурсии на любом уровне, очевидно, будет более сложным, но я думаю, мы можем согласиться с тем, что это кажется выполнимым.

Update

И вот рекурсивное (каламбур не предназначенное, но приятное, тем не менее) решение, которое обнаруживает рекурсию на любом уровне:

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;
}

Посмотрите на действие.

Конечно, это также можно записать для работы с итерацией вместо рекурсии, сохраняя стек вручную, что поможет в тех случаях, когда проблема с очень большим уровнем рекурсии.

Ответ 2

Я вникнул в это подробно некоторое время назад, и мне не удалось найти какой-либо полезный механизм для обнаружения рекурсии в массивах PHP.

Вопрос сводится к тому, можно ли сказать, являются ли две переменные PHP ссылками на одну и ту же вещь.

Если вы работаете с объектами, а не с массивами (или даже с объектами внутри ваших массивов), тогда это возможно, так как можно узнать, являются ли два объекта одной и той же ссылкой с помощью spl_object_hash(). Поэтому, если у вас есть объекты в вашей структуре, вы можете обнаружить рекурсию, пройдя по дереву и сравнивая объекты.

Однако для обычных переменных - то есть не-объектов - это невозможно легко обнаружить с помощью стандартного PHP.

В работе используются print_r() (как вы уже знаете) или var_dump(), но ни одно из них не является особенно элегантным решением.

Существует также функция, предоставляемая xDebug, которая может помочь, xdebug_debug_zval(), но которая, очевидно, доступна только в том случае, если вы установили xDebug, что не рекомендуется в производственной системе.

Дополнительные советы и предложения доступны здесь.

Ответ 3

Следующая функция проще [мнение], чем код в принятом ответе, и, похоже, работает для любого случая использования, которое я смог выдумать. Это также кажется удивительно быстрым, как правило, с микросекундами, хотя я не провел обширного бенчмаркинга. Если есть проблема с этим, я был бы признателен, если бы кто-нибудь мог указать на это?

// returns TRUE iff the passed object or array contains
// a self-referencing object or array
function is_r($obj, &$visited=array())
  {
  $visited[] = $obj;
  foreach ($obj as $el)
    {
    if (is_object($el) || is_array($el))
      {
      if (in_array($el, $visited, TRUE))
        return TRUE;
      if (is_r($el, $visited))
        return TRUE;
      }
    }
  return FALSE;
  }

Ответ 4

Я считаю, что вы не можете это проверить. Подробнее читайте Ссылка Doc.

Вот функция для проверки 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;
    }