【问题标题】:Check arrays for recursion检查数组的递归
【发布时间】:2013-01-22 23:41:59
【问题描述】:

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

给定以下代码:

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

来自PHP Manual:

print_r() 到达第三个时会显示 RECURSION 数组的元素。

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

有没有更优雅的检查方式?

PS。这就是我使用 regex 和 print_r() 检查和获取递归数组键的方式

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

谢谢

【问题讨论】:

  • RECURSION 术语不是仅在它获得 3 个元素深度时才显示,而不是在它引用自身时显示吗?另外,我很确定这是 php.ini 中的可配置深度
  • 我使用 spl_object_hash() 对对象执行此操作,但对于数组我不知道。

标签: php arrays recursion


【解决方案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;
}

See it in action

any 级别检测递归显然会更棘手,但我认为我们可以同意这似乎是可行的。

更新

这里是递归(双关语不是有意的,但仍然令人愉快)的解决方案,可以检测任何级别的递归:

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

See it in action.

当然,这也可以通过手动保留堆栈来编写为使用迭代而不是递归,这在递归级别非常大的情况下会有所帮助。

【讨论】:

【解决方案2】:

以下函数比已接受答案中的代码更简单[意见],并且似乎适用于我能够设计的任何用例。它似乎也快得惊人,通常需要几微秒,尽管我没有做过广泛的基准测试。如果有问题,请高人指点一下?

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

【讨论】:

  • 不错,简单快捷。
【解决方案3】:

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

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

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

但是对于常规变量(即非对象),使用标准 PHP 无法轻松检测到这一点。

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

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

进一步的意见和建议available here.

【讨论】:

    【解决方案4】:

    我相信你无法检查。阅读ReferenceDoc 了解更多参考信息。

    这是检查 RECURSION 的函数(来自 PHP doc cmets),虽然它似乎很慢(我不建议这样做):

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

    【讨论】:

      【解决方案5】:

      您会在 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 以递归计数 (see docs)。如果一个递归数组被传递给count,它将发出一个我们可以捕获和检查的警告。我已经写了更多关于这个解决方案on my blog。测试和基准测试在github

      为什么大多数解决方案都失败了?

      任何将标记添加到数组然后稍后检查这些标记是否存在的实现都不适用于所有输入。具体来说,在某些情况下,如果数组先前已按值分配(例如,由函数返回),它们无法检测到递归。这是由于 PHP 处理数组赋值的方式,如chapter 4 of the PHP Language Specification 中所述。我已经写了更多关于这个on my blog的文章。

      【讨论】:

        猜你喜欢
        • 2021-11-20
        • 1970-01-01
        • 2014-03-26
        • 2020-03-09
        • 2012-11-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-30
        相关资源
        最近更新 更多