有没有一种方法可以找出 PHP 数组的“深度”?

62

PHP数组可以有数组作为其元素。而这些数组可以有数组,以此类推。有没有一种方法可以找出PHP数组中存在的最大嵌套层数?一个例子是一个函数,如果初始数组没有数组作为元素,则返回1,如果至少有一个元素是数组,则返回2,依此类推。

20个回答

85

这是另一种避免Kent Fredric指出问题的选择。它将print_r()的任务设置为检查无限递归(其执行得很好),并使用输出的缩进来查找数组的深度。

function array_depth($array) {
    $max_indentation = 1;

    $array_str = print_r($array, true);
    $lines = explode("\n", $array_str);

    foreach ($lines as $line) {
        $indentation = (strlen($line) - strlen(ltrim($line))) / 4;

        if ($indentation > $max_indentation) {
            $max_indentation = $indentation;
        }
    }

    return ceil(($max_indentation - 1) / 2) + 1;
}

31
那其实相当聪明。 - Nathan Strong
3
这个函数应该返回整数而不是浮点数(它来自于 ceil 函数)。 - Bell

79

这应该就可以了:

<?php

function array_depth(array $array) {
    $max_depth = 1;

    foreach ($array as $value) {
        if (is_array($value)) {
            $depth = array_depth($value) + 1;

            if ($depth > $max_depth) {
                $max_depth = $depth;
            }
        }
    }

    return $max_depth;
}

?>

编辑:我快速测试了一下,它似乎可以工作。


48

警惕那些只是用递归来解决问题的示例。

Php可以创建包含对数组中其他位置的引用的数组,并且可以包含具有类似递归引用的对象,任何纯递归算法在这种情况下都可能被认为是危险地幼稚的,因为它将递归超出堆栈深度,永远不会终止。

(好吧,当它超出堆栈深度时,它将终止,此时您的程序将致命终止,这不是我认为您想要的)

过去,我曾尝试使用序列化 -> 替换参考标记与字符串 -> 反序列化来满足我的需求(通常是调试带有大量递归引用的回溯),这似乎还可以,你到处都有洞,但对于那个任务而言效果很好。

对于您的任务,如果您发现数组/结构中出现递归引用,请查看这里的用户贡献评论:

http://php.net/manual/en/language.references.spot.php

然后找到一种计算递归路径深度的方法。

您可能需要找出自己的计算机科学书籍,查看以下文献:

(抱歉言简意赅,但深入研究图论不太适合此格式;))


12

在这里获得一些灵感之后,我在PHP文档中发现了这个RecursiveIteratorIterator的东西,最终得到了这个解决方案。

你应该使用这个,相当不错:

function getArrayDepth($array) {
    $depth = 0;
    $iteIte = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));

    foreach ($iteIte as $ite) {
        $d = $iteIte->getDepth();
        $depth = $d > $depth ? $d : $depth;
    }

    return $depth;
}

适用于PHP5和PHP7,希望这有所帮助。


不幸的是,在测试中,这个函数返回一个少,所以对于深度为5的数组,它返回4 - Martin
1
在编程中从0开始计数是一种正常的行为。如果这让你感到不适,只需将结果加1或将$depth更改为1即可。 - TwystO
此外,该函数无法有效处理空数组或具有键但没有值的数组。 - Martin
当然你的代码不能工作,因为你没有声明任何数组。仔细查看当你点击“执行代码”按钮时返回的警告信息。 - TwystO
1
@rounin 不客气! - TwystO
显示剩余4条评论

9

你好,这是一个替代方案。

/*** IN mixed (any value),OUT (string)maxDepth ***/
/*** Retorna la profundidad maxima de un array ***/
function getArrayMaxDepth($input){
    if( ! canVarLoop($input) ) { return "0"; }
    $arrayiter = new RecursiveArrayIterator($input);
    $iteriter = new RecursiveIteratorIterator($arrayiter);
    foreach ($iteriter as $value) {
            //getDepth() start is 0, I use 0 for not iterable values
            $d = $iteriter->getDepth() + 1;
            $result[] = "$d";
    }
    return max( $result );
}
/*** IN mixed (any value),OUT (bool)true/false, CHECK if can be used by foreach ***/
/*** Revisa si puede ser iterado con foreach ***/
function canVarLoop($input) {
    return (is_array($input) || $input instanceof Traversable) ? true : false;
}

简单易懂,不使用任何技巧,让 PHP 自行处理:http://php.net/RecursiveIteratorIterator.getDepth - SeanDowney

