给出一个任意大小的任意深度嵌套列表的列表,我想在树中的所有元素上使用一个平坦的,深度优先的迭代器,但是路径指示也是如此:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>Sample 1</li>
<li>Sample 2</li>
<li>Sample 3</li>
<li>Sample 4</li>
<li>Sample 5</li>
<li>Sample 6</li>
<li>Sample 7</li>
<li>Sample 8</li>
<li>Sample 9</li>
<li>Sample 10</li>
<li>Sample 1</li>
<li>Sample 2</li>
<li>Sample 3</li>
<li>Sample 4</li>
<li>Sample 5</li>
<li>Sample 6</li>
<li>Sample 7</li>
<li>Sample 8</li>
<li>Sample 9</li>
<li>Sample 10</li>
</ul>
那是
for x, y in flatten(L), x == L[y[0]][y[1]]...[y[-1]].
应该产生:
L = [[[1, 2, 3], [4, 5]], [6], [7,[8,9]], 10]
flatten(L)
我使用带有(1, (0, 0, 0)),
(2, (0, 0, 1)),
(3, (0, 0, 2)),
(4, (0, 1, 0)),
(5, (0, 1, 1)),
(6, (1, 0)),
(7, (2, 0)),
(8, (2, 1, 0)),
(9, (2, 1, 1)),
(10, (3,))
语句的生成器为此做了一个递归实现:
yield
但我不认为它是一个好的或负责任的实现,是否有任何配方可以更普遍地使用内置函数或std lib这样的东西,如def flatten(l):
for i, e in enumerate(l):
try:
for x, y in flatten(e):
yield x, (i,) + y
except:
yield e, (i,)
?
答案 0 :(得分:3)
我认为你自己的解决方案没问题,没有什么比这更简单,而且Python的标准库也无济于事。但这是另一种方式,它以迭代方式而不是递归方式工作,因此它可以处理非常深层嵌套的列表。
def flatten(l):
stack = [enumerate(l)]
path = [None]
while stack:
for path[-1], x in stack[-1]:
if isinstance(x, list):
stack.append(enumerate(x))
path.append(None)
else:
yield x, tuple(path)
break
else:
stack.pop()
path.pop()
我将当前“活动”列表保留在enumerate
迭代器堆栈上,并将当前索引路径保留为另一个堆栈。然后在while循环中,我总是尝试从当前列表中获取下一个元素并适当地处理它:
enumerate
迭代器推送到堆栈上,并为索引路径堆栈上的更深层索引腾出空间。演示:
>>> L = [[[1, 2, 3], [4, 5]], [6], [7,[8,9]], 10]
>>> for entry in flatten(L):
print(entry)
(1, (0, 0, 0))
(2, (0, 0, 1))
(3, (0, 0, 2))
(4, (0, 1, 0))
(5, (0, 1, 1))
(6, (1, 0))
(7, (2, 0))
(8, (2, 1, 0))
(9, (2, 1, 1))
(10, (3,))
请注意,如果您即时处理条目,就像打印一样,那么您只需将路径作为列表生成,即使用yield x, path
。演示:
>>> for entry in flatten(L):
print(entry)
(1, [0, 0, 0])
(2, [0, 0, 1])
(3, [0, 0, 2])
(4, [0, 1, 0])
(5, [0, 1, 1])
(6, [1, 0])
(7, [2, 0])
(8, [2, 1, 0])
(9, [2, 1, 1])
(10, [3])
这样,迭代器只需要整个迭代的O(n)时间,其中n是结构中对象的总数(列表和数字)。当然,打印增加了复杂性,就像创建元组一样。但那就是在发电机之外和打印的“故障”或者你在每条路径上做的任何事情。例如,如果您只查看每个路径的长度而不是其内容,这需要O(1),那么整个事实实际上就是O(n)。
所有这些说再说一遍,我认为你自己的解决方案没问题。而且显然比这简单。就像我在@ naomik的answer下评论一样,我认为你的解决方案无法处理大约1000或更多的深度列表是相当无关紧要的。人们首先应该没有这样的清单。如果有人这样做,那就是应该修复的错误。如果列表也可以 wide ,就像你的情况一样,并且是平衡的,那么即使分支因子只有2,你的内存也会在100以下的深度耗尽,你就不会' t得到任何接近1000.如果列表不能变宽,那么嵌套列表是错误的数据结构选择,加上你不会对索引路径感兴趣。如果它可以变宽但不,那么我会说应该改进创建算法(例如,如果它代表一个排序树,添加平衡)。 / p>
再次关于我的解决方案:除了能够处理任意深度列表及其效率之外,我发现其中一些细节值得注意:
enumerate
对象存储在某处。通常它们只是直接用在循环和Co中,例如for i, x in enumerate(l):
。path[-1]
点并使用for path[-1], x in ...
写入其中。for
- 循环与直接break
和else
分支,迭代下一个单一值,句柄优雅地结束,而不try
/ {{1没有except
和一些默认值。next
,即不将每个路径转换为元组,那么您确实需要在迭代期间直接处理它。例如,如果您执行yield x, path
,则会获得list(flatten(L))
。也就是说,“所有”索引路径将为空。当然那是因为实际上只有一个路径对象,我一遍又一遍地更新并屈服,最后它是空的。这与[(1, []), (2, []), (3, []), (4, []), (5, []), (6, []), (7, []), (8, []), (9, []), (10, [])]
非常相似,例如itertools.groupby
为您提供[list(g) for _, g in list(groupby('aaabbbb'))]
。这不是一件坏事。我最近wrote about that extensively。较短的版本,其中一个堆栈同时包含索引和[[], ['b']]
个对象:
enumerate
答案 1 :(得分:0)
递归是展平深层嵌套列表的好方法。您的实施也做得很好。我建议用similar recipe修改它,如下所示:
<强>代码强>
from collections import Iterable
def indexed_flatten(items):
"""Yield items from any nested iterable."""
for i, item in enumerate(items):
if isinstance(item, Iterable) and not isinstance(item, (str, bytes)):
for item_, idx in indexed_flatten(item):
yield item_, (i,) + idx
else:
yield item, (i,)
lst = [[[1, 2, 3], [4, 5]], [6], [7, [8, 9]], 10]
list(indexed_flatten(lst))
输出
[(1, (0, 0, 0)),
(2, (0, 0, 1)),
(3, (0, 0, 2)),
(4, (0, 1, 0)),
(5, (0, 1, 1)),
(6, (1, 0)),
(7, (2, 0)),
(8, (2, 1, 0)),
(9, (2, 1, 1)),
(10, (3,))]
这可以很好地适用于许多项目类型,例如[[[1, 2, 3], {4, 5}], [6], (7, [8, "9"]), 10]
。