【问题标题】:ordered dictionary of ordered dictionaries in pythonpython中有序字典的有序字典
【发布时间】:2015-12-15 16:30:50
【问题描述】:

我需要一个字典数据结构来存储字典,如下所示:

custom = {1: {'a': np.zeros(10), 'b': np.zeros(100)}, 
          2: {'c': np.zeros(20), 'd': np.zeros(200)}}

但问题是我在代码中多次迭代这个数据结构。每次迭代它时,我都需要遵守迭代顺序,因为这个复杂数据结构中的所有元素都映射到一维数组(如果愿意,可以序列化),因此顺序很重要。我想过为此写一个有序的dict 或有序的dict,但我不确定这是正确的解决方案,因为我可能选择了错误的数据结构。对于我的情况,最合适的解决方案是什么?

更新

这就是我目前想出的:

class Test(list):

    def __init__(self, *args, **kwargs):

        super(Test, self).__init__(*args, **kwargs)

        for k,v in args[0].items():
            self[k] = OrderedDict(v)

        self.d = -1
        self.iterator = iter(self[-1].keys())
        self.etype = next(self.iterator)
        self.idx = 0


    def __iter__(self):
        return self

    def __next__(self):

        try:
            self.idx += 1
            return self[self.d][self.etype][self.idx-1]

        except IndexError:

            self.etype = next(self.iterator)
            self.idx = 0
            return self[self.d][self.etype][self.idx-1]

    def __call__(self, d):

        self.d = -1 - d
        self.iterator = iter(self[self.d].keys())
        self.etype = next(self.iterator)
        self.idx = 0
        return self


def main(argv=()):

    tst = Test(elements)
    for el in tst:
        print(el)
    # loop over a lower dimension
    for el in tst(-2):
        print(el)

    print(tst)


    return 0

if __name__ == "__main__":
    sys.exit(main())

我可以在这个有序结构中迭代任意多次,并且我实现了__call__,因此我可以迭代较低的维度。我不喜欢这样一个事实,即如果列表中没有较低的维度,它不会给我任何错误。我也觉得每次调用return self[self.d][self.etype][self.idx-1] 的效率都低于字典上的原始迭代。这是真的?我该如何改进?

