遍历嵌套的类字典结构,跟踪从根到叶的每条路径 (Python)

Traversing a nested dict-like structure, keeping track of each path from root to leaf (Python)

我正在尝试遍历一个具有任意深度嵌套叶子的对象,获取每个根节点到叶子的路径。这方面的一个例子可能是:

class Field (object):
    def __init__(self, name, subfields=None):
        self.name = name
        self.subfields = subfields


f1 = Field("username")
f2 = Field("ip_address")
f3 = Field("song_id")
f11 = Field("song_length")
f10 = Field("misc_attrs", subfields=[f3, f11])

f4 = Field("event_time")
f5 = Field("song_name", subfields=[f1,f2])
f6 = Field("user_song_upload", subfields=[f10])

input_list = [f4,f5,f6]

我的目标是生成列表的列表 output,其中 output 中的每个列表代表从根节点到叶节点的路径。所以在上面的例子中,我们会有以下输出:

[["event_time"],["song_name","username"],["song_name","ip_address"],["user_song_upload","misc_attrs","song_id"],["user_song_upload","misc_attrs","song_length"]]

我有一个函数可以 return 所有叶节点,但我无法跟踪每个节点的完整路径。这是我目前所拥有的:

def get_leaf_paths(input_list, output = [], path=[]):
    for x in input_list:
        if not x.subfields:
            # we have a leaf
            path.append(x.name)
            output.append(path) 
            # next line clears the leaf from our last path
            path = path[:-1]

        else:
            # we don't have a leaf node, and need to iterate on the subfields
            path.append(x.name)
            get_leaf_paths(x.subfields, output, path)
    return output

然而,这会导致路径构建不正确(例如,两个相邻的顶级节点将在输出中出现在同一个内部列表中:

[['event_time'],
 ['song_name', 'username', 'user_song_upload', 'misc_attrs', 'song_id'],
 ['song_name', 'ip_address'],
 ['song_name', 'username', 'user_song_upload', 'misc_attrs', 'song_id'],
 ['song_name', 'username', 'user_song_upload', 'misc_attrs', 'song_length']]

您可以构造一个适当的基本案例来简化代码。尝试以下操作:

def get_leaf_paths(lst):
    if not lst: # empty list or None
        return [[]]
    return [[f.name, *path] for f in lst for path in get_leaf_paths(f.subfields)]

print(get_leaf_paths(input_list))

在代码中,基本情况是“空列表或 None”,即当 subfields 传递给函数但 subfields 实际上是 None.在叶节点调用函数时会出现这种情况。

输出:

[['event_time'], ['song_name', 'username'], ['song_name', 'ip_address'], ['user_song_upload', 'misc_attrs', 'song_id'], ['user_song_upload', 'misc_attrs', 'song_length']]

如果你需要将它应用到一个非常深的结构和递归(就像@j1-lee 在他们非常干净的实现中建议的那样)不是一个选项,这个工作:

def get_leaf_paths(lst):
    stack, l, i = [], lst, 0
    while l and len(l) > i:
        if l[i].subfields:
            stack.append((l, i))
            l, i = l[i].subfields, 0
        else:
            yield [x[n].name for x, n in stack] + [l[i].name]
            i += 1
        if i >= len(l):
            l, i = stack.pop()
            i += 1


print(list(get_leaf_paths(input_list)))

结果:

[['event_time'], ['song_name', 'username'], ['song_name', 'ip_address'], ['user_song_upload', 'misc_attrs', 'song_id'], ['user_song_upload', 'misc_attrs', 'song_length']]