检查数组是否存在递归

11

在PHP中,检查数组是否递归的最佳方法是什么?

给定以下代码:

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

当print_r()函数到达数组的第三个元素时,将显示RECURSION

似乎没有其他方法可以扫描具有递归引用的数组,所以如果你需要检查它们,你必须使用print_r()和它的第二个参数来捕获输出并查找单词RECURSION

有更优雅的检查方式吗?

附:这是我使用正则表达式和print_r()检查和获取递归数组键的方法。

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

谢谢


1
“_RECURSION_”这个术语不是在引用自身时才显示,而是在深度达到3个元素时才显示。此外,我相信在php.ini中可以配置这个深度。 - JamesHalsall
我用 spl_object_hash() 函数处理了对象,但对于数组,我毫无头绪。 - Hugo Mota
6个回答

7

尝试解决“不可能”的问题总是很有趣!

这里有一个函数,可以检测递归数组是否在顶层发生:

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

在这里查看实际效果.

当然,也可以手动保留一个栈以便迭代方式实现,这样有助于解决递归层数过多的问题。


不适用于所有数组,请参见https://3v4l.org/V4K6r。 - hBGl

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

不错,简单快捷。 - Bas

2
我曾经深入研究过这个问题,但是我无法找到任何有用的机制来检测 PHP 数组中的递归。
问题归结为是否可能确定两个 PHP 变量是否引用相同的内容。
如果您使用的是对象而不是数组(甚至在您的数组中使用对象),那么就可以通过使用 spl_object_hash() 找出两个对象是否是相同的引用。因此,如果您的结构中有对象,则可以通过遍历树并比较对象来检测递归。
然而,对于常规变量 - 即非对象 - 使用标准的 PHP 很难检测到这一点。
解决方法是使用 print_r()(正如您已经知道的那样)或 var_dump(),但这两种方法都不是特别优雅的解决方案。
xDebug 还提供了一个函数可以帮助,即 xdebug_debug_zval(),但这显然只有在安装了 xDebug 的情况下才可用,这在生产系统上是不推荐的。
更多建议和建议 请参见此处

1

我相信你无法检查这个。阅读参考资料文档以获取有关引用的更多信息。

以下是用于检查递归的函数(来自PHP文档注释),尽管它似乎非常慢(我不建议使用):

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

0

你会在 Stack Overflow 上找到很多解决方案是有问题的(请参见下面的说明)。我提出的函数适用于所有数组,并且比 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,可以设置为常量COUNT_RECURSIVE以进行递归计数(请参阅文档)。如果传递给count的是一个递归数组,它将发出一个警告,我们可以捕获并检查。我在我的博客中写了更多关于这个解决方案的内容。测试和基准测试在github上。

为什么大多数解决方案都是错误的?

任何将标记添加到数组中,然后稍后检查这些标记是否存在的实现都无法适用于所有输入。具体来说,在某些情况下,它们无法检测到递归,其中数组先前已通过值分配(例如由函数返回)。这是由于PHP处理数组的值分配方式所致,如《PHP语言规范》第4章所述。我在我的博客上对此进行了更详细的阐述。


0

我认为检查递归最快、最简单的方法是使用简单查询

if(array(($variable['variable_name'])==('*RECURSION*')))
{
    return true;
}
else
{
    return false;
}

为了消除与变量相关的 PHP 错误,您可以使用 is_ 函数 if((is_array(($variable['variable_name']))==('*RECURSION*'))){return true;}else{return false;} - pacanosiu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接