Python:使用索引展平嵌套列表

时间:2018-02-26 19:51:29

标签: python recursion iterator nested-lists flatten

给出一个任意大小的任意深度嵌套列表的列表,我想在树中的所有元素上使用一个平坦的,深度优先的迭代器,但是路径指示也是如此:

<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,)

2 个答案:

答案 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 - 循环与直接breakelse分支,迭代下一个单一值,句柄优雅地结束,而不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]