【问题标题】:Multi-level defaultdict with variable depth?具有可变深度的多级默认字典?
【发布时间】:2011-07-19 04:33:15
【问题描述】:

我有一个大列表,例如:

[A][B1][C1]=1
[A][B1][C2]=2
[A][B2]=3
[D][E][F][G]=4

我想构建一个多级字典,例如:

A
--B1
-----C1=1
-----C2=1
--B2=3
D
--E
----F
------G=4

我知道,如果我使用递归 defaultdict,我可以编写 table[A][B1][C1]=1table[A][B2]=2,但这只有在我对这些插入语句进行硬编码时才有效。

在解析列表时,我不知道需要多少个 [] 才能调用 table[key1][key2][...]

【问题讨论】:

标签: python dictionary


【解决方案1】:

你甚至可以在不定义类的情况下做到这一点:

from collections import defaultdict

nested_dict = lambda: defaultdict(nested_dict)
nest = nested_dict()

nest[0][1][2][3][4][5] = 6

【讨论】:

  • 这太棒了!但是如果我想让叶子通过标准(int、list 等)工厂初始化呢?例如,我想能够说:table[0][1][2][3][4][5] += 1
  • 有没有办法用内置的 dict 和 .get() 做同样的事情?
  • class l(dict): __missing__=lambda a,b:a.setdefault(b,l()) 然后从 table=l() 继续
  • PyCharm 说它违反了 PEP 8:“不要使用 def 分配 lambda 表达式”。有什么方法可以通过函数来​​消除警告?
  • def nested_dict(): return defaultdict(nested_dict) 但我更喜欢 lambda 版本。它看起来有点神秘;-)
【解决方案2】:

您的示例表明,在任何级别都可以有一个值,也可以是一个子元素字典。这称为,并且有许多可用的实现。这是一个:

from collections import defaultdict
class Tree(defaultdict):
    def __init__(self, value=None):
        super(Tree, self).__init__(Tree)
        self.value = value

root = Tree()
root.value = 1
root['a']['b'].value = 3
print root.value
print root['a']['b'].value
print root['c']['d']['f'].value

输出:

1
3
None

您可以通过在 JSON 中编写输入并使用 json.load 将其作为嵌套字典结构读取来执行类似的操作。

【讨论】:

  • 我认为value 构造是不必要的,至少就提议的问题而言。只需删除对value 的引用并将值直接分配给字典键。
  • +1:虽然value arg/属性并不是真正需要的。
  • @Martineau @Jason。 value 实例变量是必要的,否则当您直接分配给节点时,您会丢失子节点(请参阅我对 Jason 优雅解决方案的评论)。干预__setitem__ 将提供更强大的解决方案,但对于简单的需求来说,这将是一个过于复杂的解决方案。
  • 我不清楚如何修改集合属性是list 而不是int/float 的其他答案。这个答案很清楚,self.value = [] 正是我想要的!
【解决方案3】:

我认为递归字典的最简单实现是这样的。只有叶节点可以包含值。

# Define recursive dictionary
from collections import defaultdict
tree = lambda: defaultdict(tree)

用法:

# Create instance
mydict = tree()

mydict['a'] = 1
mydict['b']['a'] = 2
mydict['c']
mydict['d']['a']['b'] = 0

# Print
import prettyprint
prettyprint.pp(mydict)

输出:

{
  "a": 1, 
  "b": {
    "a": 1
  }, 
  "c": {},
  "d": {
    "a": {
      "b": 0
    }
  }
}

【讨论】:

  • 刚刚注意到我的帖子与 #2 重复。对不起
  • 这可能是一个骗局,但我认为这个例子非常具有说明性和有用性,所以我想说你肯定添加了一些有用的内容。
【解决方案4】:

我会使用定义__missing__dict 的子类:

>>> class NestedDict(dict):
...     def __missing__(self, key):
...             self[key] = NestedDict()
...             return self[key]
...
>>> table = NestedDict()
>>> table['A']['B1']['C1'] = 1
>>> table
{'A': {'B1': {'C1': 1}}}

