将嵌套的python列表简化为结构化树的最佳方法(同时保留顺序)

时间:2019-09-22 05:13:43

标签: python anytree

假设我有一个Python 3.6列表,看起来像这样:

l1 = [
     [a,b,c], 
     [b,c], 
     [c], 
     [d, e], 
     [e]
     ...
] 

我需要使用anytree将其转换为树状结构,以使其看起来像这样:

>>> print(RenderTree(l1))

l1
|__ a
|   |__b
|      |__c
|___d
    |__e

将对象abcde视为字符串(如果有帮助的话)。我目前已经阅读了很多关于anytree的文档,并在StackOverflow上搜索了一段时间,但是找不到任何可以帮助我解决该问题的东西。解决此问题的最有效方法是什么?

编辑:为澄清起见,原始列表l1应该表示一棵树,其中l1中的第一个元素是父节点,而其中的每个节点都是子节点。每个子节点可以是其之前节点的子节点,依此类推

编辑编辑:因此,这是原始列​​表的(假设)如下:

l1 = [
['a', 'b', 'c'],
['b', 'c'],
['c'],
['d', 'e'],
['e']
]

在这里,每个子列表的第一个元素总是最终成为该分支的父级。将这些分支的每一个连接在一起将使我获得所需的格式,但是我一直在努力用语言表达出来(这里是凌晨2点)。这是我的一些尝试:

用于将列表转换为节点:

from anytree import Node

l = []

for x in l1:
    a = Node(x[0])
    for i in x[1:]:
        Node(i, parent = a)
    l.append(a)

但是,这将返回一个树/列表,因此:


>>> l
[Node('/a'), Node('/b'), Node('/c'), Node('/d'), Node('/e')]
>>> print(RenderTree(l[0]))
Node('/a')
├── Node('/a/b')
└── Node('/a/c')
>>> print(RenderTree(l[1]))
Node('/b')
└── Node('/b/c')
>>> print(RenderTree(l[2]))
Node('/c')
>>> print(RenderTree(l[3]))
Node('/d')
└── Node('/d/e')
>>> print(RenderTree(l[4]))
Node('/e')

要对此进行过滤,我尝试执行以下操作:

def tuple_replace(tup, pos, val):
    return tup[:pos] + (val,) + tup[pos+1:]

>>> l2=[]
>>> for pos, x in enumerate(l):
    for pos_2, i in enumerate(x.children):
        for j in l[pos+1:]:
            if j.name == i.name:
                x.children = tuple_replace(x.children, pos_2, i)
                break
        l2.append(x)

>>> for x in l2:
    print(RenderTree(x))


Node('/a')
├── Node('/a/b')
└── Node('/a/c')
Node('/a')
├── Node('/a/b')
└── Node('/a/c')
Node('/b')
└── Node('/b/c')
Node('/d')
└── Node('/d/e')

那是我目前所处的步骤

编辑编辑编辑:

所以,树的表示方式是我有一个函数,该函数返回像l1这样的列表,并具有以下逻辑:

列表中的每个元素都有2部分。父母和孩子。父级是列表中的第一个元素,其他所有元素都是其子级,或者是子级的子级,依此类推。因此,像[a, b, c][d, e, f, g]这样的元素代表分支中的所有元素,而不仅仅是持续下降的直接父级。这就是其余元素起作用的地方。下一个元素通常包含父母的第一个孩子:[b, c][e, f][g]。但是现在,元素[d, e, f, g][a, b, c]不同,因为元素内部有2个不同的子分支,而不是一个。因此,像这样的树:

l1
|
|_a
|   |__b
|   |__c
|
|_d
   |__e
   |    |__f
   |__g

将被描述为:

编辑:修复了输入树,因为f没有独立的分支

l1=[
 [a,b,c],
 [b, c],
 [c],
 [d,e,f,g],
 [e,f]
 [f]
 [g]
]

1 个答案:

答案 0 :(得分:1)

您可以使用递归来构建嵌套的字典来表示您的树,然后遍历结果以打印所需的图:

from functools import reduce
data = [['a', 'b', 'c'], ['b', 'c'], ['c'], ['d', 'e'], ['e']]
new_data = [a for i, a in enumerate(data) if all(a[0] not in c for c in data[:i])]
def to_tree(d):
   return d[0] if len(d) == 1 else {d[0]:to_tree(d[1:])}

tree = reduce(lambda x, y:{**x, **y}, [to_tree(i) for i in new_data])

现在,要打印结构:

import re
def print_tree(d, c = 0):
   for a, b in d.items():
     yield f'{"|" if c else ""}{"   "*c}|__{a}'
     if not isinstance(b, dict):
        yield f'{"|" if (c+1) else ""}{"   "*(c+1)}|__{b}'
     else:
        yield from print_tree(b, c+1)

*r, _r = print_tree(tree)
print('l1\n{}\n{}'.format('\n'.join(r), re.sub("^\|", "", _r)))

输出:

l1
|__a
|  |__b
|     |__c
|__d
   |__e

编辑:可选的树形成方法:

当前的to_tree方法假定父子节点结构都将作为每个父节点的单个列表包括在内,即['a', 'b', 'c']是树的完整路径,而['d', 'e']也是一条完整的道路。如果将来输入可能不是这种情况,则可以使用下面的代码来构建字典:

def to_tree(d, s, seen = []):
   _l = [b for a, b, *_ in d if a == s and b not in seen]
   return s if not _l else {s:to_tree(d, _l[0], seen+[s, _l[0]])}

data = [['a', 'b', 'c'], ['b', 'c'], ['c'], ['d', 'e'], ['e']]
p = [a[0] for i, a in enumerate(data) if all(a[0] not in c for c in data[:i])]
c = [i for i in data if len(i) > 1]
tree = reduce(lambda x, y:{**x, **y}, [to_tree(c, i) for i in p])