【问题讨论】:

    标签: python dictionary


    【解决方案1】:

    这是另一种选择,它使用OrderedDefaultdict 来定义您想要的树状数据结构。我正在重用我的另一个 answer 的定义。

    要使用它,您必须确保按照您以后想要访问它们的顺序定义条目。

    class OrderedDefaultdict(OrderedDict):
        def __init__(self, *args, **kwargs):
            if not args:
                self.default_factory = None
            else:
                if not (args[0] is None or callable(args[0])):
                    raise TypeError('first argument must be callable or None')
                self.default_factory = args[0]
                args = args[1:]
            super(OrderedDefaultdict, self).__init__(*args, **kwargs)
    
        def __missing__ (self, key):
            if self.default_factory is None:
                raise KeyError(key)
            self[key] = default = self.default_factory()
            return default
    
        def __reduce__(self):  # optional, for pickle support
            args = (self.default_factory,) if self.default_factory else ()
            return self.__class__, args, None, None, self.iteritems()
    
    Tree = lambda: OrderedDefaultdict(Tree)
    
    custom = Tree()
    custom[1]['a'] = np.zeros(10)
    custom[1]['b'] = np.zeros(100)
    custom[2]['c'] = np.zeros(20)
    custom[2]['d'] = np.zeros(200)
    

    我不确定我是否理解您的后续问题。如果数据结构仅限于两个级别,您可以使用嵌套的for 循环按照定义的顺序迭代其元素。例如:

    for key1, subtree in custom.items():
        for key2, elem in subtree.items():
            print('custom[{!r}][{!r}]: {}'.format(key1, key2, elem))
    

    (在 Python 2 中,您希望使用 iteritems() 而不是 items()。)

    【讨论】:

    • 感谢您的解决方案。如果您必须提供更友好的用户界面来按顺序遍历字典中的元素,这可能吗?假设我想做for el in custom:,然后以有序的方式单独遍历每个元素,你会怎么做?
    • 我的意思是,如果只用一个循环就可以按顺序遍历整个数据结构就好了。我试图通过覆盖__iter____next__ 方法来做到这一点,但我失败得很惨。我还想问您是否可以解释一下您编写的代码,因为这对我来说是相当高级的 Python。
    • 对于字典,__iter__()next()(不是 __next__)方法只是遍历字典的键,但是基于字典的递归树结构可以支持不止一种形式的迭代 —例如,广度优先和深度优先遍历。此外,字典有一个items() 方法,它返回它包含的所有(key, value) 对的副本。有了这些差异,当您只说要按顺序遍历元素时,不清楚您要完成什么。在这种情况下,您认为什么顺序和什么是“元素”?
    • 我的答案中的代码是在 Python 中实现 autovivification 的示例,它源自链接的 Wikipedia 文章中显示的 Python 代码(其中包含一些额外的参考资料)。
    • 我想要的是您建议的数据结构,但是我希望用户不要像您在答案末尾显示的那样使用两个循环来迭代抛出字典元素键入for i in custom:,并且由于您使用的是有序字典,因此在遍历字典时顺序始终相同。你认为这可以通过覆盖__iter__next() 来完成吗?
    【解决方案2】:

    我认为使用OrderedDicts 是最好的方法。它们是内置的并且相对较快:

    custom = OrderedDict([(1, OrderedDict([('a', np.zeros(10)),
                                           ('b', np.zeros(100))])),
                          (2, OrderedDict([('c', np.zeros(20)),
                                           ('d', np.zeros(200))]))])
    

    如果您想让迭代数据结构的内容变得容易,您总是可以提供一个实用函数来做到这一点:

    def iter_over_contents(data_structure):
        for delem in data_structure.values():
            for v in delem.values():
                for row in v:
                    yield row
    

    请注意,在允许yield from <expression> 的Python 3.3+ 中,可以消除最后一个for 循环:

    def iter_over_contents(data_structure):
        for delem in data_structure.values():
            for v in delem.values():
                yield from v
    

    使用其中一个,您就可以编写如下内容:

    for elem in iter_over_contents(custom):
        print(elem)
    

    并隐藏复杂性。

    虽然您可以定义自己的类以尝试封装此数据结构并使用类似iter_over_contents() 生成器函数作为其__iter__() 方法,但这种方法可能会更慢并且不会允许表达式使用两个索引级别,如下所示:

    custom[1]['b']
    

    使用嵌套字典(或OrderedDefaultdicts,如我的其他答案所示)会。

    【讨论】:

      【解决方案3】:

      你能用一个字典列表吗?

      custom = [{'a': np.zeros(10), 'b': np.zeros(100)},
                {'c': np.zeros(20), 'd': np.zeros(200)}]
      

      如果外部字典是您唯一需要的以正确顺序排列的字典,这可能会起作用。您仍然可以使用custom[0]custom[1] 访问内部字典(注意,索引现在从0 开始)。

      如果没有使用所有索引,您可以执行以下操作:

      custom = [None] * maxLength   # maximum dict size you expect
      
      custom[1] = {'a': np.zeros(10), 'b': np.zeros(100)}
      custom[2] = {'c': np.zeros(20), 'd': np.zeros(200)}
      

      【讨论】:

      • 我不能使用它,因为 1 可能存在也可能不存在,0 和 2 也是如此。
      • 啊,好吧,所以您实际上需要外部dict 中的键 - 没关系,抱歉造成误解!
      • @aaragon 我编辑了答案以保留作为外部dict 键的索引,并将所有不可用的元素设置为None
      • 您的解决方案仍未排序,例如循环遍历custom[1] 可以获得a 的元素,然后是b,在不同的迭代中,元素b 然后是@ 987654333@。这可以通过使用OrderedDict 来解决,但问题仍然存在:有没有更好的方法来处理这个问题?
      【解决方案4】:

      当您首先对它们进行排序时,您可以在迭代时修复键的顺序:

      for key in sorted(custom.keys()):
          print(key, custom[key])
      

      如果您想减少sorted()-calls,您可能希望将密钥存储在一个额外的列表中,然后将其用作您的迭代顺序:

      ordered_keys = sorted(custom.keys())
      for key in ordered_keys:
          print(key, custom[key])
      

      您应该准备好根据需要对数据结构进行尽可能多的迭代。

      【讨论】:

      • 我在这个数据结构上迭代了很多次。
      • 结构在整个应用程序中被迭代了很多次,我想做的是提供一种更加用户友好的方法来输入for k,v in custom.items(): for i,r in enumerate(v): # etc.
      • 嗯,这似乎是一个完全不同的问题。
      • 确实如此,看看here,不过这个我得仔细考虑,所以我从选择合适的数据结构开始。
      猜你喜欢
      • 2014-04-19
      • 1970-01-01
      • 2012-04-22
      • 2018-12-14
      • 2013-05-01
      • 2011-05-18
      • 1970-01-01
      • 1970-01-01
      • 2010-10-02
      相关资源
      最近更新 更多