【问题标题】:Best way to simplify a nested python list into a structured tree (while retaining order)将嵌套 python 列表简化为结构化树的最佳方法(同时保留顺序)
【发布时间】:2019-09-22 05:13:43
【问题描述】:

假设我有一个如下所示的 Python 3.6 列表:

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

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

>>> print(RenderTree(l1))

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

如果有任何帮助,请将对象 a、b、c、d、e 视为字符串。我目前已经阅读了很多关于 anytree 的文档,并在 StackOverflow 上搜索了一段时间,但找不到任何可以帮助我解决这个问题的东西。我可以解决这个问题的最 Pythonic 方式是什么?

编辑:澄清一下,原始列表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 个不同的子分支,而不是1 个。所以,像这样的一棵树:

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

将被描述为:

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

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

【问题讨论】:

  • 您可以将您的代码发布为minimal reproducible example吗?
  • 我该怎么做?问题是我的问题也很抽象。可能还有其他一些隐含的条件,我稍后会补充
  • SO 不是免费的编码、设计或研究服务。如果某些东西过于抽象,那意味着你需要做更多的研究。转储要求不是获得答案的好方法。
  • 嗯,我绝对同意你@MadPhysicist。我对在 StackOverflow 上发布问题还很陌生。你建议我如何详细说明这个问题?
  • 您可以导入库并尝试将列表组织成库接受的格式。从文档看来,它使用了marc = Node("Marc", parent=udo) 格式。即使在解释之后,我也不清楚l1 如何代表一棵树。你能用图表阐明实际结构吗?谢谢。

标签: python anytree


【解决方案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])

【讨论】:

  • 感谢@Ajax1234 的回答。我尝试了你的解决方案,它就像一个魅力。然而,当我给它另一个测试用例data = [['a', 'b', 'c'], ['b', 'c'], ['c'], ['d', 'e', 'f', 'g'], ['e', 'f'], ['g']] 时,树返回如下:{'a': {'b': 'c'}, 'd': {'e': {'f': 'g'}}},而不是{'a': {'b': 'c'}, 'd': {'e': 'f', 'g'}}。当表示为一棵树时,它将类似于: l1 |__a | |__b | |__c |__d | |__e | |__f | |__g
  • @AliAbbas 感谢您的评论。 {'a': {'b': 'c'}, 'd': {'e': 'f', 'g'}} 是无效的字典,因为 'g' 需要作为键/值配对。你能澄清一下你的结构应该如何创建吗?你的意思是结果应该是{'a': {'b': 'c'}, 'd': {'e':[ 'f', 'g']}}?
  • 所以@Ajax1234,我知道它无效,但没有时间简化:结果应该类似于:{'a': {'b': 'c'}, 'd': {'e':[ 'f'], 'g':''}}。即:元素g 应该是 d 下的子元素,而不是 e,与 e 处于同一级别。这有意义吗?
  • @AliAbbas 树结构的规则相当不清楚,例如,您将['a', 'b', 'c'] 构造为{'a': {'b': 'c'}}(序列嵌套),但是['d', 'e', 'f', 'g'] 变成{'d': {'e':[ 'f'], 'g':''}}。是什么决定了最后一个元素是嵌套的子元素还是带有空列表的父元素?
  • 是的,我同意他们目前有点不清楚。我也会在帖子中简化它们:所以,树的表示方式是我有一个函数,它返回一个类似l1 的列表,并且背后有以下逻辑:列表中的每个元素都有 2 个部分。家长,还有孩子。父元素是列表中的第一个元素,其他所有元素都是它的子元素,或者是子元素的子元素,依此类推。所以像:[a, b, c] 这样的元素代表所有
猜你喜欢
  • 1970-01-01
  • 2018-01-23
  • 2015-06-23
  • 1970-01-01
  • 1970-01-01
  • 2018-06-23
  • 2017-10-15
  • 2019-06-01
  • 1970-01-01
相关资源
最近更新 更多