defaultdict 的嵌套 defaultdict
- 2024-12-19 09:24:00
- admin 原创
- 88
问题描述:
有没有办法让 defaultdict 也成为 defaultdict 的默认值?(即无限级递归 defaultdict?)
我希望能够做到:
x = defaultdict(...stuff...)
x[0][1][0]
{}
因此,我可以这样做x = defaultdict(defaultdict)
,但这只是第二个级别:
x[0]
{}
x[0][0]
KeyError: 0
有一些方法可以做到这一点。但是,仅仅使用普通的 defaultdict 参数就可以做到这一点吗?
请注意,这是在询问如何执行无限级递归 defaultdict,因此它与Python:defaultdict of defaultdict?不同,后者是如何执行两级 defaultdict。
我可能最终会使用一束图案,但当我意识到我不知道如何做到这一点时,我就产生了兴趣。
解决方案 1:
这里的其他答案告诉您如何创建defaultdict
包含“无限多个”的defaultdict
,但它们未能解决我认为可能是您最初的需求,即简单地拥有一个两深度默认字典。
您可能一直在寻找:
defaultdict(lambda: defaultdict(dict))
您可能更喜欢这种结构的原因是:
它比递归解更明确,因此读者可能更容易理解。
这使得的“叶子”
defaultdict
可以是字典之外的东西,例如:defaultdict(lambda: defaultdict(list))
或defaultdict(lambda: defaultdict(set))
解决方案 2:
对于任意数量的级别:
def rec_dd():
return defaultdict(rec_dd)
>>> x = rec_dd()
>>> x['a']['b']['c']['d']
defaultdict(<function rec_dd at 0x7f0dcef81500>, {})
>>> print json.dumps(x)
{"a": {"b": {"c": {"d": {}}}}}
当然你也可以用 lambda 来实现这一点,但我发现 lambda 的可读性较差。无论如何它看起来应该是这样的:
rec_dd = lambda: defaultdict(rec_dd)
解决方案 3:
有一个巧妙的技巧可以做到这一点:
tree = lambda: defaultdict(tree)
然后您就可以用 创建您的x
了x = tree()
。
解决方案 4:
与 BrenBarn 的解决方案类似,但不包含两次变量的名称tree
,因此即使在变量字典发生变化后它仍然有效:
tree = (lambda f: f(f))(lambda a: (lambda: defaultdict(a(a))))
然后您可以x
使用创建每个新的x = tree()
。
对于该def
版本,我们可以使用函数闭包作用域来保护数据结构免受名称重新绑定时现有实例停止工作的缺陷的影响tree
。它看起来像这样:
from collections import defaultdict
def tree():
def the_tree():
return defaultdict(the_tree)
return the_tree()
解决方案 5:
我还建议采用更多 OOP 风格的实现,它支持无限嵌套以及正确格式的repr
。
class NestedDefaultDict(defaultdict):
def __init__(self, *args, **kwargs):
super(NestedDefaultDict, self).__init__(NestedDefaultDict, *args, **kwargs)
def __repr__(self):
return repr(dict(self))
用法:
my_dict = NestedDefaultDict()
my_dict['a']['b'] = 1
my_dict['a']['c']['d'] = 2
my_dict['b']
print(my_dict) # {'a': {'b': 1, 'c': {'d': 2}}, 'b': {}}
解决方案 6:
这是一个递归函数,用于将递归默认字典转换为普通字典
def defdict_to_dict(defdict, finaldict):
# pass in an empty dict for finaldict
for k, v in defdict.items():
if isinstance(v, defaultdict):
# new level created and that is the new value
finaldict[k] = defdict_to_dict(v, {})
else:
finaldict[k] = v
return finaldict
defdict_to_dict(my_rec_default_dict, {})
解决方案 7:
我根据 Andrew 在此处的回答得出此结论。如果您希望将 json 或现有字典中的数据加载到 nester defaultdict 中,请参见以下示例:
def nested_defaultdict(existing=None, **kwargs):
if existing is None:
existing = {}
if not isinstance(existing, dict):
return existing
existing = {key: nested_defaultdict(val) for key, val in existing.items()}
return defaultdict(nested_defaultdict, existing, **kwargs)
https://gist.github.com/nucklehead/2d29628bb49115f3c30e78c071207775
解决方案 8:
这是一个用于任意深度嵌套的任意基本默认字典的函数。
(从Can't pickle defaultdict交叉发布)
def wrap_defaultdict(instance, times=1):
"""Wrap an instance an arbitrary number of `times` to create nested defaultdict.
Parameters
----------
instance - list, dict, int, collections.Counter
times - the number of nested keys above `instance`; if `times=3` dd[one][two][three] = instance
Notes
-----
using `x.copy` allows pickling (loading to ipyparallel cluster or pkldump)
- thanks https://stackoverflow.com/questions/16439301/cant-pickle-defaultdict
"""
from collections import defaultdict
def _dd(x):
return defaultdict(x.copy)
dd = defaultdict(instance)
for i in range(times-1):
dd = _dd(dd)
return dd
解决方案 9:
@nucklehead 的回复也可以扩展以处理 JSON 中的数组:
def nested_dict(existing=None, **kwargs):
if existing is None:
existing = defaultdict()
if isinstance(existing, list):
existing = [nested_dict(val) for val in existing]
if not isinstance(existing, dict):
return existing
existing = {key: nested_dict(val) for key, val in existing.items()}
return defaultdict(nested_dict, existing, **kwargs)
解决方案 10:
但是,根据 Chris W 的回答,为了解决类型注释问题,您可以将其设为定义详细类型的工厂函数。例如,这是我在研究此问题时遇到的问题的最终解决方案:
def frequency_map_factory() -> dict[str, dict[str, int]]:
"""
Provides a recorder of: per X:str, frequency of Y:str occurrences.
"""
return defaultdict(lambda: defaultdict(int))
解决方案 11:
这是一个类似于@Stanislav 的答案的解决方案,它可以用于多处理并且还允许终止嵌套:
from collections import defaultdict
from functools import partial
class NestedDD(defaultdict):
def __init__(self, n, *args, **kwargs):
self.n = n
factory = partial(build_nested_dd, n=n - 1) if n > 1 else int
super().__init__(factory, *args, **kwargs)
def __repr__(self):
return repr(dict(self))
def build_nested_dd(n):
return NestedDD(n)
解决方案 12:
这是一个类似于@Chris W. 的解决方案,它使更多级别成为可能。它仍然允许将“叶子”指定为 defaultdict 以外的其他内容。
定义的不是 lambda,而是闭包。
您可能更喜欢这种方法,因为
嵌套 defaultdict 的声明被写为嵌套函数,因此可能更容易阅读。
可以实现超过两个级别。
最后一片叶子可以是:列表,集合,......
这是一个例子。
from collections import defaultdict
import json
def another_defaultdict(factory):
'return another layer of defaultdict as a factory function'
def layer():
return defaultdict(factory)
return layer
>>> # two levels
>>> d = defaultdict(another_defaultdict(list))
>>> # three levels
>>> d = defaultdict(another_defaultdict(another_defaultdict(list)))
>>> d['Canada']['Alberta'] = ['Calgary', 'Magrath', 'Cardston', 'Lethbridge']
>>> d['France']['Nord'] = ['Dunkirk', 'Croix']
>>> print(json.dumps(d, indent=2))
{
"Canada": {
"Alberta": [
"Calgary",
"Magrath",
"Cardston",
"Lethbridge"
]
},
"France": {
"Nord": [
"Dunkirk",
"Croix"
]
}
}
解决方案 13:
这是一个defaultdict
完全避免的解决方案。
class InfiniteDefaultDict(dict):
def __getitem__(self, key):
if key in self:
value = super().__getitem__(key)
else:
value = InfiniteDefaultDict()
super().__setitem__(key, value)
return value
In [1]: d = InfiniteDefaultDict()
In [2]: d['a']['b']['c']['d']['e'] = 1
In [3]: d
Out[4]: {'a': {'b': {'c': {'d': {'e': 1}}}}}
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)