展平不规则(任意嵌套)列表

Flatten an irregular (arbitrarily nested) list of lists

提问人:telliott99 提问时间:1/29/2010 最后编辑:Karl Knechteltelliott99 更新时间:9/20/2023 访问量:206549

问:

是的,我知道这个主题之前已经讨论过:

但据我所知,除了一个解决方案外,所有解决方案在像这样的列表中都失败了,其中所需的输出是(或者更好的是迭代器)。[[[1, 2, 3], [4, 5]], 6][1, 2, 3, 4, 5, 6]

我看到的唯一适用于任意嵌套的解决方案是在这个问题中找到的:

def flatten(x):
    result = []
    for el in x:
        if hasattr(el, "__iter__") and not isinstance(el, basestring):
            result.extend(flatten(el))
        else:
            result.append(el)
    return result

这是最好的方法吗?我忽略了什么吗?有什么问题吗?

Python 优化 套列表 扁平化

评论

32赞 Mittenchops 3/18/2014
事实上,在这个问题上有这么多的答案和这么多的行动,这确实表明这应该是某个地方的内置功能,对吧?尤其糟糕的是,compiler.ast 已从 Python 3.0 中删除
3赞 clay 4/8/2015
我想说的是,Python 真正需要的是不间断的递归,而不是另一个内置的。
4赞 Azat Ibrakov 5/23/2019
@Mittenchops:完全不同意,人们使用明显糟糕的 API/过于复杂的数据结构(请注意:s 旨在是同构的)这一事实并不意味着这是 Python 的错,我们需要一个内置的来完成这样的任务list
7赞 viddik13 3/5/2020
如果你有能力为你的项目添加一个包 - 我想 more_itertools.collapse 解决方案会做得最好。从这个答案: stackoverflow.com/a/40938883/3844376
0赞 lindes 2/8/2021
@viddik13:请考虑将其作为这个问题的答案。这绝对会得到我的赞成票。(我同意 Mittenchops 的观点。它不是一个内置函数的事实很好(re Azat Ibrakov),但应该有一个库例程(而且,显然,是!)来做到这一点。(因为:并非所有的违规行为都是“坏的”/“过于复杂”。有时,它只是......不定期,没关系。 恕我直言。只要它是什么,它的定义很好,它可以是,而且仍然是不规则的(“任意嵌套的列表(列表(列表的))的整数“,例如,定义得很好)。

答:

68赞 Josh Lee 1/29/2010 #1

我的解决方案:

import collections


def flatten(x):
    if isinstance(x, collections.Iterable):
        return [a for i in x for a in flatten(i)]
    else:
        return [x]

更简洁一点,但几乎相同。

评论

7赞 abarnert 7/5/2013
如果你只是为了测试它是否可迭代,你可以在不导入任何东西的情况下做到这一点......但我不认为必须导入 stdlib 模块是一个值得避免的缺点。try: iter(x)
8赞 Nir Alfasi 12/7/2014
值得注意的是,此解决方案仅在所有项目均为类型时才有效int
3赞 Zero 7/26/2017
可以使它更简洁,但可读性在这里可能是主观的。def flatten(x): return [a for i in x for a in flatten(i)] if isinstance(x, collections.Iterable) else [x]
5赞 aandis 11/29/2018
这不适用于字符串,因为字符串也是可迭代的。将条件替换为if isinstance(x, collections.Iterable) and not isinstance(x, basestring)
0赞 noobninja 6/17/2020
替换为collections.Iterablelist
475赞 Cristian 1/29/2010 #2

使用生成器函数可以使您的示例更易于阅读并提高性能。

Python 2 中文文档

使用 2.6 中添加的可迭代 ABC

from collections import Iterable

def flatten(xs):
    for x in xs:
        if isinstance(x, Iterable) and not isinstance(x, basestring):
            for item in flatten(x):
                yield item
        else:
            yield x

Python 3 (英语)

在 Python 3 中,不再是,但元组给出了相同的效果。此外,运算符的产量一次从生成器返回一个项目。basestring(str, bytes)

from collections.abc import Iterable

def flatten(xs):
    for x in xs:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            yield from flatten(x)
        else:
            yield x

评论

9赞 JWL 6/7/2012
在此页面上的所有建议中,这是唯一一个在我这样做时快速展平此列表的建议。所有其他人,都会开始工作并永远工作!l = ([[chr(i),chr(i-32)] for i in xrange(ord('a'), ord('z')+1)] + range(0,9))list(flatten(l))
8赞 josch 6/21/2015
这也使词典扁平化。也许你想用代替?collections.Sequencecollections.Iteratable
3赞 RolKau 8/18/2015
这不适用于最初未列出的内容,例如。这可以通过将 -test 和 else-子句移到 -loop 之外来解决。(然后你可以向它扔任何东西,它会从中制作一个扁平化的列表)for i in flatten(42): print (i)isinstancefor el
6赞 dawg 9/30/2018
对于 Python 3.7,不推荐使用 using。请改用。collections.Iterablecollections.abc.Iterable
5赞 cglacet 3/11/2019
事实上,从不需要递归。在这种特定情况下,使用递归不是最好的解决方案,因为它会在深度嵌套列表(深度> 1000)上崩溃。但是,如果您不以安全为目标,那么是的,递归函数会更好,因为它们更容易读/写。
33赞 unutbu 1/29/2010 #3

此版本避免了 python 的递归限制(因此适用于任意深度的嵌套可迭代对象)。它是一个可以处理字符串和任意可迭代对象(甚至是无限可迭代对象)的生成器。flatten

import itertools
import collections

def flatten(iterable, ltypes=collections.Iterable):
    remainder = iter(iterable)
    while True:
        try:
            first = next(remainder)
        except StopIteration:
            break
        if isinstance(first, ltypes) and not isinstance(first, (str, bytes)):
            remainder = itertools.chain(first, remainder)
        else:
            yield first

以下是一些示例来演示其用法:

print(list(itertools.islice(flatten(itertools.repeat(1)),10)))
# [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

print(list(itertools.islice(flatten(itertools.chain(itertools.repeat(2,3),
                                       {10,20,30},
                                       'foo bar'.split(),
                                       itertools.repeat(1),)),10)))
# [2, 2, 2, 10, 20, 30, 'foo', 'bar', 1, 1]

print(list(flatten([[1,2,[3,4]]])))
# [1, 2, 3, 4]

seq = ([[chr(i),chr(i-32)] for i in range(ord('a'), ord('z')+1)] + list(range(0,9)))
print(list(flatten(seq)))
# ['a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'E', 'f', 'F', 'g', 'G', 'h', 'H',
# 'i', 'I', 'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n', 'N', 'o', 'O', 'p', 'P',
# 'q', 'Q', 'r', 'R', 's', 'S', 't', 'T', 'u', 'U', 'v', 'V', 'w', 'W', 'x', 'X',
# 'y', 'Y', 'z', 'Z', 0, 1, 2, 3, 4, 5, 6, 7, 8]

虽然可以处理无限生成器,但它不能处理无限嵌套:flatten

def infinitely_nested():
    while True:
        yield itertools.chain(infinitely_nested(), itertools.repeat(1))

print(list(itertools.islice(flatten(infinitely_nested()), 10)))
# hangs

评论

1赞 wim 9/16/2013
关于是否使用 ABC Iterable 或 ABC Sequence,是否有任何共识?
0赞 unutbu 9/16/2013
sets、 、 、 、 、扁平化 a 的结果有点不稳定,但除此之外,我认为是比 .这绝对是更自由的。dictsdequeslistiteratorsgenerators__iter__collections.Iterablecollections.Sequencedictcollections.Iterablecollections.Sequence
0赞 unutbu 9/17/2013
@wim:使用的一个问题是,这包括无限生成器。我已经改变了我的答案处理这种情况。collections.Iterable
2赞 Georgy 5/23/2019
这似乎不适用于第 3 个和第 4 个示例。它抛出.另外,看起来可以替换为 .StopIterationwhile True: first = next(remainder)for first in remainder:
1赞 baduker 2/24/2020
@Georgy这可以通过将扁平体封装在 .try-except StopIteration block
36赞 Alex Martelli 1/29/2010 #4

@unutbu 非递归解决方案的生成器版本,正如 @Andrew 在评论中要求的那样:

def genflat(l, ltypes=collections.Sequence):
    l = list(l)
    i = 0
    while i < len(l):
        while isinstance(l[i], ltypes):
            if not l[i]:
                l.pop(i)
                i -= 1
                break
            else:
                l[i:i + 1] = l[i]
        yield l[i]
        i += 1

此生成器的略微简化版本:

def genflat(l, ltypes=collections.Sequence):
    l = list(l)
    while l:
        while l and isinstance(l[0], ltypes):
            l[0:1] = l[0]
        if l: yield l.pop(0)

评论

2赞 Andrew Wagner 1/29/2010
这是由嵌套列表形成的树的预排序遍历。只返回叶子。请注意,无论好坏,此实现都将使用原始数据结构。编写一个既保留原始树又不必复制列表条目的树可能会很有趣。
7赞 c-urchin 11/30/2010
我认为您需要测试字符串 - 例如添加“而不是 isinstance(l[0], basestring)”,就像在 Cristian 的解决方案中一样。否则,你会得到一个围绕 l[0:1] = l[0] 的无限循环
0赞 Daniel 'Dang' Griffith 10/26/2012
这是制作生成器的一个很好的例子,但正如 c-urchin 所提到的,当序列包含字符串时,算法本身会失败。
18赞 kev 1/4/2011 #5
def flatten(xs):
    res = []
    def loop(ys):
        for i in ys:
            if isinstance(i, list):
                loop(i)
            else:
                res.append(i)
    loop(xs)
    return res

评论

1赞 mattino 9/2/2021
这看起来非常优雅和简单。为什么它没有更多的赞成票?这个解决方案有什么问题吗?
1赞 pradeepvaranasi 7/22/2022
这是一个绝妙的解决方案!
0赞 Rolf of Saxony 11/24/2022
优雅的解决方案
5赞 clay 1/13/2011 #6

我更喜欢简单的答案。没有发电机。没有递归或递归限制。只是迭代:

def flatten(TheList):
    listIsNested = True

    while listIsNested:                 #outer loop
        keepChecking = False
        Temp = []

        for element in TheList:         #inner loop
            if isinstance(element,list):
                Temp.extend(element)
                keepChecking = True
            else:
                Temp.append(element)

        listIsNested = keepChecking     #determine if outer loop exits
        TheList = Temp[:]

    return TheList

这适用于两个列表:内部 for 循环和外部 while 循环。

内部 for 循环遍历列表。如果它找到一个列表元素,它 (1) 使用 list.extend() 将该部分扁平化一级嵌套,(2) 将 keepChecking 切换为 True。keepchecking 用于控制外部 while 循环。如果外部循环设置为 true,则会触发内部循环进行另一次传递。

这些传递会一直发生,直到找不到更多的嵌套列表。当最终在未找到任何通道的情况下发生传递时,keepChecking 永远不会跳闸为 true,这意味着 listIsNested 保持 false 并且外部 while 循环退出。

然后返回扁平化列表。

试运行

flatten([1,2,3,4,[100,200,300,[1000,2000,3000]]])

[1, 2, 3, 4, 100, 200, 300, 1000, 2000, 3000]

评论

0赞 telliott99 1/13/2011
我也喜欢简单。但是,在本例中,您可以迭代列表的次数与嵌套或级别的次数一样多。可能会变得昂贵。
0赞 clay 1/14/2011
@telliott99:如果你的列表真的很大和/或嵌套得很深,你是对的。但是,如果不是这种情况,那么更简单的解决方案同样有效,并且没有其他一些答案的深刻魔力。多阶段递归生成器推导有一个地方,但我不相信这应该是你首先关注的地方。(我想你知道我在“越差越好”的辩论中的位置。
0赞 clay 1/14/2011
@telliott99:或者换一种说法,你不必“尝试格罗克”我的解决方案。如果性能不是瓶颈,那么作为程序员,对你来说最重要的是什么?
0赞 dash-tom-bang 4/17/2014
更简单的解决方案具有较少的逻辑。递归是一个非常基本的编程结构,任何认为自己是程序员的人都应该完全适应。生成器在很大程度上是 Python 的方式,并且(以及理解)是任何专业的 Python 程序员都应该立即摸索的东西。
1赞 clay 4/17/2014
我同意递归。当我写下我的答案时,python 仍然在 1000 个周期时破坏了递归。他们改变了这一点吗?至于成为一名专业的 python 程序员,我不是。此外,我想很多用 python 编程的人并不是全职这样做的。
11赞 clay 1/15/2011 #7

这是另一个更有趣的答案......

import re

def Flatten(TheList):
    a = str(TheList)
    b,_Anon = re.subn(r'[\[,\]]', ' ', a)
    c = b.split()
    d = [int(x) for x in c]

    return(d)

基本上,它将嵌套列表转换为字符串,使用正则表达式去除嵌套语法,然后将结果转换回(扁平化)列表。

评论

0赞 abarnert 9/10/2014
如果您尝试将其推广到 int 值以外的其他值,例如,:)另一方面,给定一个包含自身的列表,它会比其他答案做得更好一些,引发异常,而不是只是循环直到内存耗尽/递归直到耗尽堆栈......[['C=64', 'APPLE ]['], ['Amiga', 'Mac', 'ST']]
0赞 clay 9/10/2014
最初的提示是关于扁平化整数列表。如果您只是将列表推导式更改为 d=[x for x for x in c],它应该适用于您的样本。
0赞 abarnert 9/11/2014
首先,只是一种缓慢而冗长的复制方式,那么为什么要这样做呢?其次,你的代码显然会转换为 ,因为它不处理引号,它只是假设任何括号都是列表括号。[x for x in c]c'APPLE ][''APPLE '
0赞 clay 9/11/2014
医 管 局!您的评论在我的计算机上格式化的方式,我什至没有意识到这应该是旧计算机上出现的 Apple II。无论如何,我对你们这两个问题的回答是,这个练习——对我来说——只是一个实验,旨在找到一个创造性的解决方案来扁平化一个列表。我不确定我是否会将其推广到将每个列表扁平化。
0赞 Jorge Orpinel Pérez 1/22/2019
你只需要,然后真的。查看 github.com/jorgeorpinel/flatten_nested_lists/blob/master/...arr_str = str(arr)[int(s) for s in re.findall(r'\d+', arr_str)]
6赞 Xolve 3/8/2011 #8

虽然已经选择了一个优雅且非常蟒蛇般的答案,但我会提出我的解决方案,只是为了复习:

def flat(l):
    ret = []
    for i in l:
        if isinstance(i, list) or isinstance(i, tuple):
            ret.extend(flat(i))
        else:
            ret.append(i)
    return ret

请说出这段代码的好坏?

评论

2赞 dansalmo 7/7/2013
用。初始化空变量是我寻找替代代码结构的标志,通常是推导式、生成器、递归等。isinstance(i, (tuple, list))
3赞 dash-tom-bang 4/17/2014
return type(l)(ret)也会让您获得与传入相同的容器类型。:)
0赞 Xolve 4/19/2014
@dash-tom-bang 你能详细解释一下它的含义吗?
2赞 dash-tom-bang 6/14/2014
如果传入列表,则可能需要返回列表。如果传入元组,则可能需要返回元组。如果你把两者混在一起,你会得到外面的东西。
46赞 samplebias 3/24/2011 #9

这是我的递归扁平化函数版本,它处理元组和列表,并允许您抛出任何位置参数的组合。返回一个生成器,该生成器按顺序生成整个序列,逐个 arg:

flatten = lambda *n: (e for a in n
    for e in (flatten(*a) if isinstance(a, (tuple, list)) else (a,)))

用法:

l1 = ['a', ['b', ('c', 'd')]]
l2 = [0, 1, (2, 3), [[4, 5, (6, 7, (8,), [9]), 10]], (11,)]
print list(flatten(l1, -2, -1, l2))
['a', 'b', 'c', 'd', -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

评论

3赞 Kristof Pal 11/20/2013
很好的解决方案,但是如果您添加一些注释来描述什么,将会很有帮助 , , 参考ean
3赞 Dennis Williamson 1/6/2014
@WolfgangKuehne:尝试 , (或较短的,或者您可能更喜欢的) for 和 for , 所以:argsnintermediatemidelementaresulteflatten = lambda *args: (result for mid in args for result in (flatten(*mid) if isinstance(mid, (tuple, list)) else (mid,)))
0赞 bcdan 7/31/2015
这比 要快得多。伟大的、紧凑的代码,适用于任何(我认为)对象类型。compiler.ast.flatten
0赞 Alexej Magura 8/3/2020
这是我在适度的谷歌搜索中遇到的唯一解决方案,在任何实际适用于嵌套深度超过一个级别的列表的网站上。
1赞 Jeff Hykin 10/26/2020
这是一件艺术品。这么少的字符,仍然几乎无法理解。我见过🏌️ ♂️🏌️ ♀️⛳️的 10/10 最好的 Python 代码高尔夫。这么短的东西几乎弥补了 Python 没有内置扁平化函数的事实。
1赞 Bogdan 8/12/2011 #10

我在这里没有看到这样的东西,只是从一个关于同一主题的封闭式问题中得出的,但为什么不做这样的事情(如果你知道你要拆分的列表类型):

>>> a = [1, 2, 3, 5, 10, [1, 25, 11, [1, 0]]]    
>>> g = str(a).replace('[', '').replace(']', '')    
>>> b = [int(x) for x in g.split(',') if x.strip()]

你需要知道元素的类型,但我认为这可以概括,就速度而言,我认为它会更快。

评论

0赞 DanB 6/9/2012
这很聪明(而且可能很快)......但它不是很蟒蛇。
8赞 gb. 8/23/2012
“为什么不做这样的事情,”你说?因为它很容易打破!非常糟糕的主意。举个例子,如果你的项目是字符串,而不是整数,该怎么办?然后,如果一个字符串包含一个“[”,你就注定要失败。如果您的项目没有好的(或很长的)字符串表示形式怎么办?
0赞 Zion 11/14/2015
@gb。那么,如果这是操作所需要的呢?这个例子显然是一个列表,所以“如果”在这里不适用,如果 OP 另有说明,但话又说回来,他没有这样做,这是根据给出的最简单、最有效的答案之一。ints
2赞 gb. 11/16/2015
对不起,“假设”适用,仔细考虑所有“假设”是编程的血液和胆量。
49赞 dansalmo 1/24/2013 #11

使用递归和鸭子类型的生成器(针对 Python 3 进行了更新):

def flatten(L):
    for item in L:
        try:
            yield from flatten(item)
        except TypeError:
            yield item

list(flatten([[[1, 2, 3], [4, 5]], 6]))
>>>[1, 2, 3, 4, 5, 6]

评论

1赞 dansalmo 9/8/2015
谢谢,这对 Python 3 很有效。对于 2.x,需要前一个:for i in flatten(item): yield i
0赞 sten 3/19/2018
list(flatten([['X'], 'Y'])) 在 2.X 变体上失败
0赞 dansalmo 3/20/2018
@user1019129看到我上面的评论
0赞 sten 3/21/2018
是的,它会随着循环而失败。.我认为因为字符串也是一个字符的“数组”
9赞 Noctis Skytower 7/26/2013 #12

尝试创建一个可以在 Python 中扁平化不规则列表的函数很有趣,但当然这就是 Python 的用途(让编程变得有趣)。以下生成器运行良好,但有一些注意事项:

def flatten(iterable):
    try:
        for item in iterable:
            yield from flatten(item)
    except TypeError:
        yield iterable

它将展平您可能希望保留的数据类型(如 、 和 objects)。此外,该代码依赖于这样一个事实,即从不可迭代对象请求迭代器会引发 .bytearraybytesstrTypeError

>>> L = [[[1, 2, 3], [4, 5]], 6]
>>> def flatten(iterable):
    try:
        for item in iterable:
            yield from flatten(item)
    except TypeError:
        yield iterable


>>> list(flatten(L))
[1, 2, 3, 4, 5, 6]
>>>

编辑:

我不同意以前的实现。问题在于,你不应该能够展平一些不可迭代的东西。这是令人困惑的,并给人以错误的论点印象。

>>> list(flatten(123))
[123]
>>>

下面的生成器与第一个生成器几乎相同,但没有尝试展平不可迭代对象的问题。正如人们所期望的那样,当给出不适当的参数时,它会失败。

def flatten(iterable):
    for item in iterable:
        try:
            yield from flatten(item)
        except TypeError:
            yield item

测试生成器可以正常使用提供的列表。但是,当新代码被赋予一个不可迭代的对象时,它将引发一个。下面显示了新行为的示例。TypeError

>>> L = [[[1, 2, 3], [4, 5]], 6]
>>> list(flatten(L))
[1, 2, 3, 4, 5, 6]
>>> list(flatten(123))
Traceback (most recent call last):
  File "<pyshell#32>", line 1, in <module>
    list(flatten(123))
  File "<pyshell#27>", line 2, in flatten
    for item in iterable:
TypeError: 'int' object is not iterable
>>>
2赞 pradyunsg 8/27/2013 #13

以下是 2.7.5 中的实现:compiler.ast.flatten

def flatten(seq):
    l = []
    for elt in seq:
        t = type(elt)
        if t is tuple or t is list:
            for elt2 in flatten(elt):
                l.append(elt2)
        else:
            l.append(elt)
    return l

有更好、更快的方法(如果你已经到达这里,你已经看到了它们)

另请注意:

自 2.6 版起不推荐使用:编译器包已在 Python 3 中删除。

7赞 Wilfred Hughes 12/10/2013 #14

下面是一个简单的函数,可以展平任意深度的列表。无递归,避免堆栈溢出。

from copy import deepcopy

def flatten_list(nested_list):
    """Flatten an arbitrarily nested list, without recursion (to avoid
    stack overflows). Returns a new list, the original list is unchanged.

    >> list(flatten_list([1, 2, 3, [4], [], [[[[[[[[[5]]]]]]]]]]))
    [1, 2, 3, 4, 5]
    >> list(flatten_list([[1, 2], 3]))
    [1, 2, 3]

    """
    nested_list = deepcopy(nested_list)

    while nested_list:
        sublist = nested_list.pop(0)

        if isinstance(sublist, list):
            nested_list = sublist + nested_list
        else:
            yield sublist

评论

0赞 Jorge Orpinel Pérez 1/22/2019
是的!与我在 github.com/jorgeorpinel/flatten_nested_lists/blob/master/ 的代码非常相似......
1赞 ATOMP 4/24/2022
通过使用而不是复制,可以提高效率。假设您可以控制嵌套列表的创建。此外,达到递归限制。collections.dequedeepcopy
1赞 Joran Beasley 5/29/2014 #15

完全是黑客,但我认为它会起作用(取决于你的data_type)

flat_list = ast.literal_eval("[%s]"%re.sub("[\[\]]","",str(the_list)))
1赞 Samy Vilar 8/18/2014 #16

这是另一种 py2 方法,我不确定它是最快的还是最优雅的,也不是最安全的......

from collections import Iterable
from itertools import imap, repeat, chain


def flat(seqs, ignore=(int, long, float, basestring)):
    return repeat(seqs, 1) if any(imap(isinstance, repeat(seqs), ignore)) or not isinstance(seqs, Iterable) else chain.from_iterable(imap(flat, seqs))

它可以忽略您想要的任何特定(或派生)类型,它返回一个迭代器,因此您可以将其转换为任何特定的容器,例如列表、元组、字典或简单地使用它以减少内存占用,无论好坏,它都可以处理初始不可迭代对象,例如 int ...

请注意,大部分繁重的工作都是在 C 中完成的,因为据我所知,这就是 itertools 的实现方式,所以虽然它是递归的,但 AFAIK 不受 python 递归深度的限制,因为函数调用发生在 C 中,尽管这并不意味着您受内存限制,尤其是在 OS X 中,其堆栈大小截至今天有硬性限制(OS X Mavericks)......

有一种稍微快一点的方法,但可移植性较差,只有在您可以假设可以显式确定输入的基本元素时才使用它,否则,您将获得无限递归,而堆栈大小有限的 OS X 将很快抛出分段错误......

def flat(seqs, ignore={int, long, float, str, unicode}):
    return repeat(seqs, 1) if type(seqs) in ignore or not isinstance(seqs, Iterable) else chain.from_iterable(imap(flat, seqs))

在这里,我们使用集合来检查类型,因此需要 O(1) vs O(类型数)来检查是否应该忽略一个元素,当然,任何具有所述忽略类型的派生类型的值都会失败,这就是它使用的原因,所以请谨慎使用它......strunicode

测试:

import random

def test_flat(test_size=2000):
    def increase_depth(value, depth=1):
        for func in xrange(depth):
            value = repeat(value, 1)
        return value

    def random_sub_chaining(nested_values):
        for values in nested_values:
            yield chain((values,), chain.from_iterable(imap(next, repeat(nested_values, random.randint(1, 10)))))

    expected_values = zip(xrange(test_size), imap(str, xrange(test_size)))
    nested_values = random_sub_chaining((increase_depth(value, depth) for depth, value in enumerate(expected_values)))
    assert not any(imap(cmp, chain.from_iterable(expected_values), flat(chain(((),), nested_values, ((),)))))

>>> test_flat()
>>> list(flat([[[1, 2, 3], [4, 5]], 6]))
[1, 2, 3, 4, 5, 6]
>>>  

$ uname -a
Darwin Samys-MacBook-Pro.local 13.3.0 Darwin Kernel Version 13.3.0: Tue Jun  3 21:27:35 PDT 2014; root:xnu-2422.110.17~1/RELEASE_X86_64 x86_64
$ python --version
Python 2.7.5
1赞 Oldyoung 10/1/2014 #17

我使用递归来解决任何深度的嵌套列表

def combine_nlist(nlist,init=0,combiner=lambda x,y: x+y):
    '''
    apply function: combiner to a nested list element by element(treated as flatten list)
    '''
    current_value=init
    for each_item in nlist:
        if isinstance(each_item,list):
            current_value =combine_nlist(each_item,current_value,combiner)
        else:
            current_value = combiner(current_value,each_item)
    return current_value

所以在我定义函数combine_nlist后,很容易使用这个函数做扁平化。或者您可以将其组合成一个函数。我喜欢我的解决方案,因为它可以应用于任何嵌套列表。

def flatten_nlist(nlist):
    return combine_nlist(nlist,[],lambda x,y:x+[y])

结果

In [379]: flatten_nlist([1,2,3,[4,5],[6],[[[7],8],9],10])
Out[379]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

评论

0赞 cglacet 8/2/2018
“具有任何深度的嵌套列表”不正确。试一试,你会看到:current_value = combiner(current_value,each_item) RecursionError: maximum recursion depth exceeded
0赞 Oldyoung 8/2/2018
嗯,你是想用超过 1000 层来展平列表吗?
0赞 cglacet 8/2/2018
当然,这就是讨论递归解决方案与迭代解决方案的重点。如果您事先知道层数<于 1000,那么最简单的解决方案将起作用。当您说“任何深度”时,这包括深度> 1000 的列表。
1赞 Nir Alfasi 12/7/2014 #18

在不使用任何库的情况下:

def flat(l):
    def _flat(l, r):    
        if type(l) is not list:
            r.append(l)
        else:
            for i in l:
                r = r + flat(i)
        return r
    return _flat(l, [])



# example
test = [[1], [[2]], [3], [['a','b','c'] , [['z','x','y']], ['d','f','g']], 4]    
print flat(test) # prints [1, 2, 3, 'a', 'b', 'c', 'z', 'x', 'y', 'd', 'f', 'g', 4]
1赞 Saksham Varma 2/27/2015 #19

用:itertools.chain

import itertools
from collections import Iterable

def list_flatten(lst):
    flat_lst = []
    for item in itertools.chain(lst):
        if isinstance(item, Iterable):
            item = list_flatten(item)
            flat_lst.extend(item)
        else:
            flat_lst.append(item)
    return flat_lst

或者不带链接:

def flatten(q, final):
    if not q:
        return
    if isinstance(q, list):
        if not isinstance(q[0], list):
            final.append(q[0])
        else:
            flatten(q[0], final)
        flatten(q[1:], final)
    else:
        final.append(q)
2赞 Zion 11/14/2015 #20

我很惊讶没有人想到这一点。该死的递归,我没有得到这里的高级人员做出的递归答案。无论如何,这是我对此的尝试。需要注意的是,它非常特定于 OP 的用例

import re

L = [[[1, 2, 3], [4, 5]], 6]
flattened_list = re.sub("[\[\]]", "", str(L)).replace(" ", "").split(",")
new_list = list(map(int, flattened_list))
print(new_list)

输出:

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

评论

0赞 Jake Schmidt 10/13/2020
这仅适用于可转换为字符串并返回的类型(如 int)。也不需要正则表达式的复杂性来解决这么简单的问题。如果您想要一个简单的解决方案,pradyunsg 是最好的。
1赞 YPCrumble 12/16/2015 #21

最简单的方法是使用 morph 库。pip install morph

代码为:

import morph

list = [[[1, 2, 3], [4, 5]], 6]
flattened_list = morph.flatten(list)  # returns [1, 2, 3, 4, 5, 6]
5赞 Shreyas 4/8/2016 #22

我没有在这里浏览所有已经可用的答案,但这里有一个我想出的行,借用了 lisp 的第一个和其余列表处理方式

def flatten(l): return flatten(l[0]) + (flatten(l[1:]) if len(l) > 1 else []) if type(l) is list else [l]

这是一个简单和一个不那么简单的案例——

>>> flatten([1,[2,3],4])
[1, 2, 3, 4]

>>> flatten([1, [2, 3], 4, [5, [6, {'name': 'some_name', 'age':30}, 7]], [8, 9, [10, [11, [12, [13, {'some', 'set'}, 14, [15, 'some_string'], 16], 17, 18], 19], 20], 21, 22, [23, 24], 25], 26, 27, 28, 29, 30])
[1, 2, 3, 4, 5, 6, {'age': 30, 'name': 'some_name'}, 7, 8, 9, 10, 11, 12, 13, set(['set', 'some']), 14, 15, 'some_string', 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
>>> 

评论

3赞 cs95 1/10/2018
这不是一个班轮。无论您如何尝试将其放入其中,它都是一条单独的线。此外,这是非常不可读的。def foo():
1赞 Emilio M Bumachar 2/6/2018
我已经对代码进行了一行解构,并进行了一些进一步的重构。(在我写这篇文章时,编辑正在等待同行评审)这种特殊的方法对我来说似乎非常可读,尽管原始代码确实需要一些重构。
0赞 Shreyas 6/16/2021
请不要编辑答案。如果您觉得需要“重构”,请随时发布您自己的答案。代码以这种方式呈现是有原因的。这是为了强调这种方法来自lisp。你可以完全忽略它的“单行”部分——它不是为了某种吹嘘。这再次表明,其背后的思想仍然是“单行”的:首先和其余列表的处理。
2赞 Leo wahyd 10/3/2016 #23

我知道已经有很多很棒的答案,但我想添加一个使用函数式编程方法来解决问题的答案。在这个答案中,我使用了双递归:

def flatten_list(seq):
    if not seq:
        return []
    elif isinstance(seq[0],list):
        return (flatten_list(seq[0])+flatten_list(seq[1:]))
    else:
        return [seq[0]]+flatten_list(seq[1:])

print(flatten_list([1,2,[3,[4],5],[6,7]]))

输出:

[1, 2, 3, 4, 5, 6, 7]
3赞 diligar 5/13/2017 #24

我不确定这是否一定更快或更有效,但这就是我所做的:

def flatten(lst):
    return eval('[' + str(lst).replace('[', '').replace(']', '') + ']')

L = [[[1, 2, 3], [4, 5]], 6]
print(flatten(L))

这里的函数将列表变成一个字符串,去掉所有的方括号,将方括号附加回两端,然后把它转回一个列表。flatten

虽然,如果你知道你的列表中会有方括号的字符串,比如 ,你将不得不做其他事情。[[1, 2], "[3, 4] and [5]"]

评论

0赞 cglacet 8/2/2018
与简单解决方案相比,这没有任何优势,因为它无法处理深度列表,即“RecursionError:获取对象的 repr 时超出了最大递归深度”。
1赞 Blair Nangle 11/10/2022
你这个的流氓。
1赞 Lee 12/5/2022
@BlairNangle我讨厌它,但我喜欢它。
10赞 MSeifert 7/29/2017 #25

您可以使用第三方软件包中的 deepflatten iteration_utilities

>>> from iteration_utilities import deepflatten
>>> L = [[[1, 2, 3], [4, 5]], 6]
>>> list(deepflatten(L))
[1, 2, 3, 4, 5, 6]

>>> list(deepflatten(L, types=list))  # only flatten "inner" lists
[1, 2, 3, 4, 5, 6]

它是一个迭代器,因此您需要迭代它(例如,通过将其包装或在循环中使用它)。在内部,它使用迭代方法而不是递归方法,并且它被编写为 C 扩展,因此它可以比纯 python 方法更快:list

>>> %timeit list(deepflatten(L))
12.6 µs ± 298 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> %timeit list(deepflatten(L, types=list))
8.7 µs ± 139 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

>>> %timeit list(flatten(L))   # Cristian - Python 3.x approach from https://stackoverflow.com/a/2158532/5393381
86.4 µs ± 4.42 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

>>> %timeit list(flatten(L))   # Josh Lee - https://stackoverflow.com/a/2158522/5393381
107 µs ± 2.99 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

>>> %timeit list(genflat(L, list))  # Alex Martelli - https://stackoverflow.com/a/2159079/5393381
23.1 µs ± 710 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

我是该库的作者。iteration_utilities

7赞 cglacet 8/2/2018 #26

在尝试回答这样的问题时,您确实需要给出您提出的代码作为解决方案的局限性。如果只是关于性能,我不会太介意,但大多数作为解决方案提出的代码(包括公认的答案)都无法展平任何深度大于 1000 的列表。

当我说大多数代码时,我指的是所有使用任何形式的递归(或调用递归的标准库函数)的代码。所有这些代码都失败了,因为对于每个递归调用,(调用)堆栈都会增长一个单位,并且(默认)python 调用堆栈的大小为 1000。

如果您不太熟悉调用堆栈,那么以下内容可能会有所帮助(否则您可以滚动到实现)。

调用堆栈大小和递归编程(地下城类比)

寻找宝藏并退出

想象一下,你进入一个巨大的地牢,里面有编号的房间,寻找宝藏。你不知道这个地方,但你有一些关于如何找到宝藏的迹象。每个指示都是一个谜语(难度各不相同,但你无法预测它们会有多难)。你决定考虑一下节省时间的策略,你做了两个观察:

  1. 找到宝藏是很困难的(漫长的),因为你必须解决(可能很难的)谜语才能到达那里。
  2. 一旦找到宝藏,回到入口可能很容易,你只需要在另一个方向使用相同的路径(尽管这需要一点记忆来回忆你的路径)。

进入地牢时,你会注意到这里有一个小笔记本。你决定用它来写下你在解开谜语后离开的每个房间(进入一个新房间时),这样你就可以回到入口处。这是一个天才的想法,你甚至不会花一分钱来实施你的策略。

你进入地牢,成功地解决了前 1001 个谜语,但这里有一些你没有计划的东西,你借来的笔记本里没有空间了。你决定放弃你的任务,因为你宁愿没有宝藏,也不愿永远迷失在地牢里(这看起来确实很聪明)。

执行递归程序

基本上,这与寻找宝藏完全相同。地牢是计算机的内存,你现在的目标不是找到宝藏,而是计算一些函数(为给定的 x 找到 f(x)。指示只是可以帮助您求解 f(x) 的子例程。您的策略与调用堆栈策略相同,笔记本是堆栈,房间是函数的返回地址:

x = ["over here", "am", "I"]
y = sorted(x) # You're about to enter a room named `sorted`, note down the current room address here so you can return back: 0x4004f4 (that room address looks weird)
# Seems like you went back from your quest using the return address 0x4004f4
# Let's see what you've collected 
print(' '.join(y))

你在地牢中遇到的问题在这里是一样的,调用堆栈的大小是有限的(这里是 1000),因此,如果你输入了太多函数而没有返回,那么你将填满调用堆栈并出现一个错误,看起来像“亲爱的冒险家,我很抱歉,但你的笔记本已满”:。请注意,您不需要递归来填充调用堆栈,但非递归程序调用 1000 个函数而不返回的可能性很小。同样重要的是要了解,一旦您从函数返回,调用堆栈就会从使用的地址中释放出来(因此得名“堆栈”,返回地址在进入函数之前被推入,在返回时被拉出)。在简单递归的特殊情况下(一个函数,一遍又一遍地调用自己),你将一遍又一遍地输入,直到计算完成(直到找到宝藏),然后从返回,直到你回到你最初调用的地方。调用堆栈永远不会从任何内容中释放出来,直到最后它将一个接一个地从所有返回地址中释放出来。RecursionError: maximum recursion depth exceededffff

如何避免这个问题?

这其实很简单:“如果你不知道递归能有多深,就不要使用递归”。这并不总是正确的,因为在某些情况下,尾调用递归可以优化 (TCO)。但在 python 中,情况并非如此,即使是“写得很好”的递归函数也不会优化堆栈的使用。关于这个问题,Guido 发表了一篇有趣的文章:Tail Recursion Elimination

有一种技术可以使任何递归函数迭代,这种技术我们可以称之为自带笔记本。例如,在我们的特定情况下,我们只是在探索一个列表,进入一个房间相当于进入一个子列表,你应该问自己的问题是,我怎样才能从一个列表回到它的父列表?答案并不复杂,重复以下操作,直到 为空:stack

  1. 在输入新的子列表时推送当前列表和 in(注意列表地址+索引也是一个地址,因此我们只是使用与调用堆栈完全相同的技术);addressindexstack
  2. 每次找到项目时,它(或将它们添加到列表中);yield
  3. 完全浏览列表后,使用返回地址(和索引)返回到父列表。stack

另请注意,这相当于树中的 DFS,其中某些节点是子列表,而某些节点是简单项:(for )。树如下所示:A = [1, 2]0, 1, 2, 3, 4L = [0, [1,2], 3, 4]

                    L
                    |
           -------------------
           |     |     |     |
           0   --A--   3     4
               |   |
               1   2

DFS 遍历预购顺序为:L、0、A、1、2、3、4。 请记住,为了实现迭代 DFS,您还需要一个堆栈。我之前提出的实现导致具有以下状态(对于 和 ):stackflat_list

init.:  stack=[(L, 0)]
**0**:  stack=[(L, 0)],         flat_list=[0]
**A**:  stack=[(L, 1), (A, 0)], flat_list=[0]
**1**:  stack=[(L, 1), (A, 0)], flat_list=[0, 1]
**2**:  stack=[(L, 1), (A, 1)], flat_list=[0, 1, 2]
**3**:  stack=[(L, 2)],         flat_list=[0, 1, 2, 3]
**3**:  stack=[(L, 3)],         flat_list=[0, 1, 2, 3, 4]
return: stack=[],               flat_list=[0, 1, 2, 3, 4]

在此示例中,堆栈最大大小为 2,因为输入列表(以及树)的深度为 2。

实现

对于实现,在 python 中,您可以通过使用迭代器而不是简单列表来简化一点。对(子)迭代器的引用将用于存储子列表返回地址(而不是同时具有列表地址和索引)。这不是一个很大的区别,但我觉得这更具可读性(而且速度也更快):

def flatten(iterable):
    return list(items_from(iterable))

def items_from(iterable):
    cursor_stack = [iter(iterable)]
    while cursor_stack:
        sub_iterable = cursor_stack[-1]
        try:
            item = next(sub_iterable)
        except StopIteration:   # post-order
            cursor_stack.pop()
            continue
        if is_list_like(item):  # pre-order
            cursor_stack.append(iter(item))
        elif item is not None:
            yield item          # in-order

def is_list_like(item):
    return isinstance(item, list)

另外,请注意,在我有 ,可以更改它以处理更多输入类型,这里我只想拥有最简单的版本,其中(可迭代)只是一个列表。但你也可以这样做:is_list_likeisinstance(item, list)

def is_list_like(item):
    try:
        iter(item)
        return not isinstance(item, str)  # strings are not lists (hmm...) 
    except TypeError:
        return False

这会将字符串视为“简单项”,因此将返回而不是 .请注意,在这种情况下,每个项目都会被调用两次,让我们假设这是读者的练习,以使其更干净。flatten_iter([["test", "a"], "b])["test", "a", "b"]["t", "e", "s", "t", "a", "b"]iter(item)

对其他实现的测试和注释

最后,请记住,您不能使用 打印无限嵌套的列表,因为它在内部会使用递归调用 ()。出于同样的原因,涉及的解决方案将失败,并显示相同的错误消息。Lprint(L)__repr__RecursionError: maximum recursion depth exceeded while getting the repr of an objectflattenstr

如果需要测试解决方案,可以使用此函数生成一个简单的嵌套列表:

def build_deep_list(depth):
    """Returns a list of the form $l_{depth} = [depth-1, l_{depth-1}]$
    with $depth > 1$ and $l_0 = [0]$.
    """
    sub_list = [0]
    for d in range(1, depth):
        sub_list = [d, sub_list]
    return sub_list

这给了:>>> .build_deep_list(5)[4, [3, [2, [1, [0]]]]]

2赞 Jorge Orpinel Pérez 1/22/2019 #27

没有递归或嵌套循环。几行。格式良好且易于阅读:

def flatten_deep(arr: list):
    """ Flattens arbitrarily-nested list `arr` into single-dimensional. """

    while arr:
        if isinstance(arr[0], list):  # Checks whether first element is a list
            arr = arr[0] + arr[1:]  # If so, flattens that first element one level
        else:
            yield arr.pop(0)  # Otherwise yield as part of the flat array

flatten_deep(L)

来自我自己在 https://github.com/jorgeorpinel/flatten_nested_lists/blob/master/flatten.py 的代码

评论

1赞 Nathaniel Hoyt 1/4/2023
谢谢,这对我来说是最容易理解的,对我来说效果很好;干的好。编辑;也只是说出你的flatten_trick,喜欢它!
3赞 ADR 3/23/2019 #28

只需使用一个有趣的库:pip install funcy

import funcy


funcy.flatten([[[[1, 1], 1], 2], 3]) # returns generator
funcy.lflatten([[[[1, 1], 1], 2], 3]) # returns list

评论

1赞 Georgy 5/23/2019
仅供参考:它使用递归解决方案:链接到源代码
18赞 Mark Harrison 10/25/2020 #29

Pandas 有一个函数可以做到这一点。如您所提到的,它返回一个迭代器。

In [1]: import pandas
In [2]: pandas.core.common.flatten([[[1, 2, 3], [4, 5]], 6])
Out[2]: <generator object flatten at 0x7f12ade66200>
In [3]: list(pandas.core.common.flatten([[[1, 2, 3], [4, 5]], 6]))
Out[3]: [1, 2, 3, 4, 5, 6]

评论

1赞 rvrvrv 1/13/2021
好东西!对于无论如何都在使用熊猫的人来说(像我一样),这是一种非常简单的方法
0赞 Madaray 9/7/2022
带有 pydantic 对象列表的意外结果,属性也被展平(最终列表中不再有对象)
2赞 mozway 9/20/2023 #30

我经常使用 more_itertools.collapse

# pip install more-itertools
from more_itertools import collapse

out = list(collapse([[[1, 2, 3], [4, 5]], 6]))
# [1, 2, 3, 4, 5, 6]

它处理开箱即用的字符串/字节,并且不会将它们解压缩为单个字符:

list(collapse([[[1, 2, 3, 'abc'], [4, 5]], 6, 'def']))
# [1, 2, 3, 'abc', 4, 5, 6, 'def']

它也可以停止在给定的嵌套级别:

list(collapse([[[1, 2, 3], [4, 5]], 6], levels=1))
# [[1, 2, 3], [4, 5], 6]

完整的代码相对简单,并且还使用了递归方法:

def collapse(iterable, base_type=None, levels=None):
    def walk(node, level):
        if (
            ((levels is not None) and (level > levels))
            or isinstance(node, (str, bytes))
            or ((base_type is not None) and isinstance(node, base_type))
        ):
            yield node
            return

        try:
            tree = iter(node)
        except TypeError:
            yield node
            return
        else:
            for child in tree:
                yield from walk(child, level + 1)

    yield from walk(iterable, 0)

评论

2赞 pho 9/20/2023
令我感到非常惊讶的是,现有的 65 个答案中没有一个提到,即使那一条评论确实如此!more_itertools.collapse
0赞 mozway 9/20/2023
@Pranav我也是,我检查了两次以确保! 这是一个如此不错的图书馆,如果不在那里会很遗憾。more_itertools