Есть ли способ узнать, насколько «глубоким» является массив PHP?

Массив PHP может иметь массивы для своих элементов. И у этих массивов могут быть массивы и так далее, и так далее. Есть ли способ узнать максимальную вложенность, существующую в массиве PHP? Примером может служить функция, которая возвращает 1, если исходный массив не имеет массивов в качестве элементов, 2, если хотя бы один элемент является массивом, и так далее.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
58
0
46 178
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

Я не думаю, что есть что-то встроенное. Однако простая рекурсивная функция могла бы легко узнать.

Хм. Похоже, что это будет обычная вещь, которая будет встроена, но я только что во второй раз рассмотрел функции массива, и похоже, что вы правы.

Thomas Owens 04.11.2008 21:49
Ответ принят как подходящий

Это должно сделать это:

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

?>

Обновлено: протестировали очень быстро, и, похоже, он работает.

Остерегаться примеров, которые просто делают это рекурсивно.

Php может создавать массивы со ссылками на другие места в этом массиве и может содержать объекты с аналогичными рекурсивными ссылками, и любой чисто рекурсивный алгоритм может считаться в таком случае наивным ОПАСНО, поскольку он будет рекурсивно переполнять глубину стека, и никогда прекратить.

(ну, он завершится, когда он превысит глубину стека, и в этот момент ваша программа завершится фатально, а не то, что я думаю, вы хотите)

Раньше я пробовал сериализовать -> заменять ссылочные маркеры строками -> десериализовать для моих нужд (часто отлаживаю обратные трассы с множеством рекурсивных ссылок в них), что, кажется, работает нормально, вы получаете дыры везде, но это работает для этой задачи .

Для вашей задачи, если вы обнаружите, что в вашем массиве / структуре появляются рекурсивные ссылки, вы можете взглянуть на комментарии, добавленные пользователем здесь: http://php.net/manual/en/language.references.spot.php

а затем каким-то образом найти способ подсчитать глубину рекурсивного пути.

Возможно, вам придется достать свои книги по CS по алгоритмам и пообщаться с этими младенцами:

(Извините за такую ​​краткость, но углубление в теорию графов немного более чем подходит для этого формата;))

Вот еще одна альтернатива, позволяющая избежать проблемы, на которую указал Кент Фредрик. Он дает 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;
}

это ... на самом деле довольно умно.

Nathan Strong 05.11.2008 20:15

Эта функция должна возвращать int вместо float (это происходит от ceil).

Bell 18.11.2015 06:47

Вот моя слегка измененная версия функции Джереми Рутена

// 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), не будут ошибаться из-за рекурсии, но вы можете не получить ожидаемый результат.

// very simple and clean approach        
function array_depth($a) {
          static $depth = 0;
          if (!is_array($a)) {
            return $depth;
          }else{
            $depth++;
            array_map("array_depth", $a);
            return $depth;
          }
        }
print "depth:" . array_depth(array('k9' => 'dog')); // return 1

@ user699082 - если у вас возникли проблемы с этим постом, не зацикливайте свои жалобы на всем ответе. Вместо этого используйте комментарии.

Kev 08.04.2011 21:39

Я только что придумал ответ на этот вопрос, когда заметил этот пост. Вот мое решение. Я не пробовал это на множестве массивов разного размера, но это было быстрее, чем ответ 2008 года для данных, с которыми я работал с ~ 30 частями глубины> 4.

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, поэтому вам следует преобразовать $ arr в объект stdClass, если вам нужно использовать этот ответ с 5.2.

fncomp 29.10.2010 01:06

В этом подходе есть большой недостаток. Наличие любых двоеточий в тексте вашего массива приведет к ложным срабатываниям. Таким образом, ['x' => 'a: b: c'] вернет глубину 4.

Dieter Gribnitz 22.05.2014 16:08

Хорошее замечание, я сделал предупреждение. Я написал это 4 года назад и совершенно забыл об этом. FWIW это было довольно быстро под PHP 4.x. Я понятия не имею, насколько это еще хоть сколько-нибудь разумно.

fncomp 23.05.2014 22:11

Я считаю, что проблема, обозначенная Кентом Фредериком, имеет решающее значение. Ответ, предложенный Йеремом и Асимом, уязвим для этой проблемы.

