11

检查数组是否在 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]);

谢谢

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 回答
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;
  }
于 2015-11-02T17:27:58.440 回答
2

我前段时间对此进行了深入研究,但找不到任何有用的机制来检测 PHP 数组中的递归。

问题归结为是否有可能判断两个 PHP 变量是否是对同一事物的引用。

如果您使用的是对象而不是数组(甚至是数组中的对象),那么这是可能的,因为可以使用spl_object_hash(). 因此,如果您的结构中有对象,那么您可以通过遍历树并比较对象来检测递归。

然而,对于常规变量——即非对象——不可能使用标准 PHP 轻松检测到这一点。

变通方法是使用print_r()(如您所知)或var_dump(),但这些都不是特别优雅的解决方案。

xDebug 还提供了一个可以提供帮助的功能xdebug_debug_zval(),但显然只有在您安装了 xDebug 时才可用,不建议在生产系统上使用。

进一步的意见和建议可在此处获得

于 2013-02-07T13:14:33.670 回答
1

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

这是检查 RECURSION 的函数(来自 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;
    }
于 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,该参数可以设置为COUNT_RECURSIVE递归计数的常量(请参阅文档)。如果一个递归数组被传递给count,它将发出一个警告,我们可以捕获和检查。我已经在我的博客上写了更多关于这个解决方案的内容。测试和基准测试在github 上

为什么大多数解决方案都被破坏了?

任何向数组添加标记然后检查这些标记是否存在的实现都不适用于所有输入。具体来说,在某些情况下,在数组之前已按值分配(例如,由函数返回)的情况下,它们无法检测到递归。这是由于 PHP 处理数组赋值的方式,如PHP Language Specification 的第 4 章所述我在我的博客上对此进行了更广泛的描述。

于 2021-01-20T23:53:20.523 回答