Ответ 1
Вам придется пройти через словарь. Вы можете сделать это с помощью очереди; следующее должно быть защищено от циклических ссылок:
from collections import deque
def depth(d):
queue = deque([(id(d), d, 1)])
memo = set()
while queue:
id_, o, level = queue.popleft()
if id_ in memo:
continue
memo.add(id_)
if isinstance(o, dict):
queue += ((id(v), v, level + 1) for v in o.values())
return level
Обратите внимание, что, поскольку мы обращаемся ко всем значениям словаря в порядке дыхания, значение level
только увеличивается. Набор memo
используется для того, чтобы мы не пытались бесконечно обходить круговую ссылку.
Или вы можете пройти по дереву с помощью рекурсии (которая эффективно использует вызовы функций в качестве стека). Я использовал functools.singledispatch()
для простого расширения на другие типы контейнеров:
from functools import singledispatch, wraps
@singledispatch
def depth(_, _level=1, _memo=None):
return _level
def _protect(f):
"""Protect against circular references"""
@wraps(f)
def wrapper(o, _level=1, _memo=None, **kwargs):
_memo, id_ = _memo or set(), id(o)
if id_ in _memo: return _level
_memo.add(id_)
return f(o, _level=_level, _memo=_memo, **kwargs)
return wrapper
def _protected_register(cls, func=None, _orig=depth.register):
"""Include the _protect decorator when registering"""
if func is None and isinstance(cls, type):
return lambda f: _orig(cls, _protect(f))
return _orig(cls, _protect(func)) if func else _protect(_orig(cls))
depth.register = _protected_register
@depth.register
def _dict_depth(d: dict, _level=1, **kw):
return max(depth(v, _level=_level + 1, **kw) for v in d.values())
Это как поиск в глубину, так что теперь max()
необходим, чтобы выбрать наибольшую глубину для текущего объекта на каждом уровне. Словарь с 3 ключами каждой различной глубины должен отражать наибольшую глубину на этом уровне.
Набор memo
используемый в любой из версий, отслеживает идентификаторы объектов, поэтому мы не запускаем круги, если вы сделали что-то вроде foo = {}; foo["bar"] = foo
foo = {}; foo["bar"] = foo
.
Демо-версия:
>>> d = {'a':1, 'b': {'c':{}}}
>>> depth(d)
3
>>> d = {'foo': {'bar': {'baz': 0}, 'spam': {'ham': {'monty': 1}, 'eric': 'idle'}}, 'john': 'cleese'}
>>> depth(d)
5
>>> circular = {}
>>> circular["self"] = circular
>>> depth(circular)
2
Рекурсивная версия singledispatch
может быть расширена, чтобы охватить больше контейнеров, таких как списки:
@depth.register
def _list_depth(l: list, _level=1, **kw):
return max(depth(v, _level=_level + 1, **kw) for v in l)
Поскольку я дополнил стандартный декоратор .register
для обработки циклических ссылок, реализовать дополнительную поддержку контейнеров относительно тривиально. Просто не забудьте передать дополнительные аргументы ключевого слова в рекурсивный вызов!