Подходы с использованием отступов, снова предложенные yjerem и dave1010, для меня недостаточно стабильны, потому что они зависят от количества пробелов, которые представляют отступ с функцией print_r. Это может варьироваться в зависимости от времени / сервера / платформы.

Подход, предложенный JoshN, может быть правильным, но я думаю, что мой быстрее:

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

Отправьте сообщение, если вы проводите какое-либо тестирование, сравнивая разные методы. J

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

Функция, предложенная Джошем, определенно была быстрее:

$ 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

Я полагаю, вы забыли отфильтровать '[' и ']' или ',' и ':' и тип данных ключа (ов) и значения (ов) массива. Вот обновление вашего array_depth плюс бонус array_sort_by_depth.

function array_depth($arr){
if (is_array($arr)) {
    array_walk($arr, 
        function($val, $key) use(&$arr) {
            if ((! is_string($val)) && (! is_array($val))) {
                $val = json_encode($val, JSON_FORCE_OBJECT);
            }

            if (is_string($val)) {
                $arr[$key] = preg_replace('/[:,]+/', '', $val);
            }
        }
    );

    $json_strings = explode(',', json_encode($arr, JSON_FORCE_OBJECT));

    $max_depth = 0;

    foreach ($json_strings as $json_string){
        var_dump($json_string); echo "<br/>";
        $json_string = preg_replace('/[^:]{1}/', '', $json_string);
        var_dump($json_string); echo "<br/><br/>";
        $depth = strlen($json_string);

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

            return $max_depth;
    }

    return FALSE;
    }


    function array_sort_by_depth(&$arr_val, $reverse = FALSE) {

  if ( is_array($arr_val)) { 
    $temp_arr = array();
            $result_arr = array();

            foreach ($arr_val as $key => $val) {
                $temp_arr[$key] = array_depth($val);
            }

        if (is_bool($reverse) && $reverse == TRUE) {
                arsort($temp_arr);
            }
            else {
                asort($temp_arr);
            }

            foreach ($temp_arr as $key => $val) {
                $result_arr[$key] = $arr_val[$key];
            }

            $arr_val = $result_arr;

    return TRUE;
     }

     return FALSE;
  }

Не стесняйтесь улучшать код: D!

Я думаю, что это решит проблему рекурсии, а также даст глубину, не полагаясь на другие функции php, такие как serialize или print_r (что в лучшем случае рискованно и может привести к неразрешимым ошибкам):

function array_depth(&$array) {
    $max_depth = 1;
    $array['__compute_array_depth_flag_ZXCNADJHHDKAQP'] = 1;

    foreach ($array as $value) {
        if (is_array($value) &&
                    !isset($value['__compute_array_depth_flag_ZXCNADJHHDKAQP']))  {
            $depth = array_depth($value) + 1;

            if ($depth > $max_depth) {
                $max_depth = $depth;
            }
        }
    }
    unset($array['__compute_array_depth_flag_ZXCNADJHHDKAQP']);

    return $max_depth;
}

Более быстрый способ:

max(array_map('count', $array));

Старый вопрос, но остающийся актуальным и по сей день. :)

Также можно внести небольшое изменение в ответ Джереми Рутена.

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');

Еще одна (лучшая) модификация функции от Джереми Рутена:

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 не имеет специального ключа для хранения своих дочерних элементов.

Используйте '&' для переменных, чтобы избежать их копирования. Я думаю, это улучшит производительность вашего кода. Например, "foreach ($ массив как & $ значение)"

Mehran 28.10.2012 16:20

Спасибо за предложение. Просто изменение параметра с передачи по значению на передачу по ссылке (с использованием «&») может потребовать общей модификации кода. Например, переменная $ max_depth или $ depth также должна передаваться во время рекурсивного вызова с использованием «&», а оператор «return» должен быть отброшен. Код будет сильно отличаться от того, что предлагал Джереми Рутен. :)

Amir Syafrudin 28.10.2012 19:10

