【问题标题】:Nested `defaultdict of defaultdict of defaultdict` each with a backreference嵌套的`defaultdict of defaultdict of defaultdict`,每个都有一个反向引用
【发布时间】:2021-12-01 12:27:21
【问题描述】:

使用tree = lambda: dedfaultdict(tree),我可以替换以下代码:

from collections import defaultdict

END = '$'
words = ['hi', 'hello', 'hiya', 'hey']

root = {}
for word in words:
  node = root
  for ch in word:
    node = node.setdefault(ch, {}) # <---- Code that can be replaced
  node[END] = None

与:

from collections import defaultdict

END = '$'
words = ['hi', 'hello', 'hiya', 'hey']

tree = lambda: defaultdict(tree)
root = tree()
for word in words:
  node = root
  for ch in word:
    node = node[ch] # <------ Replaced code
  node[END] = None

我真正想要的是每个字典节点都有一个对其父字典节点的反向引用。我可以这样做:

from collections import defaultdict

BACKREF, END = 'BACKREF', '$'
words = ['hi', 'hello', 'hiya', 'hey']

root = {}
for word in words:
  node = root
  for ch in word:
    node = node.setdefault(ch, {BACKREF: node}) # <---- Code I want to replace
  node[END] = None

(证明这是有效的:link

所以,鉴于我能够使用tree = lambda: defaultdict(tree) 替换

  • node = node.setdefault(ch, {})
  • node = node[ch]

有没有办法可以使用tree = lambda: default(tree) 的修改版本来替换

  • node = node.setdefault(ch, {BACKREF: node})
  • 用更简单的东西,比如node = node[ch] ?

我尝试过类似的方法:

def tree():
  _ = defaultdict(tree)
  _[BACKREF] = ?
  return _
root = tree()
h = root['h']

但这需要tree 知道哪个字典调用了对tree 的调用。例如。在h = root['h'] 中,root['h'] 调用对tree 的调用,因为h 尚未在root 中。 tree 必须知道它是通过调用 root['h'] 调用的,以便它可以执行 h[BACKREF] = root。有没有解决的办法?即使可以做到,这也是个坏主意吗?

我知道反向引用在技术上意味着 trie 将具有循环(而不是真正的树),但我计划遍历 trie 的方式,这不会是一个问题。我想要反向引用的原因是,如果我想从 trie 中删除一个单词,它会很有用。例如,假设我有以下尝试:

我在 root['h']['e']['l']['l']['o'] 并想从 trie 中删除 'hello'。我可以通过将特里树从root['h']['e']['l']['l']['o'] 回溯到root['h']['e']['l']['l']root['h']['e']['l']root['h']['e'] 来做到这一点(我停在这里是因为len(set(root['h']['e'].keys()) - {BACKREF}) &gt; 1。然后我可以简单地做del root['h']['e']['l'] 并且我将切掉@ 987654356@ from 'he' 表示 trie 仍将具有 'hey'。虽然有其他选择,但使用反向引用回溯 trie 将非常容易。


tree = lambda: defaultdict(tree) 上的上下文

使用:

from collections import defaultdict

tree = lambda: defaultdict(tree)
root = tree()

可以创建任意嵌套的dicts。例如。之后:

root['h']['i']
root['h']['e']['l']['l']['o']
root['h']['i']['y']['a']
root['h']['e']['y']

root 看起来像:

{'h': {'i': {'y': {'a': {}}}, 'e': {'y': {}, 'l': {'l': {'o': {}}}}}}

这表示一棵如下所示的树: 使用https://www.cs.usfca.edu/~galles/visualization/Trie.html可视化

【问题讨论】:

  • 把相同的{BACKREF: node}defaultdict?
  • @don'ttalkjustcode 我不确定如何将{BACKREF: node} 放入tree = lambda: defaultdict(tree) 或类似的东西。另一个问题是 node 在定义 tree 时未定义 - 我该如何解决?
  • 如果我这样做root = defaultdict(lambda: {BACKREF: root}),它适用于一个嵌套级别,但不适用于进一步 - 例如root['h'] 将根据需要导致root = {'h': {'backref': root}},但如果我尝试使用root['h']['i'] 更进一步,则会导致KeyErrror: 'i',因为root['h'] 只是一个普通的dict,它本身并不是defaultdict(lambda: {BACKREF: root['h']})

标签: python trie defaultdict recursive-datastructures


【解决方案1】:

解决方案 1

defaultdict同样的{BACKREF: node}

from collections import defaultdict

BACKREF, END = 'BACKREF', '$'
words = ['hi', 'hello', 'hiya', 'hey']

tree = lambda: defaultdict(tree, {BACKREF: node})
node = None
root = tree()
for word in words:
  node = root
  for ch in word:
    node = node[ch]
  node[END] = None

root 节点有一个 backref None,如果麻烦可以删除。

解决方案 2

如果该代码是创建树节点的唯一代码(从我自己构建此类树的时间来看,这对我来说似乎很可能),那么上述代码就可以正常工作。否则,您需要确保 node 指向正确的父节点。如果这是一个问题,这里有一个 dict(不是 defaultdict)子类的替代方案,它实现 __missing__ 以在需要时自动创建带有 backrefs 的子类:

BACKREF, END = 'BACKREF', '$'
words = ['hi', 'hello', 'hiya', 'hey']

class Tree(dict):
    def __missing__(self, key):
        child = self[key] = Tree({BACKREF: self})
        return child

root = Tree()
for word in words:
  node = root
  for ch in word:
    node = node[ch]
  node[END] = None

也不给根节点一个反向引用,并且作为一个字典,它的字符串表示远没有默认字典那么混乱,因此更具可读性:

>>> import pprint
>>> pprint.pp(root)
{'h': {'BACKREF': <Recursion on Tree with id=2494556270320>,
       'i': {'BACKREF': <Recursion on Tree with id=2494556270400>,
             '$': None,
             'y': {'BACKREF': <Recursion on Tree with id=2494556270480>,
                   'a': {'BACKREF': <Recursion on Tree with id=2494556340608>,
                         '$': None}}},
       'e': {'BACKREF': <Recursion on Tree with id=2494556270400>,
             'l': {'BACKREF': <Recursion on Tree with id=2494556340288>,
                   'l': {'BACKREF': <Recursion on Tree with id=2494556340368>,
                         'o': {'BACKREF': <Recursion on Tree with id=2494556340448>,
                               '$': None}}},
             'y': {'BACKREF': <Recursion on Tree with id=2494556340288>,
                   '$': None}}}}

比较的默认dict结果:

>>> pprint.pp(root)
defaultdict(<function <lambda> at 0x000001A13760BE50>,
            {'BACKREF': None,
             'h': defaultdict(<function <lambda> at 0x000001A13760BE50>,
                              {'BACKREF': <Recursion on defaultdict with id=1791930855152>,
                               'i': defaultdict(<function <lambda> at 0x000001A13760BE50>,
                                                {'BACKREF': <Recursion on defaultdict with id=1791930855312>,
                                                 '$': None,
                                                 'y': defaultdict(<function <lambda> at 0x000001A13760BE50>,
                                                                  {'BACKREF': <Recursion on defaultdict with id=1791930912832>,
                                                                   'a': defaultdict(<function <lambda> at 0x000001A13760BE50>,
                                                                                    {'BACKREF': <Recursion on defaultdict with id=1791930913232>,
                                                                                     '$': None})})}),
                               'e': defaultdict(<function <lambda> at 0x000001A13760BE50>,
                                                {'BACKREF': <Recursion on defaultdict with id=1791930855312>,
                                                 'l': defaultdict(<function <lambda> at 0x000001A13760BE50>,
                                                                  {'BACKREF': <Recursion on defaultdict with id=1791930912912>,
                                                                   'l': defaultdict(<function <lambda> at 0x000001A13760BE50>,
                                                                                    {'BACKREF': <Recursion on defaultdict with id=1791930912992>,
                                                                                     'o': defaultdict(<function <lambda> at 0x000001A13760BE50>,
                                                                                                      {'BACKREF': <Recursion on defaultdict with id=1791930913072>,
                                                                                                       '$': None})})}),
                                                 'y': defaultdict(<function <lambda> at 0x000001A13760BE50>,
                                                                  {'BACKREF': <Recursion on defaultdict with id=1791930912912>,
                                                                   '$': None})})})})

【讨论】:

  • @joseville 就像文档说的那样,它就像传递给 dict 构造函数一样传递,即它用于初始化新 defaultdict 的 contents
  • 谢谢!效果很好。我使用def 函数想出了类似的东西,但这更简洁。我很难理解如何处理 defaultdict 的第二个参数,但 another SO thread 有帮助。特别是:“因此,我们在这里构造了一个默认字典,其中第二个参数{BACKREF: node} 中的项目作为键值对,我们使用第一个参数tree 作为“工厂”来构造缺失键的值。” (引用适合这个例子)。
  • @joseville 查看上面的 cmets 以及第二个解决方案的答案更新。
  • @kaya3 感谢您指出这一点!需要明确的是,反向引用不会动态指向node,它们指向node 在调用defaultdict.__missing__() 时的值。所以在上面写的解决方案1中,所有的反向引用都是正确的,如果我将node更改为None,反向引用不会突然全部指向None。评论继续...
  • 但是如果在 for 循环之后(在 解决方案 1 中)我这样做:node = rootnode['a']['l']['o']['h']['a'],然后是 node['a']node['a']['l'] 的反向引用、node['a']['l']['o']node['a']['l']['o']['h']node['a']['l']['o']['h']['a'] 都将指向等于 rootnode——这是 node['a'] 的正确反向引用,但不适用于其他引用。
【解决方案2】:

你试图实现的行为似乎更容易写成一个类而不是一个函数。

from collections import defaultdict

class Tree(defaultdict):
    def __init__(self, backref=None):
        super().__init__(self.make_child)
        self.backref = backref
    def make_child(self):
        return Tree(backref=self)

用法:

>>> root = Tree()
>>> root['h'].backref is root
True
>>> root['h']['e'].backref is root['h']
True

【讨论】:

  • @don'ttalkjustcode 好点,这部分与我的答案并不真正相关,我误解了它在问题中的用途。
  • 好吧,这就是我的怀疑。但是我认为当我们立即在构造函数中设置所有属性时,有一种优化可以使属性字典更小(不确定它是否必须已经在 defaultdict 的构造函数中而不仅仅是你的构造函数中),所以也可以这样。
  • @kaya3 谢谢!它运作良好!我正在玩这个版本。将super().__init__(self.make_child) 替换为super().__init__(lambda: Tree(backref=self)) 并将self.backref = backref 替换为self['backref'] = backref,但这不起作用,想知道您是否知道原因?是否与lambda 中的self 有关,可能在调用lambda 时没有指代正确的东西?
  • @joseville 不确定你做了什么,但这应该可以工作,并且在我测试它时确实可以工作。 self 没有在它定义的范围内重新分配,所以这不是问题。也许您正在测试 root['h'].backref is root 而不是 root['h']['backref'] is root
猜你喜欢
  • 2013-10-11
  • 2021-11-28
  • 2022-01-12
  • 1970-01-01
  • 1970-01-01
  • 2014-12-17
相关资源
最近更新 更多