如何进行高级Python哈希自动修复?

时间:2010-03-12 21:24:17

标签: python data-structures autovivification

这个问题是关于在Python中实现完整的Perl自动修改。我知道之前有类似的问题,到目前为止最好的答案是“What is the best way to implement nested dictionaries in Python?”。但是,我希望这样做:

a['x']['y'].append('z')

首先声明a['x']['y'] = [],或者更确切地说,不声明a['x'] = {}。 (请注意,在Perl中,您可以执行push @{$a->{x}{y}}, 'z';。)

我知道dictlist类没有混合,所以这很难,但我很想知道某人是否有一个巧妙的解决方案可能涉及从{{创建一个继承的类1}}但是在它上面定义了一个新的dict方法?

我也知道这可能会让一些Python纯粹主义者想要我坚持使用Perl。但是,即使只是为了挑战,我也希望看到一些东西。

4 个答案:

答案 0 :(得分:16)

a = collections.defaultdict(lambda: collections.defaultdict(list))

答案 1 :(得分:2)

也许这可以解决您对词典中任意数量“维度”的需求:

a= collections.defaultdict(list)

代码中唯一的变化是:

a['x', 'y'].append('z')

当然,这个解决方案就是你想要的解决方案取决于两个条件:

  1. 您是否需要轻松访问所有列表,例如: “第一维”中的“x”
  2. 你是否坚持Perl神奇地让你更喜欢的方式:)
  3. 如果这两个条件中的任何一个都成立,我的解决方案将无法帮助您。

答案 2 :(得分:2)

由于我们事先并不知道我们是否需要字典或列表,因此您无法将自动生成与列表相结合。除非,根据链接问题解决Nosklo的回答,你要添加列表"功能"到底层字典。基本上假设"排序"键的顺序,并始终使用列表方法。我已经做了这个例子:

class AutoVivification(dict):
    """Implementation of perl's autovivification feature. Has features from both dicts and lists,
    dynamically generates new subitems as needed, and allows for working (somewhat) as a basic type.
    """
    def __getitem__(self, item):
        if isinstance(item, slice):
            d = AutoVivification()
            items = sorted(self.iteritems(), reverse=True)
            k,v = items.pop(0)
            while 1:
                if (item.start < k < item.stop):
                    d[k] = v
                elif k > item.stop:
                    break
                if item.step:
                    for x in range(item.step):
                        k,v = items.pop(0)
                else:
                    k,v = items.pop(0)
            return d
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

    def __add__(self, other):
        """If attempting addition, use our length as the 'value'."""
        return len(self) + other

    def __radd__(self, other):
        """If the other type does not support addition with us, this addition method will be tried."""
        return len(self) + other

    def append(self, item):
        """Add the item to the dict, giving it a higher integer key than any currently in use."""
        largestKey = sorted(self.keys())[-1]
        if isinstance(largestKey, str):
            self.__setitem__(0, item)
        elif isinstance(largestKey, int):
            self.__setitem__(largestKey+1, item)

    def count(self, item):
        """Count the number of keys with the specified item."""
        return sum([1 for x in self.items() if x == item])

    def __eq__(self, other):
        """od.__eq__(y) <==> od==y. Comparison to another AV is order-sensitive
        while comparison to a regular mapping is order-insensitive. """
        if isinstance(other, AutoVivification):
            return len(self)==len(other) and self.items() == other.items()
        return dict.__eq__(self, other)

    def __ne__(self, other):
        """od.__ne__(y) <==> od!=y"""
        return not self == other

这遵循为dud键动态生成自身的基本自动修复功能。但是,它还实现了一些方法listed here。这使它像一个准列表/字典的行为。

对于列表功能的其余部分,请添加列出的方法。我用列表方法将其视为字典。如果调用了一个列表方法,那么它会假设所持有的项目的顺序,即字符串排序低于整数,并且这些键总是在&#34;排序&#34;顺序。

它还支持添加these methods的示例。这来自我自己的用例。我需要从AutoVivified字典中添加项目,但如果它不存在,则创建并返回一个新的AutoVivification对象。它们没有整数&#34;值&#34;所以你不能这样做:

rp = AutoVivification()
rp['a']['b'] = 3
rp['a']['b'] + rp['q']

这打败了目的,因为我不知道是否会有某些东西存在,但我还是想要默认。所以我已经添加了__add____radd__方法。它们使用基础字典的length作为integer值,因此新创建的AV对象的值为零以进行添加。如果一个键除了AV对象之外还有其它东西,那么我们就会得到那个东西的加法方法。

答案 3 :(得分:1)

只是扩展Ignacio的答案,提出一些额外的选项,允许您从Python词典中明确请求更多神奇的行为。以这种方式编写的代码的可维护性仍然是可疑的,但我想清楚地表明这是一个“这种数据结构是否可维护?”的问题。 (我有疑虑)不是“可以让Python以这种方式行事吗?” (当然可以)。

为了支持命名空间方面的任意级别的嵌套,您所要做的就是命名函数(而不是使用lambda)并使其自引用:

>>> from collections import defaultdict
>>> def autodict(): return defaultdict(autodict)
...
>>> a = autodict()
>>> a[1][2][3] = []
>>> type(a[1])
<class 'collections.defaultdict'>
>>> type(a[1][2])
<class 'collections.defaultdict'>
>>> type(a[1][2][3])
<class 'list'>
>>> a[1][2][3]
[]

但是,这会引入“问题”,您必须先明确设置列表,然后才能追加它。 Python的答案在于setdefault方法,它实际上比collections.defaultdict更长:

>>> a.setdefault(3, []).append(10)
>>> a.setdefault(3, []).append(11)
>>> a[3]
[10, 11]
>>> a[2].setdefault(3, []).append(12)
>>> a[2].setdefault(3, []).append(13)
>>> a[2][3]
[12, 13]
>>> a[1][2].setdefault(3, []).append(14)
>>> a[1][2].setdefault(3, []).append(15)
>>> a[1][2][3]
[14, 15]

所有collections.defaultdict实际上都是通常情况下,您总是将相同的第二个参数传递给dict.setdefault更容易使用。对于更复杂的情况,例如此情况,您仍然可以直接使用dict.setdefault来处理collections.defaultdict无法处理的方面。