【问题标题】:What is the fastest way to flatten arbitrarily nested lists in Python? [duplicate]在 Python 中展平任意嵌套列表的最快方法是什么? [复制]
【发布时间】:2012-06-05 03:16:21
【问题描述】:

可能重复:
Flattening a shallow list in Python
Flatten (an irregular) list of lists in Python

编辑:问题不在于 如何 - 这是 discussed 在其他 questions - 问题是,这是 最快的方法?

我之前已经找到了解决方案,但我想知道最快的解决方案是扁平化包含其他任意长度列表的列表。

例如:

[1, 2, [3, 4, [5],[]], [6]]

会变成:

[1,2,3,4,5,6]

可以有无限多个级别。一些列表对象可以是字符串,它们不能在输出列表中被展平为它们的连续字符。

【问题讨论】:

  • This answer 来自previously asked question 应该做你想做的事。 (投票以重复结束)。
  • @GreenMatt,如果 question 相同,则它只是重复,而不是答案。很明显,另一个问题是针对单层嵌套的,更简单的解决方案更合适。
  • @Mark Ransom:我以为我以前见过这个,这就是我在搜索时发现的。我猜我没有仔细阅读这个问题。
  • 请停止删除系统生成的“可能重复”横幅。第二个链接有您问题的答案。
  • 不同意,答案可能恰好相同,但该问题需要适用于任意嵌套列表,因此不是重复问题。

标签: python algorithm optimization


【解决方案1】:

它没有 是递归的。事实上,由于函数调用涉及的开销,迭代解决方案通常更快。这是我不久前写的一个迭代版本:

def flatten(items, seqtypes=(list, tuple)):
    for i, x in enumerate(items):
        while i < len(items) and isinstance(items[i], seqtypes):
            items[i:i+1] = items[i]
    return items

尚未测试此特定实现的性能,但它可能不是那么好,因为所有切片分配最终可能会移动大量内存。不过,不要假设它必须是递归的,或者这样写更简单。

这种实现确实具有“就地”展平列表而不是返回副本的优点,递归解决方案总是这样做的。当内存紧张时,这可能很有用。如果你想要一个扁平化的副本,只需传入你想要扁平化的列表的浅层副本:

flatten(mylist)                # flattens existing list
newlist = flatten(mylist[:])   # makes a flattened copy

此外,此算法不受 Python 递归限制的限制,因为它不是递归的。不过,我敢肯定这实际上永远不会发挥作用。

2021 年编辑:在我看来,使用try/except 可能会更好地处理列表末尾的检查,因为它只会发生一次,并且将测试从主循环中取出可以提供性能优势。看起来像:

def flatten(items, seqtypes=(list, tuple)):
    try:
        for i, x in enumerate(items):
            while isinstance(items[i], seqtypes):    
                items[i:i+1] = items[i]
    except IndexError:
        pass
    return items

通过进一步调整以使用由enumerate 返回的x 而不是访问items[i] 这么多,你会得到这个,它比顶部的原始版本稍微快或显着快,具体取决于大小和列表的结构。

def flatten(items, seqtypes=(list, tuple)):
    try:
        for i, x in enumerate(items):
            while isinstance(x, seqtypes):    
                items[i:i+1] = x
                x = items[i]
    except IndexError:
        pass
    return items

【讨论】:

  • @Mark 这是一次性代码:)。做笔记以警告未来的编辑并继续您的生活。
  • @Triptych,我不太明白你的评论。
  • enumerate() 非常简单,并且被记录为惰性(即,返回产生顺序索引值的迭代器)。列表中iter() 的行为(使用带有__getitem__() 的递增索引,并在每次迭代中将其与len() 进行比较,即使长度发生变化也可以使用)也被记录下来。因此,尽管它看起来有点像 hack,但除非对语言进行重大更改,否则它实际上是相当安全的。
  • 范围而不是枚举也可以解决问题。
  • 我很惊讶这没有更多的选票。我认为这是因为您必须仔细阅读才能理解它在做什么。
【解决方案2】:

这个函数应该能够在不使用任何递归的情况下快速扁平化嵌套的、可迭代的容器:

import collections

def flatten(iterable):
    iterator = iter(iterable)
    array, stack = collections.deque(), collections.deque()
    while True:
        try:
            value = next(iterator)
        except StopIteration:
            if not stack:
                return tuple(array)
            iterator = stack.pop()
        else:
            if not isinstance(value, str) \
               and isinstance(value, collections.Iterable):
                stack.append(iterator)
                iterator = iter(value)
            else:
                array.append(value)

大约五年后,我对这件事的看法发生了变化,这可能会更好用:

def main():
    data = [1, 2, [3, 4, [5], []], [6]]
    print(list(flatten(data)))


def flatten(iterable):
    iterator, sentinel, stack = iter(iterable), object(), []
    while True:
        value = next(iterator, sentinel)
        if value is sentinel:
            if not stack:
                break
            iterator = stack.pop()
        elif isinstance(value, str):
            yield value
        else:
            try:
                new_iterator = iter(value)
            except TypeError:
                yield value
            else:
                stack.append(iterator)
                iterator = new_iterator


if __name__ == '__main__':
    main()

【讨论】:

    【解决方案3】:

    这是一种对字符串友好的递归方法:

    nests = [1, 2, [3, 4, [5],['hi']], [6, [[[7, 'hello']]]]]
    
    def flatten(container):
        for i in container:
            if isinstance(i, (list,tuple)):
                for j in flatten(i):
                    yield j
            else:
                yield i
    
    print list(flatten(nests))
    

    返回:

    [1, 2, 3, 4, 5, 'hi', 6, 7, 'hello']
    

    请注意,这并不能保证速度或开销使用,但说明了一个递归解决方案,希望会有所帮助。

    【讨论】:

    • 这只是yield的一个漂亮的应用。干得好……
    • 在 Python 3.3+ 中,您可以使用 yield from flatten(i) 代替 for j in flatten(i): yield j
    • 你也可以改用if isinstance(i, (list, tuple)):。但无论如何都是很好的解决方案。
    • 如果是一套呢?我用if hasattr(i, '__iter__'):
    • 字符串类型导致无限递归hastattr(i, '__iter__')。我过滤掉了if hasattr(i, '__iter__') and not isinstance(i, str):
    猜你喜欢
    • 2012-06-12
    • 2012-11-18
    • 2016-12-17
    • 1970-01-01
    • 2013-10-06
    • 2012-08-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多