递归遍历字典

4
我需要递归遍历一个字典并“记住上一个key”。
让我解释一下:
dic = {u'resources': {u'info': {u'load': (u'37', u'17')}}, u'peak': {u'load': (u'0', u'1')}}

元素始终是一个值或字典,直到它达到一个值。我想像这样打印上面的dic:(省略下面的xxx,它应该最终是两个值的差异)
resources info load 37 17 xxx
resources peak load 0 1 xxx

这是我目前所拥有的代码:
def convertToTable(var):
    if isinstance(var, tuple):
        if len(var) != 2:
            return str(var)

        v1, v2 = var
        try:
            v1 = float(v1)
            v2 = float(v2)
        except ValueError:
            pass
        if type(v1) != type(v2):
            return '\t%s\t%s\n' % (v1, v2)
        elif isinstance(v1, int) or isinstance(v1, float):
            sign = '+' if v2 - v1 > 0 else ''
            return '\t%s\t%s\t%s%s\n' % (v1, v2, sign, v2 - v1)
        elif isinstance(v1, list):
            ret = ''
            for i in range(max(len(v1), len(v2))):
                v1v = v1[i] if i < len(v1) else ''
                v2v = v2[i] if i < len(v2) else ''
                ret += '\t%s, %s\n' % (v1v, v2v)
            return ret
        else:
            return '\t%s\t%s\n' % (v1, v2)
    elif isinstance(var, dict):
        ret = ''
        for key, value in var.iteritems():
            # fix this crap, it's not printing all recursive levels of keys!
            ret += '%s %s' % (key, convertToTable(value))
        return ret
    else:
        return '%s\n' % (var)

我不知道如何将之前的键递归地再次传递给函数!我要么得到额外的键打印或者什么都没有!(请不要建议我使用json.dumps,因为它并不能真正满足我的需求!)我希望有人能检查我的解决方案并指出其中的缺陷!

孩子们可以是列表或字典类型? - Fabian
level1的值是字典类型,但level2Blevel3B的值是字典列表。level1可以/应该是包含单个字典的列表吗? - Awalias
抱歉各位,我搞砸了这个例子。请查看更新后的版本! - theAlse
@theAlse 如果你的峰值下面有第二个条目呢? - Fabian
@fabian,那么它将是另一个字典内的内容。例如dic = {u'resources': {u'info': {u'load': (u'37', u'17')}}, u'peak': {u'xxx': {u'yyy': (u'37', u'17')}}} - theAlse
显示剩余6条评论
3个回答

5
我不确定你的代码出了什么问题,但是这个可能可以实现你想要的功能:
def iteritems_recursive(d):
  for k,v in d.iteritems():
    if isinstance(v, dict):
      for k1,v1 in iteritems_recursive(v):
        yield (k,)+k1, v1
    else:
      yield (k,),v

dic = {u'resources': {u'info': {u'load': (u'37', u'17')}, u'peak': {u'load': (u'0', u'1')}}}

for p,v in iteritems_recursive(dic):
  print p, "->", v

iteritems_recursive会迭代传入的字典,并返回一个(path, value)元组。其中,path是一个元组,描述了到达该项的键。

以上代码输出:

(u'resources', u'info', u'load') -> (u'37', u'17')
(u'resources', u'peak', u'load') -> (u'0', u'1')

如果您想让表格更美观,可以将上面的for循环替换为以下内容:
for p,v in iteritems_recursive(dic):
  diff = float(v[0]) - float(v[1])
  p = ''.join('{:10}'.format(w) for w in p)
  v = ''.join('{:5}'.format(f) for f in v)
  print p, v, diff

这将打印:

resources info      load       37   17    20.0
resources peak      load       0    1     -1.0

我希望有人能指出我的解决方案中的缺陷。我不知道我该如何更改这段代码以获取值的差异。 - theAlse
啊,我误解了你的问题,因为你没有提出一个问题。你可能想要编辑你的帖子并包含一个具体的问题。 - Robᵩ
“值的差异”是什么?考虑到您的示例输入,您希望输出具体是什么样子? - Robᵩ
我的意思是每个项目提供的两个值之间的差异,但这也可以从您的代码中轻松获得。 - theAlse
我接受你的解决方案,因为它是最简单易懂的。 - theAlse
有没有一种修改函数以处理这种情况的方法。 dic = {u'resources': {u'info': {u'load': (u'37', u'17')}, u'peak': {u'load': (u'0', u'1')}}, u'statistics': (None, OrderedDict([(u'depth', u'0'), (u'depth2', u'0'), (u'depth3', u'0')]))}(当元素是字典时,它应附加到原始字典中) - theAlse

2
def convertToTable(inp, history=[]):
    for key, value in inp.iteritems():
        history.append(key)
        if type(value) == dict:
            convertToTable(value, history)
        else:
            print '{} {} {}'.format(' -> '.join(history), value[0], value[1])
        history.pop()

dic = {'peak': {'load': ('0', '1'), 'unload': ('2', '3')}, 'resources': {'info': {'loadxx': ('37', '17')}}}
convertToTable(dic)

# peak -> load 0 1
# peak -> unload 2 3
# resources -> info -> loadxx 37 17

2
我有两个解决方案,第一个方案将所有层级按键的名称记录下来,并在返回堆栈之前将它们打印在底部。
第二种方案在下行过程中将它们打印出来,从而避免了必须要“记住”这些层级的名称。
import sys

dic = {u'resources':
            {u'info':
                {u'load': (u'37', u'17')}
            },
       u'peak':
            {u'load': (u'0', u'1')}
      }


def racecar(goomba, levels=None):
    if levels == None:
        levels = []
    for key in goomba:
        if type(goomba[key]) is dict:
            levels.append(key)
            levels = racecar(goomba[key], levels)
        else:
            levels.append(key)
            for name in levels:
                sys.stdout.write(name + ' ')
            for val in goomba[key]:
                sys.stdout.write(val + ' ')
            sys.stdout.write('xxx\n')
            return []


def racecar2(goomba):
    for key in goomba:
        sys.stdout.write(key + ' ')
        if type(goomba[key]) is dict:
            racecar(goomba[key])
        else:
            for val in goomba[key]:
                sys.stdout.write(val + ' ')
            sys.stdout.write('xxx\n')

racecar(dic)
racecar2(dic)

返回:

peak load 0 1 xxx
resources info load 37 17 xxx

谢谢你的回复,你们两个的解决方案都是错误的!几乎和我的一样不正确。你们有运行过它们并检查打印结果吗?它甚至没有打印出所有的元素! - theAlse
指定的输出似乎不反映提供的输入。你期望的输出是什么? - Awalias
哎呀,我错过了load元素,现在已经修复了。 - Awalias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接