【问题标题】:How to iterate a dict of dynamic "depths" in python?如何在python中迭代动态“深度”的字典?
【发布时间】:2010-08-12 03:16:11
【问题描述】:

我有一个具有各种“深度”的 dict 数据结构。 “深度”是指例如: 当 depth 为 1 时,dict 会是这样的:

{'str_key1':int_value1, 'str_key2:int_value2}

当depth为2时,dict会是这样的:

{'str_key1':
    {'str_key1_1':int_value1_1,
     'str_key1_2':int_value1_2},
 'str_key2':
    {'str_key2_1':int_value2_1, 
     'str_key2_2':int_value2_2} }

以此类推。

当我需要处理数据时,现在我正在这样做:

def process(keys,value):
  #do sth with keys and value
  pass

def iterate(depth,dict_data):
  if depth == 1:
    for k,v in dict_data:
      process([k],v)
  if depth == 2:
    for k,v in dict_data:
      for kk,vv, in v:
        process([k,kk],v)
  if depth == 3:
    .........

所以当 depthn 时,我需要 n 循环。由于深度可以达到 10,我想知道是否有更动态的方式来进行迭代,而不必写出所有 if 和 for 子句。

谢谢。

【问题讨论】:

    标签: python algorithm dictionary iteration


    【解决方案1】:

    我不确定为什么每个人都在考虑递归(或递归消除)——我只需执行 depth 步骤,每个步骤都会通过将列表向下扩展一层来重建列表。

    例如:

    def itr(depth, d):
      cp = [([], d)]
      for _ in range(depth):
        cp = [(lk+[k], v) for lk, d in cp for k, v in d.items()]
      for lk, ad in cp:
        process(lk, ad)
    

    如果出于教学目的需要使其更具可读性,则易于使用更长的标识符和更低的代码密度“扩展”,但我认为逻辑足够简单,它可能不需要这种处理(而且,为了它本身的冗长也有它的缺点;-)。

    例如:

    d = {'str_key1':
          {'str_key1_1':'int_value1_1',
           'str_key1_2':'int_value1_2'},
         'str_key2':
          {'str_key2_1':'int_value2_1', 
           'str_key2_2':'int_value2_2'} }
    
    def process(lok, v):
      print lok, v
    
    itr(2, d)
    

    打印

    ['str_key2', 'str_key2_2'] int_value2_2
    ['str_key2', 'str_key2_1'] int_value2_1
    ['str_key1', 'str_key1_1'] int_value1_1
    ['str_key1', 'str_key1_2'] int_value1_2
    

    (如果需要特定的顺序,当然可以对cp进行适当的排序)。

    【讨论】:

    • 这将花费大量时间来构建一次性中间列表。对我来说,递归似乎是一个更好的选择。
    • @Nick,试过测量吗?记住函数调用是有代价的。如果分析显示中间列表的构造比 @Ned 的中间列表更容易优化,后者在递归调用中作为第三个参数传递。如果可以接受元组来代替列表(尽管 OP 的示例将列表显示为 process 的第一个参数),那么它们当然可以在这里和在您的答案中一样使用。
    • 我意识到函数调用是有代价的,但我希望手动展开 dicts 几次将远远超过这一点 - 并不是我有数据支持这一点。 :)
    【解决方案2】:

    显而易见的答案是使用递归。但是,你可以在这里用 Python 做一些巧妙的事情来扁平化字典。这仍然是从根本上递归的——我们只是在实现我们自己的堆栈。

    def flatten(di):
         stack = [di]
         while stack:
             e = stack[-1]
             for k, v in e.items():
                 if isinstance(v, dict):
                     stack.append(v)
                 else:
                     yield k, v
             stack.remove(e)
    

    然后,您可以执行以下操作:

    for k, v in flatten(mycomplexdict):
         process(k, v)
    

    【讨论】:

    • 我不知道您将键添加到键列表的哪个位置。看起来除了最后一个之外的所有键都被丢弃了。
    • @deinst,这将删除所有不是叶节点的键。但是,您也可以在追加后通过 yield 轻松地生成它们。需要进行一些修改以适应特定的应用程序。 (我在文件系统上递归时使用这种模式)。
    • @Aaron Gallegher 大概会在某个地方遇到一个非字典值,或者它一直是字典?
    • @Aaron Gallagher,我认为这里有必要进行类型检查,因为它决定了列表中返回的内容。可能有一个像字典一样的值,但实际上应该在列表中。在最好的情况下,这是一个语义问题。
    • @carl,您仍然没有按要求回答问题。如果您打算切线,至少提供有用的代码,例如具有单独的childrenvalue 属性的类。类型检查基本上总是错误的。
    【解决方案3】:

    递归是你的朋友:

    def process(keys,value):
      #do sth with keys and value
      pass
    
    def iterate(depth, dict_data):
      iterate_r(depth, dict_data, [])
    
    def iterate_r(depth, data, keys):
      if depth == 0:
        process(keys, data)
      else:
        for k,v in dict_data.items():
          iterate_r(depth-1, v, keys+[k])
    

    【讨论】:

      【解决方案4】:

      递归,记住python只能递归1000次:

      def process(key, value):
          print key, value
      
      def process_dict(dict, callback):
          for k, v in dict.items():
              if hasattr(v, 'items'):
                  process_dict(v, callback)
              else:
                  callback(k, v)
      
      d = {'a': 1, 'b':{'b1':1, 'b2':2, 'b3':{'bb1':1}}}
      process_dict(d, process)
      

      打印:

      a 1
      b1 1
      b2 2
      bb1 1
      

      【讨论】:

      • 既然你乐于指出别人的错误,也许你想描述一下正确的方式。
      • @Matt Williamson,我认为你以前的版本更好。如果我有一个具有“items”方法的对象,但它不是字典怎么办?
      • @carl,如果我有一个行为类似于 dict 但不继承 dict 的对象怎么办?
      • 与 OP 的解决方案不同,这也不会保留路径。
      • @carl,没关系,因为它像鸭子一样嘎嘎叫。我们在其上使用的唯一方法是 items 方法,因此它应该类似于字典。这样更灵活。
      【解决方案5】:

      假设您想要一个固定的深度(大多数其他答案似乎假设您想要递归到最大深度),并且您需要保留原始问题中的路径,这是最直接的解决方案:

      def process_dict(d, depth, callback, path=()):
        for k, v in d.iteritems():
          if depth == 1:
            callback(path + (k,), v)
          else:
            process_dict(v, depth - 1, callback, path + (k,))
      

      这是一个实际的例子:

      >>> a_dict = {
      ...     'dog': {
      ...         'red': 5,
      ...         'blue': 6,
      ...     },
      ...     'cat': {
      ...         'green': 7,
      ...     },
      ... }
      >>> def my_callback(k, v):
      ...   print (k, v)
      ...
      >>> process_dict(a_dict, 1, my_callback)
      (('dog',), {'blue': 6, 'red': 5})
      (('cat',), {'green': 7})
      >>> process_dict(a_dict, 2, my_callback)
      (('dog', 'blue'), 6)
      (('dog', 'red'), 5)
      (('cat', 'green'), 7)
      

      【讨论】:

      • 由于元组是不可变的,因此无需使用path=None 和额外的if not path: -- 只需使用path=(),它将完全免费地简化您的代码。
      • 好点。我在中途从列表切换到元组。固定。
      最近更新 更多