Не совсем, все, что вам нужно сделать, это вставить два амперсанда. Один в операторе foreach (как я упоминал ранее) и еще один перед параметром функции array_depth (& $ array, '. Я считаю, что этого достаточно без каких-либо изменений.

Mehran 28.10.2012 21:19

Не думаю, что следую вашей логике. Как добавление двух амперсандов улучшит производительность функции? Пожалуйста, расскажите подробнее по этому поводу. Я особенно не понимаю, как добавить амперсанд внутри foreach.

Amir Syafrudin 29.10.2012 06:26

Два упомянутых амперсанда не позволят PHP копировать массив каждый раз, когда он передается / повторяется. Когда вы повторяете массив в foreach как foreach ($arr as $key => $value), каждый извлеченный $value - это не тот же элемент в исходном массиве, а его копия. Но когда вы пишете foreach ($arr as $key => &$value), $value будет точным элементом из массива, и его изменение приведет к изменению исходного массива. В вашем случае это не позволит PHP копировать каждый элемент массива и, таким образом, повысить производительность.

Mehran 29.10.2012 18:54

Спасибо, что не отставали от меня. :) Теперь я понимаю, что вы пытаетесь сказать о избегать копирования массиве $ value. Я часть тех программистов, которые используют амперсанды только в том случае, если я хочу изменить исходную переменную. Я никогда не использовал амперсанды для повышения производительности, как вы предлагали. Однако остается один вопрос: о каком улучшении производительности мы здесь говорим?

Amir Syafrudin 30.10.2012 10:56

Чтобы иметь представление о степени улучшения производительности, вам нужно подумать о том, сколько копий вы избегаете, используя ссылки. В вашем случае это много. В худшем случае для массивов (на самом деле это больше похоже на дерево) с глубиной D и фактором ветвления B вы избежите копирования переменных sum(sum(B^dd | dd=0~d) | d=D~0)!

Mehran 02.11.2012 12:24

Я согласен, что это очень много. Я не совсем понимаю ваши математические обозначения, но думаю, что понимаю. Я представляю себе все дочерние копии, которые функция будет создавать до того, как вызов функции первый (который запускает рекурсивную функцию) завершится. Я учту ваше предложение. Надеюсь, это пригодится мне в будущих проектах. Спасибо.

Amir Syafrudin 02.11.2012 15:15

Просто комментарий по использованию ссылок. Я могу ошибаться, но я помню, как пару лет назад читал в какой-то книге, что php не копирует переменную, пока в нее не будут внесены изменения. Таким образом, не должно быть огромного количества копий, но он все равно будет действовать как ссылка, пока одна из переменных не изменится. Поскольку никакие ключи не изменяются и только читаются, это не должно вызывать проблем с производительностью. Как я уже сказал, я могу ошибаться в этом, но если кто-то знает, правильно ли это, вы можете проверить? Если я найду сообщение об этом, я подтвердю, правда ли это.

Dieter Gribnitz 22.05.2014 16:25

Привет Это альтернативное решение.

/*** 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 справляться с этим: php.net/RecursiveIteratorIterator.getDepth

SeanDowney 06.12.2013 00:32

Этот, кажется, мне подходит

<?php
function array_depth(array $array)
{
    $depth = 1;
    foreach ($array as $value) {
        if (is_array($value)) {
            $depth += array_depth($value);
            break;
        }
    }

    return $depth;
}

Это не очень хорошо, когда один из более поздних дочерних элементов массива является более глубоким массивом, чем один из его предшествующих братьев и сестер.

Bramus 28.02.2019 12:30

Немного вдохновившись здесь и обнаружив эту вещь RecursiveIteratorIterator в документации PHP, я пришел к этому решению.

Вы должны использовать этот, довольно аккуратный:

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, надеюсь, это поможет.

Я бы использовал следующий код:

function maxDepth($array) {
    $iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array), \RecursiveIteratorIterator::CHILD_FIRST);
    $iterator->rewind();
    $maxDepth = 0;
    foreach ($iterator as $k => $v) {
        $depth = $iterator->getDepth();
        if ($depth > $maxDepth) {
            $maxDepth = $depth;
        }
    }
    return $maxDepth;
}
//Get the dimension or depth of an array
function array_depth($arr)
{
    if (!is_array($arr)) return 0;
    if (empty($arr))     return 1;
    return max(array_map(__FUNCTION__,$arr))+1;
}

Другие вопросы по теме