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






Я не думаю, что есть что-то встроенное. Однако простая рекурсивная функция могла бы легко узнать.
Это должно сделать это:
<?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;
}
это ... на самом деле довольно умно.
Эта функция должна возвращать int вместо float (это происходит от ceil).
Вот моя слегка измененная версия функции Джереми Рутена
// 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 - если у вас возникли проблемы с этим постом, не зацикливайте свои жалобы на всем ответе. Вместо этого используйте комментарии.
Я только что придумал ответ на этот вопрос, когда заметил этот пост. Вот мое решение. Я не пробовал это на множестве массивов разного размера, но это было быстрее, чем ответ 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.
В этом подходе есть большой недостаток. Наличие любых двоеточий в тексте вашего массива приведет к ложным срабатываниям. Таким образом, ['x' => 'a: b: c'] вернет глубину 4.
Хорошее замечание, я сделал предупреждение. Я написал это 4 года назад и совершенно забыл об этом. FWIW это было довольно быстро под PHP 4.x. Я понятия не имею, насколько это еще хоть сколько-нибудь разумно.
Я считаю, что проблема, обозначенная Кентом Фредериком, имеет решающее значение. Ответ, предложенный Йеремом и Асимом, уязвим для этой проблемы.
Подходы с использованием отступов, снова предложенные 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 ($ массив как & $ значение)"
Спасибо за предложение. Просто изменение параметра с передачи по значению на передачу по ссылке (с использованием «&») может потребовать общей модификации кода. Например, переменная $ max_depth или $ depth также должна передаваться во время рекурсивного вызова с использованием «&», а оператор «return» должен быть отброшен. Код будет сильно отличаться от того, что предлагал Джереми Рутен. :)
Не совсем, все, что вам нужно сделать, это вставить два амперсанда. Один в операторе foreach (как я упоминал ранее) и еще один перед параметром функции array_depth (& $ array, '. Я считаю, что этого достаточно без каких-либо изменений.
Не думаю, что следую вашей логике. Как добавление двух амперсандов улучшит производительность функции? Пожалуйста, расскажите подробнее по этому поводу. Я особенно не понимаю, как добавить амперсанд внутри foreach.
Два упомянутых амперсанда не позволят PHP копировать массив каждый раз, когда он передается / повторяется. Когда вы повторяете массив в foreach как foreach ($arr as $key => $value), каждый извлеченный $value - это не тот же элемент в исходном массиве, а его копия. Но когда вы пишете foreach ($arr as $key => &$value), $value будет точным элементом из массива, и его изменение приведет к изменению исходного массива. В вашем случае это не позволит PHP копировать каждый элемент массива и, таким образом, повысить производительность.
Спасибо, что не отставали от меня. :) Теперь я понимаю, что вы пытаетесь сказать о избегать копирования массиве $ value. Я часть тех программистов, которые используют амперсанды только в том случае, если я хочу изменить исходную переменную. Я никогда не использовал амперсанды для повышения производительности, как вы предлагали. Однако остается один вопрос: о каком улучшении производительности мы здесь говорим?
Чтобы иметь представление о степени улучшения производительности, вам нужно подумать о том, сколько копий вы избегаете, используя ссылки. В вашем случае это много. В худшем случае для массивов (на самом деле это больше похоже на дерево) с глубиной D и фактором ветвления B вы избежите копирования переменных sum(sum(B^dd | dd=0~d) | d=D~0)!
Я согласен, что это очень много. Я не совсем понимаю ваши математические обозначения, но думаю, что понимаю. Я представляю себе все дочерние копии, которые функция будет создавать до того, как вызов функции первый (который запускает рекурсивную функцию) завершится. Я учту ваше предложение. Надеюсь, это пригодится мне в будущих проектах. Спасибо.
Просто комментарий по использованию ссылок. Я могу ошибаться, но я помню, как пару лет назад читал в какой-то книге, что php не копирует переменную, пока в нее не будут внесены изменения. Таким образом, не должно быть огромного количества копий, но он все равно будет действовать как ссылка, пока одна из переменных не изменится. Поскольку никакие ключи не изменяются и только читаются, это не должно вызывать проблем с производительностью. Как я уже сказал, я могу ошибаться в этом, но если кто-то знает, правильно ли это, вы можете проверить? Если я найду сообщение об этом, я подтвердю, правда ли это.
Привет Это альтернативное решение.
/*** 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
Этот, кажется, мне подходит
<?php
function array_depth(array $array)
{
$depth = 1;
foreach ($array as $value) {
if (is_array($value)) {
$depth += array_depth($value);
break;
}
}
return $depth;
}
Это не очень хорошо, когда один из более поздних дочерних элементов массива является более глубоким массивом, чем один из его предшествующих братьев и сестер.
Немного вдохновившись здесь и обнаружив эту вещь 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;
}
Хм. Похоже, что это будет обычная вещь, которая будет встроена, но я только что во второй раз рассмотрел функции массива, и похоже, что вы правы.