Проверка массивов для рекурсии
Каков наилучший способ проверить, является ли массив рекурсивным в 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;
}