热门标签:
Q:

如何从列表列表中制作一个平面列表?

在Python中,有没有一个快捷方式可以从列表列表中制作一个简单的列表?

我可以在for循环中做到这一点,但是有一些很酷的"单行"吗?

我用functools.reduce()试过:

from functools import reduce
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
reduce(lambda x, y: x.extend(y), l)

但我得到这个错误:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'
原网址
A:

给定列表列表 t

flat_list = [item for sublist in t for item in sublist]

这意味着:

flat_list = []
for sublist in t:
    for item in sublist:
        flat_list.append(item)

比迄今为止发布的快捷方式更快。 ( t 是要拼合的列表。)

下面是相应的函数:

def flatten(t):
    return [item for sublist in t for item in sublist]

作为证据,可以使用标准库中的 timeit 模块:

$ python -mtimeit -s't=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in t for item in sublist]'
10000 loops, best of 3: 143 usec per loop
$ python -mtimeit -s't=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(t, [])'
1000 loops, best of 3: 969 usec per loop
$ python -mtimeit -s't=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,t)'
1000 loops, best of 3: 1.1 msec per loop

解释:基于 + 的快捷方式(包括 sum 中的隐含使用)是必要的,当有T个子列表时--随着中间结果列表越来越长,每个步骤都会分配一个新的中间结果列表对象,并且必须复制前一个中间结果中的所有项目(以及最后添加的一些新项目)。 因此,为了简单起见,并且没有实际的一般性损失,假设您有T个k项的子列表:第一个k项被来回复制T-1次,第二个k项被复制T-2次,依此类推;总副本数是x的总和的k倍x从1到T排除,即 k * (T**2)/2

列表理解只是生成一个列表,一次,并复制每个项目(从其原始居住地到结果列表)也正好一次。

所有回答

共 25 条

author avatar

您可以使用itertools.chain():

import itertools

list2d = [[1,2,3], [4,5,6], [7], [8,9]]
merged = list(itertools.chain(*list2d))

或者你可以使用itertools.chain.from_iterable()不需要用*运算符解包列表:

merged = list(itertools.chain.from_iterable(list2d))
author avatar

作者的注释:这是低效的。 但有趣,因为monoids很棒。 它不适用于生产Python代码。

