Python 了解字典的深度
假设我们有这样一句话:Python 了解字典的深度,python,dictionary,depth,Python,Dictionary,Depth,假设我们有这样一句话: d = {'a':1, 'b': {'c':{}}} 知道它的嵌套深度最直接的方法是什么?您必须遍历字典。你可以通过排队来做到这一点;以下内容应避免循环引用: from collections import deque def depth(d): queue = deque([(id(d), d, 1)]) memo = set() while queue: id_, o, level = queue.popleft()
d = {'a':1, 'b': {'c':{}}}
知道它的嵌套深度最直接的方法是什么?您必须遍历字典。你可以通过排队来做到这一点;以下内容应避免循环引用:
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
请注意,因为我们以第一顺序访问所有字典值,级别
值只会上升。memo
集合用于确保我们不会无休止地遍历循环引用
或者可以使用递归遍历树(它有效地将函数调用用作堆栈)。我已经使用了便于扩展到其他容器类型的功能:
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 is not None else _orig(_protect(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
集都跟踪对象ID,因此如果执行类似foo={}的操作,我们不会运行is circles;foo[“bar”]=foo
演示:
递归singledispatch
版本可以扩展以覆盖更多容器,例如列表:
@depth.register
def _list_depth(l: list, _level=1, **kw):
return max(depth(v, _level=_level + 1, **kw) for v in l)
因为我已经扩展了标准的.register
装饰程序来处理循环引用测试,所以实现额外的容器支持相对来说比较简单。只需记住将任何额外的关键字参数传递给递归调用 您需要创建一个递归函数:
>>> def depth(d):
... if isinstance(d, dict):
... return 1 + (max(map(depth, d.values())) if d else 0)
... return 0
...
>>> d = {'a':1, 'b': {'c':{}}}
>>> depth(d)
3
非递归解决方案:
def depth(d):
depth=0
q = [(i, depth+1) for i in d.values() if isinstance(i, dict)]
max_depth = 0
while (q):
n, depth = q.pop()
max_depth = max(max_depth, depth)
q = q + [(i, depth+1) for i in n.values() if isinstance(i, dict)]
print max_depth
from collections import deque
def depth(d):
q = deque([d])
q2 = deque()
max_depth = 0
while q:
curr_dict = q.popleft()
if isinstance(curr_dict, dict):
for di in curr_dict.itervalues():
q2.append(di)
if not q:
q, q2 = q2, q
max_depth += 1
return max_depth
print depth(None)
print depth({})
print depth({"a": "b"})
print depth({"a": "b", "c": {"d": "e"}, "f": {"g": "h"}, "i": {"j": "k"}, "x": {}, "z": {} })
print depth({'a':1, 'b': {'c':{}}})
print depth({'foo': {'bar': {'baz': 0}, 'spam': {'ham': {'monty': 1}, 'eric': 'idle'}}, 'john': 'cleese'})
迭代解决方案:
def depth(d):
depth=0
q = [(i, depth+1) for i in d.values() if isinstance(i, dict)]
max_depth = 0
while (q):
n, depth = q.pop()
max_depth = max(max_depth, depth)
q = q + [(i, depth+1) for i in n.values() if isinstance(i, dict)]
print max_depth
from collections import deque
def depth(d):
q = deque([d])
q2 = deque()
max_depth = 0
while q:
curr_dict = q.popleft()
if isinstance(curr_dict, dict):
for di in curr_dict.itervalues():
q2.append(di)
if not q:
q, q2 = q2, q
max_depth += 1
return max_depth
print depth(None)
print depth({})
print depth({"a": "b"})
print depth({"a": "b", "c": {"d": "e"}, "f": {"g": "h"}, "i": {"j": "k"}, "x": {}, "z": {} })
print depth({'a':1, 'b': {'c':{}}})
print depth({'foo': {'bar': {'baz': 0}, 'spam': {'ham': {'monty': 1}, 'eric': 'idle'}}, 'john': 'cleese'})
它可能会分支,或者每层只有一个键?您担心的只是嵌套的dict,或者dict的值也可能是其他容器吗?对于您给出的示例,我会说(最直接的)答案是查看它。另外,我不敢相信这不是一个重复的+1的可能的重复(但它似乎检查了!)一个警告:应该无和{}返回1吗?但这是一个惯例问题。@lukas:技术可以调整;重点更在于说明需要做些什么。这里的关键点是遍历(在深度优先时使用'max()),我想说。level的默认值应该是0,而不是1。一个简单的dict返回2作为深度,这是不正确的。同样对于无和空的dicts,深度应该是0而不是1。@samyarus:这就是所有的解释;如果顶级字典为空怎么办?深度是0还是1?字典已经得到值这一事实可以看作是另一个层面。空字典->1,带值字典->2(字典本身需要一个级别的引用,值需要另一个级别的引用)。@Samyarus:您可以通过将level
的起始值调整为0,并为非字典值返回level
,为空字典返回level+1
来获得解释。但是请注意,OP没有指定深度的计算方式,所以我们都可以自由地进行自己的解释。为什么在这里使用两个队列?向右追加和向左弹出的本质意味着单个队列的行为完全相同。@MartijnPieters为了计算最大值,不需要第二个队列。如果您想打印每个级别及其最大深度,这将非常有用。不过,您也不需要使用两个队列。只需在队列中包含一个深度,并在处理队列时打印新值(例如,当下一个级别高于上一个级别时)。非常优雅的解决方案如果需要,那么为什么是
呢?@baxx,如果字典为空(d=={}
d
是falsy>)max(…)
针对空iterable的调用导致ValueError<代码>。。。如果d else..
用于防止出现这种情况。试试max([])
@baxx,如果你使用Python 3.4+(其中default
关键字参数添加到max
),你可以使用max(map(depth,d.values()),default=0)
。啊,好的,我想我试过了,没有空字典,谢谢你提供的信息