4

当我注意到这篇文章的时候,我刚好回答完这个问题。这是我的解决方案。我没有试过在大量不同的数组大小上运行它,但对于我正在处理的数据(深度> 4,约30个元素),它比2008年的答案更快。

function deepness(array $arr){
    $exploded = explode(',', json_encode($arr, JSON_FORCE_OBJECT)."\n\n");
    $longest = 0;
    foreach($exploded as $row){
        $longest = (substr_count($row, ':')>$longest)?
            substr_count($row, ':'):$longest;
    }
    return $longest;
}

警告:此方法不处理任何边缘案例。如果您需要一个强大的解决方案,请寻找其他方法,但对于简单的情况,我发现这个方法非常快。


请注意,options参数直到php 5.3才被添加,所以如果您需要在5.2版本中使用此答案,您应该将$arr转换为stdClass对象。 - fncomp
1
这种方法存在一个很大的缺陷。如果你的数组文本中有任何冒号,就会产生错误的结果。因此,['x'=>'a:b:c']将返回深度为4。 - Dieter Gribnitz
好的,谢谢指出这一点。我是4年前写的这段代码,已经完全忘记了。顺便说一下,在PHP 4.x下运行非常快,但我不知道现在是否仍然可用。 - fncomp

3

Jeremy Ruten提出了一个更好的修改方案,如下:

function array_depth($array, $childrenkey = "_no_children_")
{
    if (!empty($array[$childrenkey]))
    {
        $array = $array[$childrenkey];
    }

    $max_depth = 1;

    foreach ($array as $value)
    {
        if (is_array($value))
        {
            $depth = array_depth($value, $childrenkey) + 1;

            if ($depth > $max_depth)
            {
                $max_depth = $depth;
            }
        }
    }

    return $max_depth;
}

默认值添加到$childrenkey中,可以使函数适用于没有子元素键的简单数组,即可以适用于简单的多维数组。

现在可以使用以下方式调用此函数:

$my_array_depth = array_depth($my_array, 'the_key_name_storing_child_elements');

或者

$my_array_depth = array_depth($my_array);

$my_array没有任何特定的键来存储其子元素时。


1
使用'&'符号来引用变量,避免复制它们。我认为这将提高你的代码性能。例如:"foreach ($array as &$value)" - Mehran
谢谢您的建议。只是将参数由传值变为传引用(使用“&”)可能需要对代码进行全面修改。例如,在递归调用期间,**$max_depth$depth**变量也应该使用“&”进行传递,并且“return”语句应该被舍弃。这样,代码将与Jeremy Ruten建议的完全不同。:) - Amir Syafrudin
1
上述的两个&符号将防止PHP在每次传递/迭代数组时复制它。当你在foreach中迭代数组时,例如 foreach ($arr as $key => $value),每个提取出来的$value不是原始数组中相同的元素,而是它的副本。但如果你写成foreach ($arr as $key => &$value),则$value将是来自数组的确切元素,改变它将导致更改原始数组。在您的情况下,它将防止PHP复制每个数组元素,从而提高性能。 - Mehran
我同意这很多。我不太理解你的数学符号,但我想我明白了。我正在想象在第一个函数调用(启动递归函数)终止之前,该函数将创建所有子副本。我会记住你的建议。希望它在我的未来项目中有用。谢谢。 - Amir Syafrudin
1
关于使用引用的评论。我可能错了,但我记得几年前在某本书中读到过,php在更改变量之前不会复制它。因此,虽然不会产生大量副本,但它仍将像引用一样运行,直到其中一个变量发生更改。由于没有修改键,只读取,这不应该导致任何性能问题。正如我所说,我可能在这方面是错误的,但如果有人知道这是否正确,请验证一下?如果我找到了关于这个问题的帖子,我会确认这是否正确。 - Dieter Gribnitz
显示剩余4条评论

3
这是我稍作修改后的Jeremy Ruten函数。
// you never know if a future version of PHP will have this in core
if (!function_exists('array_depth')) {
function array_depth($array) {
    // some functions that usually return an array occasionally return false
    if (!is_array($array)) {
        return 0;
    }

    $max_indentation = 1;
    // PHP_EOL in case we're running on Windows
    $lines = explode(PHP_EOL, print_r($array, true));

    foreach ($lines as $line) {
        $indentation = (strlen($line) - strlen(ltrim($line))) / 4;
        $max_indentation = max($max_indentation, $indentation);
    }
    return ceil(($max_indentation - 1) / 2) + 1;
}
}