>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> sum(l, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

这只是对第一个参数中传递的可迭代元素进行求和,将第二个参数视为总和的初始值(如果没有给出,则使用0,这种情况会给你一个错误)。

因为你正在求和嵌套列表,你实际上得到[1,3]+[2,4]作为sum([[1,3],[2,4]],[])的结果,它等于[1,3,2,4]

请注意,仅适用于列表列表。 对于列表列表,您将需要另一个解决方案。

author avatar

我用perfplot(我的一个宠物项目,本质上是timeit的包装器)测试了大多数建议的解决方案,并发现

import functools
import operator
functools.reduce(operator.iconcat, a, [])

是最快的解决方案,无论是当许多小列表和很少的长列表被连接时。 (operator.iadd同样快。)

一个更简单且也可接受的变体是

out = []
for sublist in a:
    out.extend(sublist)

如果子列表的数量很大,这比上面的建议执行得稍差。


代码重现剧情:

import functools
import itertools
import operator

import numpy as np
import perfplot


def forfor(a):
    return [item for sublist in a for item in sublist]


def sum_brackets(a):
    return sum(a, [])


def functools_reduce(a):
    return functools.reduce(operator.concat, a)


def functools_reduce_iconcat(a):
    return functools.reduce(operator.iconcat, a, [])


def itertools_chain(a):
    return list(itertools.chain.from_iterable(a))


def numpy_flat(a):
    return list(np.array(a).flat)


def numpy_concatenate(a):
    return list(np.concatenate(a))


def extend(a):
    out = []
    for sublist in a:
        out.extend(sublist)
    return out


b = perfplot.bench(
    setup=lambda n: [list(range(10))] * n,
    # setup=lambda n: [list(range(n))] * 10,
    kernels=[
        forfor,
        sum_brackets,
        functools_reduce,
        functools_reduce_iconcat,
        itertools_chain,
        numpy_flat,
        numpy_concatenate,
        extend,
    ],
    n_range=[2 ** k for k in range(16)],
    xlabel="num lists (of length 10)",
    # xlabel="len lists (10 lists total)"
)
b.save("out.png")
b.show()
author avatar
>>> from functools import reduce
>>> l = [[1,2,3], [4,5,6], [7], [8,9]]
>>> reduce(lambda x, y: x+y, l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

示例中的extend()方法修改x而不是返回有用的值(whichfunctools.reduce()期望)。

执行reduce版本的更快方法是

>>> from functools import reduce
>>> import operator
>>> l = [[1,2,3], [4,5,6], [7], [8,9]]
>>> reduce(operator.concat, l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
author avatar

这里是一个通用的方法,适用于数字字符串嵌套列表和混合容器。 这可以拼合简单和复杂的容器(另请参阅Demo)。

代码

from typing import Iterable 
#from collections import Iterable                            # < py38


def flatten(items):
    """Yield items from any nested iterable; see Reference."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            for sub_x in flatten(x):
                yield sub_x
        else:
            yield x

笔记:

  • 在Python3中,yield from flatten(x)可以替换for sub_x in flatten(x): yield sub_x
  • 在Python3.8中,抽象基类collection.abc。abc到typing模块。

演示

simple = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(flatten(simple))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

complicated = [[1, [2]], (3, 4, {5, 6}, 7), 8, "9"]              # numbers, strs, nested & mixed
list(flatten(complicated))
# [1, 2, 3, 4, 5, 6, 7, 8, '9']

参考

  • 该解决方案是从Beazley,D.和B.Jones中的配方修改而来的。 食谱4.14,Python食谱第3版。 繝シ繧ォ繝シ縺ォ縺ェ縺"縺ヲ縺縺セ縺呐 塞瓦斯托波尔,CA:2013.
  • 找到了更早的SO post,可能是原始演示。
author avatar

如果你想扁平化一个不知道嵌套有多深的数据结构,你可以使用iteration_utilities.deepflatten1

>>> from iteration_utilities import deepflatten

>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(deepflatten(l, depth=1))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> l = [[1, 2, 3], [4, [5, 6]], 7, [8, 9]]
>>> list(deepflatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

它是一个生成器,因此您需要将结果强制转换为list或显式迭代它。


只拼合一个级别,如果每个项目本身都是可迭代的,你也可以使用iteration_utilities.flatten它本身只是itertools.chain.from_iterable:

>>> from iteration_utilities import flatten
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(flatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

只是为了添加一些计时(基于Nico Schlömer的答案不包括此答案中呈现的功能):

这是一个对数-对数图,以适应跨越的巨大值范围。 对于定性推理:越低越好。

结果表明,如果迭代只包含几个内部迭代,那么sum将是最快的,但是对于长迭代,只有itertools.chain.from_iterableiteration_utilities.deepflatten或嵌套理解具有合理的性能,itertools.chain.from_iterable是最快的(正如Nico Schlömer已经注意到的)。

from itertools import chain
from functools import reduce
from collections import Iterable  # or from collections.abc import Iterable
import operator
from iteration_utilities import deepflatten

def nested_list_comprehension(lsts):
    return [item for sublist in lsts for item in sublist]

def itertools_chain_from_iterable(lsts):
    return list(chain.from_iterable(lsts))

def pythons_sum(lsts):
    return sum(lsts, [])

def reduce_add(lsts):
    return reduce(lambda x, y: x + y, lsts)

def pylangs_flatten(lsts):
    return list(flatten(lsts))

def flatten(items):
    """Yield items from any nested iterable; see REF."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            yield from flatten(x)
        else:
            yield x

def reduce_concat(lsts):
    return reduce(operator.concat, lsts)

def iteration_utilities_deepflatten(lsts):
    return list(deepflatten(lsts, depth=1))


from simple_benchmark import benchmark

b = benchmark(
    [nested_list_comprehension, itertools_chain_from_iterable, pythons_sum, reduce_add,
     pylangs_flatten, reduce_concat, iteration_utilities_deepflatten],
    arguments={2**i: [[0]*5]*(2**i) for i in range(1, 13)},
    argument_name='number of inner lists'
)

b.plot()

1免责声明:我是该库的作者

author avatar

考虑安装more_itertools包。

> pip install more_itertools

它附带了一个用于flatten的实现(source,来自itertools配方):

import more_itertools


lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.flatten(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

注意:如docs中所述,flatten需要列表列表。 请参阅下面关于扁平化更不规则的输入。


从版本2.4开始,您可以使用more_itertools.collapsesource,由abarnet贡献)。

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.collapse(lst)) 
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

lst = [[1, 2, 3], [[4, 5, 6]], [[[7]]], 8, 9]              # complex nesting
list(more_itertools.collapse(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
author avatar

以下对我来说似乎最简单:

>>> import numpy as np
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> print(np.concatenate(l))
[1 2 3 4 5 6 7 8 9]
author avatar

您的函数不起作用的原因是因为extend就地扩展了一个数组,并且不返回它。 您仍然可以从lambda返回x,使用如下所示:

reduce(lambda x,y: x.extend(y) or x, l)

注意:extend比列表上的+更有效。

author avatar

matplotlib.cbook.flatten()将适用于嵌套列表,即使它们比示例嵌套得更深。

import matplotlib
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
print(list(matplotlib.cbook.flatten(l)))
l2 = [[1, 2, 3], [4, 5, 6], [7], [8, [9, 10, [11, 12, [13]]]]]
print(list(matplotlib.cbook.flatten(l2)))

结果:

[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]

这比下划线快18倍。_.展平:

Average time over 1000 trials of matplotlib.cbook.flatten: 2.55e-05 sec
Average time over 1000 trials of underscore._.flatten: 4.63e-04 sec
(time for underscore._)/(time for matplotlib.cbook) = 18.1233394636
author avatar

根据您的list[[1, 2, 3], [4, 5, 6], [7], [8, 9]]即1列表级别,我们可以简单地使用sum(list,[])而不使用任何库

sum([[1, 2, 3], [4, 5, 6], [7], [8, 9]],[])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
author avatar

也可以使用NumPy的flat:

import numpy as np
list(np.array(l).flat)

只有当子列表具有相同的维度时,它才有效。

author avatar

你可以使用listextend方法,它显示是最快的:

flat_list = []
for sublist in l:
    flat_list.extend(sublist)

工作表现:

import functools
import itertools
import numpy
import operator
import perfplot



def functools_reduce_iconcat(a):
    return functools.reduce(operator.iconcat, a, [])


def itertools_chain(a):
    return list(itertools.chain.from_iterable(a))


def numpy_flat(a):
    return list(numpy.array(a).flat)


def extend(a):
    n = []

    list(map(n.extend, a))

    return n 


perfplot.show(
    setup=lambda n: [list(range(10))] * n,
    kernels=[
        functools_reduce_iconcat, extend,itertools_chain, numpy_flat
        ],
    n_range=[2**k for k in range(16)],
    xlabel='num lists',
    )

输出:

author avatar

如果你愿意为了更干净的外观而放弃少量的速度,那么你可以使用numpy.concatenate().tolist()numpy.concatenate().ravel().tolist()

import numpy

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]] * 99

%timeit numpy.concatenate(l).ravel().tolist()
1000 loops, best of 3: 313 µs per loop

%timeit numpy.concatenate(l).tolist()
1000 loops, best of 3: 312 µs per loop

%timeit [item for sublist in l for item in sublist]
1000 loops, best of 3: 31.5 µs per loop

您可以在文档numpy中找到更多信息。连接numpy。拉威尔

author avatar

有几个答案具有与下面相同的递归追加方案,但没有一个使用try,这使得解决方案更加健壮和Pythonic

def flatten(itr):
    for x in itr:
        try:
            yield from flatten(x)
        except TypeError:
            yield x

用法:这是一个生成器,您通常希望将其包含在像list()tuple()这样的可迭代构建器中,或者在for循环中使用它。

这种解决方案的优点是:

  • 适用于任何类型的可迭代(甚至是未来的!)
  • 适用于嵌套的任何组合和深度
  • 如果顶层包含裸露的项目,也可以工作
  • 没有依赖关系
  • 快速高效(您可以部分拼合嵌套的iterable,而不会在您不需要的剩余部分上浪费时间)
  • 多功能(您可以使用它来构建您选择的可迭代或循环)

注意:由于所有可迭代对象都被拼合,因此字符串被分解为单个字符的序列。 如果你不喜欢/想要这样的行为,你可以使用下面的版本,它从扁平化的iterables(如字符串和字节)中过滤掉:

def flatten(itr):
    if type(itr) in (str,bytes):
        yield itr
    else:
        for x in itr:
            try:
                yield from flatten(x)
            except TypeError:
                yield x
author avatar

注意:下面适用于Python3.3+,因为它使用yield_fromsix也是一个第三方包,虽然它是稳定的。 或者,您可以使用sys.version


obj = [[1, 2,], [3, 4], [5, 6]]的情况下,这里的所有解决方案都很好,包括列表理解和itertools.chain.from_iterable

但是,考虑这个稍微复杂的情况:

>>> obj = [[1, 2, 3], [4, 5], 6, 'abc', [7], [8, [9, 10]]]

这里有几个问题:

  • 一个元素,6,只是一个标量;它是不可迭代的,所以上面的路由在这里会失败。
  • 一个元素,'abc'在技术上是可迭代的(所有str都是)。 但是,在字里行间读一点,你不想把它当作这样的东西-你想把它当作一个单一的元素。
  • 最后一个元素,[8, [9, 10]]本身就是一个嵌套的可迭代元素。 基本列表理解和chain.from_iterable只提取"1级"。"

您可以按如下方式对此进行补救:

>>> from collections import Iterable
>>> from six import string_types

>>> def flatten(obj):
...     for i in obj:
...         if isinstance(i, Iterable) and not isinstance(i, string_types):
...             yield from flatten(i)
...         else:
...             yield i


>>> list(flatten(obj))
[1, 2, 3, 4, 5, 6, 'abc', 7, 8, 9, 10]

在这里,你检查子元素(1)是可迭代的Iterable,一个来自itertools的ABC,但也想确保(2)元素是not"string-like。"

author avatar
def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])
author avatar