你不能直接用defaultdict来做,因为defaultdict expects the factory function在初始化时,但是在初始化时,没有办法描述同一个defaultdict。上面的构造与默认 dict 所做的事情相同,但由于它是一个命名类(NestedDict),它可以在遇到缺失键时引用自己。也可以继承 defaultdict 并覆盖__init__

【讨论】:

  • 这还不够。如果你尝试table['A']['B1']['C1']['D2'] = 2,你会得到一个错误。节点必须能够保存一个值子节点。
  • @Apalala:实际上,从 OP 的示例输入来看,节点似乎只需要能够保存一个值 个子节点,而不是两者——这就是为什么 @ Jason 和我声称你的答案的 value 属性是不必要的。
  • @martinau MHO 是这一切都变得不稳定(容易出错),除非它被解决为一棵树。语法和实现无关紧要。是,还是不是需要树结构的问题?我的观点是,除非有令人信服的理由,否则不应强迫设计采用 pretty 语法。亲吻。
  • @Apalala 我知道这是旧的。但是我们如何实现一个既包含值又包含子元素的defaultdict
  • @HalcyonAbrahamRamirez 看看 Apalala 在同一个问题中的回答。
【解决方案5】:

这相当于上面的,但避免使用 lambda 表示法。也许更容易阅读?

def dict_factory():
   return defaultdict(dict_factory)

your_dict = dict_factory()

另外——来自 cmets——如果你想从现有的字典中更新,你可以简单地调用

your_dict[0][1][2].update({"some_key":"some_value"})

为了给字典添加值。

【讨论】:

  • 此解决方案不提供传递初始值的能力。由于这个原因,我认为 Dan O'Huiginn 的解决方案(通过 Dvd Avins 的帖子)稍微好一些。
【解决方案6】:

Dan O'Huiginn 在 2010 年的日记中发布了一个非常好的解决方案:

http://ohuiginn.net/mt/2010/07/nested_dictionaries_in_python.html

>>> class NestedDict(dict):
...     def __getitem__(self, key):
...         if key in self: return self.get(key)
...         return self.setdefault(key, NestedDict())


>>> eggs = NestedDict()
>>> eggs[1][2][3][4][5]
{}
>>> eggs
{1: {2: {3: {4: {5: {}}}}}}

【讨论】:

  • 当我想快速创建嵌套字典时,我发现这种方法很好。如果我想“重新启用”KeyError,使用dict() 很容易转换回标准字典。
  • return self.setdefault(key, NestedDict()) 就足够了。不需要 if。
【解决方案7】:

您可以通过递归defaultdict 来实现这一点。

from collections import defaultdict

def tree():
    def the_tree():
        return defaultdict(the_tree)
    return the_tree()

在闭包(“私有”本地函数范围)中保护默认工厂名称 the_tree 很重要。避免使用由于 Python 的 late binding closures 而存在错误的单行 lambda 版本,而是使用 def 来实现它。

使用 lambda 的公认答案存在一个缺陷,即实例必须依赖外部范围中存在的 nested_dict 名称。如果由于某种原因无法解析工厂名称(例如,它被反弹或删除),那么预先存在的实例也会被巧妙地破坏:

>>> nested_dict = lambda: defaultdict(nested_dict)
>>> nest = nested_dict()
>>> nest[0][1][2][3][4][6] = 7
>>> del nested_dict
>>> nest[8][9] = 10
# NameError: name 'nested_dict' is not defined

【讨论】:

    【解决方案8】:

    添加到@Hugo
    拥有最大深度:

    l=lambda x:defaultdict(lambda:l(x-1)) if x>0 else defaultdict(dict)
    arr = l(2)
    

    【讨论】:

      【解决方案9】:

      允许常规字典初始化的稍微不同的可能性:

      from collections import defaultdict
      
      def superdict(arg=()):
          update = lambda obj, arg: obj.update(arg) or obj
          return update(defaultdict(superdict), arg)
      

      例子:

      >>> d = {"a":1}
      >>> sd = superdict(d)
      >>> sd["b"]["c"] = 2
      

      【讨论】:

        猜你喜欢
        • 2016-01-30
        • 2016-01-28
        • 2011-05-04
        • 2011-06-29
        • 2020-07-18
        • 2016-06-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多