print array_depth($GLOBALS)这样的内容不会因为递归而出错,但你可能得不到预期的结果。


2

function createDeepArray(){
    static $depth;
    $depth++;
    $a = array();
    if($depth <= 10000){
        $a[] = createDeepArray();
    }
    return $a;
}
$deepArray = createDeepArray();

function deepness(array $arr){
    $exploded = explode(',', json_encode($arr, JSON_FORCE_OBJECT)."\n\n");
    $longest = 0;
    foreach($exploded as $row){
    $longest = (substr_count($row, ':')>$longest)?
        substr_count($row, ':'):$longest;
    }
    return $longest;
}

function array_depth($arr)
{
    if (!is_array($arr)) { return 0; }
    $arr = json_encode($arr);

    $varsum = 0; $depth  = 0;
    for ($i=0;$i<strlen($arr);$i++)
    {
    $varsum += intval($arr[$i] == '[') - intval($arr[$i] == ']');
    if ($varsum > $depth) { $depth = $varsum; }
    }

    return $depth;
}

echo 'deepness():', "\n";

$start_time = microtime(TRUE);
$start_memory = memory_get_usage();
var_dump(deepness($deepArray));
$end_time = microtime(TRUE);
$end_memory = memory_get_usage();
echo 'Memory: ', ($end_memory - $start_memory), "\n";
echo 'Time: ', ($end_time - $start_time), "\n";

echo "\n";
echo 'array_depth():', "\n";

$start_time = microtime(TRUE);
$start_memory = memory_get_usage();
var_dump(array_depth($deepArray));
$end_time = microtime(TRUE);
$end_memory = memory_get_usage();
echo 'Memory: ', ($end_memory - $start_memory), "\n";
echo 'Time: ', ($end_time - $start_time), "\n";

The function proposed by Josh was definitely faster:

$ for i in `seq 1 10`; do php test.php; echo '-------------------------';done
deepness():
int(10000)
Memory: 164
Time: 0.0079939365386963

array_depth():
int(10001)
Memory: 0
Time: 0.043087005615234
-------------------------
deepness():
int(10000)
Memory: 164
Time: 0.0076408386230469

array_depth():
int(10001)
Memory: 0
Time: 0.042832851409912
-------------------------
deepness():
int(10000)
Memory: 164
Time: 0.0080249309539795

array_depth():
int(10001)
Memory: 0
Time: 0.042320966720581
-------------------------
deepness():
int(10000)
Memory: 164
Time: 0.0076301097869873

array_depth():
int(10001)
Memory: 0
Time: 0.041887998580933
-------------------------
deepness():
int(10000)
Memory: 164
Time: 0.0079131126403809

array_depth():
int(10001)
Memory: 0
Time: 0.04217004776001
-------------------------
deepness():
int(10000)
Memory: 164
Time: 0.0078539848327637

array_depth():
int(10001)
Memory: 0
Time: 0.04179310798645
-------------------------
deepness():
int(10000)
Memory: 164
Time: 0.0080208778381348

array_depth():
int(10001)
Memory: 0
Time: 0.04272198677063
-------------------------
deepness():
int(10000)
Memory: 164
Time: 0.0077919960021973

array_depth():
int(10001)
Memory: 0
Time: 0.041619062423706
-------------------------
deepness():
int(10000)
Memory: 164
Time: 0.0080950260162354

array_depth():
int(10001)
Memory: 0
Time: 0.042663097381592
-------------------------
deepness():
int(10000)
Memory: 164
Time: 0.0076849460601807

array_depth():
int(10001)
Memory: 0
Time: 0.042278051376343


2
一个老问题,但仍然与此时相关。 :)
不妨对Jeremy Ruten的答案做出一些小修改。
function array_depth($array, $childrenkey)
{
    $max_depth = 1;

    if (!empty($array[$childrenkey]))
    {
        foreach ($array[$childrenkey] as $value)
        {
            if (is_array($value))
            {
                $depth = array_depth($value, $childrenkey) + 1;

                if ($depth > $max_depth)
                {
                    $max_depth = $depth;
                }
            }
        }
    }

    return $max_depth;
}

我添加了第二个参数 $childrenkey,因为我将子元素存储在特定的键中。

函数调用的示例:

$my_array_depth = array_depth($my_array, 'the_key_name_storing_child_elements');

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