在列表理解中使用twofor

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
flat_l = [e for v in l for e in v]
print(flat_l)
author avatar

这可能不是最有效的方法,但我想放一个单行(实际上是一个双行)。 这两个版本都将在任意层次嵌套列表上工作,并利用语言特性(Python3.5)和递归。

def make_list_flat (l):
    flist = []
    flist.extend ([l]) if (type (l) is not list) else [flist.extend (make_list_flat (e)) for e in l]
    return flist

a = [[1, 2], [[[[3, 4, 5], 6]]], 7, [8, [9, [10, 11], 12, [13, 14, [15, [[16, 17], 18]]]]]]
flist = make_list_flat(a)
print (flist)

输出为

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

这以深度优先的方式工作。 递归下去,直到找到一个非列表元素,然后扩展局部变量flist,然后回滚到父级。 每当返回flist时,它就会在列表理解中扩展到父级的flist。 因此,在根,返回一个平面列表。

上面的一个创建几个本地列表并返回它们,这些列表用于扩展父列表。 我认为这样做的方法可能是创建一个gloablflist,如下所示。

a = [[1, 2], [[[[3, 4, 5], 6]]], 7, [8, [9, [10, 11], 12, [13, 14, [15, [[16, 17], 18]]]]]]
flist = []
def make_list_flat (l):
    flist.extend ([l]) if (type (l) is not list) else [make_list_flat (e) for e in l]

make_list_flat(a)
print (flist)

输出再次

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

虽然我不确定在这个时候的效率。

author avatar

另一种不寻常的方法,适用于整数的异质和同质列表:

from typing import List


def flatten(l: list) -> List[int]:
    """Flatten an arbitrary deep nested list of lists of integers.

    Examples:
        >>> flatten([1, 2, [1, [10]]])
        [1, 2, 1, 10]

    Args:
        l: Union[l, Union[int, List[int]]

    Returns:
        Flatted list of integer
    """
    return [int(i.strip('[ ]')) for i in str(l).split(',')]
author avatar

我想要一个解决方案,它可以处理多个嵌套(例如[[1], [[[2]], [3]]], [1, 2, 3]),但也不会是递归的(我有一个很大的递归级别,我得到了一个递归错误。

这就是我想出来的:

def _flatten(l) -> Iterator[Any]:
    stack = l.copy()
    while stack:
        item = stack.pop()
        if isinstance(item, list):
            stack.extend(item)
        else:
            yield item


def flatten(l) -> Iterator[Any]:
    return reversed(list(_flatten(l)))

和测试:

@pytest.mark.parametrize('input_list, expected_output', [
    ([1, 2, 3], [1, 2, 3]),
    ([[1], 2, 3], [1, 2, 3]),
    ([[1], [2], 3], [1, 2, 3]),
    ([[1], [2], [3]], [1, 2, 3]),
    ([[1], [[2]], [3]], [1, 2, 3]),
    ([[1], [[[2]], [3]]], [1, 2, 3]),
])
def test_flatten(input_list, expected_output):
    assert list(flatten(input_list)) == expected_output
author avatar

用于拼合任意深度列表的非递归函数:

def flatten_list(list1):
    out = []
    inside = list1
    while inside:
        x = inside.pop(0)
        if isinstance(x, list):
            inside[0:0] = x
        else:
            out.append(x)
    return out

l = [[[1,2],3,[4,[[5,6],7],[8]]],[9,10,11]]
flatten_list(l)
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
author avatar

您可以使用以下内容:

def flatlst(lista):
    listaplana = []
    for k in lista: listaplana = listaplana + k
    return listaplana
author avatar

如果我想在以前的伟大答案中添加一些东西,这里是我的递归flatten函数,它不仅可以拼合嵌套列表,还可以拼合任何给定的容器或任何通常可以抛出项 这也适用于任何深度的嵌套,它是一个懒惰的迭代器,根据请求生成项目:

def flatten(iterable):
    # These types won't considered a sequence or generally a container
    exclude = str, bytes

    for i in iterable:
        try:
            if isinstance(i, exclude):
                raise TypeError
            iter(i)
        except TypeError:
            yield i
        else:
            yield from flatten(i)

这样你就可以排除你不希望它们像str那样扁平化的类型或其他类型。

这个想法是,如果一个对象可以传递iter(),它就可以产生项目。 因此,iterable可以将甚至生成器表达式作为一个项。

有人可能会争辩说,当OP没有要求时,为什么你写这个通用? 好吧,你是对的。 我只是觉得这可能会帮助某人(就像它为我自己做的那样)。

测试用例:

lst1 = [1, {3}, (1, 6), [[3, 8]], [[[5]]], 9, ((((2,),),),)]
lst2 = ['3', B'A', [[[(i ** 2 for i in range(3))]]], range(3)]

print(list(flatten(lst1)))
print(list(flatten(lst2)))

输出:

[1, 3, 1, 6, 3, 8, 5, 9, 2]
['3', b'A', 0, 1, 4, 0, 1, 2]
author avatar
np.hstack(listoflist).tolist()

相似问题