python - 建立空列表 - 如何从列表列表中制作一个平面列表?
定义一个空列表 (20)
underscore.py
包风扇的简单代码
from underscore import _
_.flatten([[1, 2, 3], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
它解决了所有展平问题(无列表项或复杂嵌套)
from underscore import _
# 1 is none list item
# [2, [3]] is complex nesting
_.flatten([1, [2, [3]], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
您可以使用pip安装underscore.py
pip install underscore.py
我想知道是否有一条快捷方式可以在Python列表中列出一个简单的列表。
我可以在for循环中做到这一点,但也许有一些很酷的“单行”? 我用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'
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倍._。flatten:
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
我接受我的陈述。 总和不是赢家。 虽然列表很小但速度更快。 但是,较大的列表会使性能显着下降。
>>> timeit.Timer(
'[item for sublist in l for item in sublist]',
'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10000'
).timeit(100)
2.0440959930419922
总和版本仍然运行超过一分钟,它还没有完成处理!
对于中型名单:
>>> timeit.Timer(
'[item for sublist in l for item in sublist]',
'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
).timeit()
20.126545906066895
>>> timeit.Timer(
'reduce(lambda x,y: x+y,l)',
'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
).timeit()
22.242258071899414
>>> timeit.Timer(
'sum(l, [])',
'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
).timeit()
16.449732065200806
使用小列表和timeit:number = 1000000
>>> timeit.Timer(
'[item for sublist in l for item in sublist]',
'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
).timeit()
2.4598159790039062
>>> timeit.Timer(
'reduce(lambda x,y: x+y,l)',
'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
).timeit()
1.5289170742034912
>>> timeit.Timer(
'sum(l, [])',
'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
).timeit()
1.0598428249359131
注意:以下内容适用于Python 3.3+,因为它使用yield_from
。six
也是第三方包,虽然它是稳定的。或者,您可以使用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
s为)。但是,稍微阅读一下这些行,您不希望将其视为这样 - 您希望将其视为单个元素。 - 最后一个元素
[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
ABC 迭代itertools
,但也要确保(2)元素不是 “类似字符串”。
为什么使用extend?
reduce(lambda x, y: x+y, l)
这应该工作正常。
以下是适用于数字 , 字符串 , 嵌套列表和混合容器的一般方法。
码
from collections import Iterable
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
注意:在Python 3中, yield from flatten(x)
可以替换for sub_x in flatten(x): yield sub_x
演示
lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(flatten(lst)) # nested lists
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
mixed = [[1, [2]], (3, 4, {5, 6}, 7), 8, "9"] # numbers, strs, nested & mixed
list(flatten(mixed))
# [1, 2, 3, 4, 5, 6, 7, 8, '9']
参考
- 该解决方案是根据Beazley,D。和B. Jones的配方修改的。 食谱4.14,Python Cookbook 3rd Ed。,O'Reilly Media Inc. Sebastopol,CA:2013。
- 找到了早期的SO帖子 ,可能是最初的演示。
似乎与operator.add
混淆了! 当您将两个列表一起添加时,正确的术语是concat
,而不是添加。 operator.concat
是您需要使用的。
如果您正在考虑功能,它就像这样简单::
>>> list2d = ((1, 2, 3), (4, 5, 6), (7,), (8, 9))
>>> reduce(operator.concat, list2d)
(1, 2, 3, 4, 5, 6, 7, 8, 9)
你看到reduce尊重序列类型,所以当你提供一个元组时,你会得到一个元组。 让我们尝试一下清单::
>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> reduce(operator.concat, list2d)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
啊哈,你得到一份清单。
性能怎么样::
>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> %timeit list(itertools.chain.from_iterable(list2d))
1000000 loops, best of 3: 1.36 µs per loop
from_iterable非常快! 但是用concat减少它是没有可比性的。
>>> list2d = ((1, 2, 3),(4, 5, 6), (7,), (8, 9))
>>> %timeit reduce(operator.concat, list2d)
1000000 loops, best of 3: 492 ns per loop
你可以使用numpy:
flat_list = list(np.concatenate(list_of_list))
另一种适用于异构和同类整数列表的不寻常方法:
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(',')]
如果你想展平一个你不知道嵌套深度的数据结构,你可以使用iteration_utilities.deepflatten
1
>>> 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]
只是添加一些时间(基于NicoSchlömer的回答,不包括此答案中提供的功能):
这是一个对数日志图,可以适应各种各样的值。 对于定性推理:越低越好。
结果表明,如果iterable只包含几个内部迭代,那么sum
将是最快的,但是对于长迭代,只有itertools.chain.from_iterable
, iteration_utilities.deepflatten
或嵌套的理解具有合理的性能, itertools.chain.from_iterable
是最快的(正如NicoSchlö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免责声明:我是该图书馆的作者
如果你愿意放弃一numpy.concatenate().tolist()
速度以获得更清晰的外观,那么你可以使用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
您可以在docs numpy.concatenate和numpy.ravel找到更多numpy.ravel
您的函数不起作用的原因:extend将数组扩展到原位并且不返回它。 你仍然可以使用一些技巧从lambda返回x:
reduce(lambda x,y: x.extend(y) or x, l)
注意:extend比列表上的+更有效。
我找到的最快的解决方案(无论如何都是大型列表):
import numpy as np
#turn list into an array and flatten()
np.array(l).flatten()
完成! 您当然可以通过执行list(l)将其转回列表
我用perfplot测试了大多数建议的解决方案(我的一个宠物项目,基本上是timeit
的包装),并且发现了
list(itertools.chain.from_iterable(a))
成为最快的解决方案(如果连接的列表超过10个)。
重现情节的代码:
import functools
import itertools
import numpy
import operator
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 itertools_chain(a):
return list(itertools.chain.from_iterable(a))
def numpy_flat(a):
return list(numpy.array(a).flat)
def numpy_concatenate(a):
return list(numpy.concatenate(a))
perfplot.show(
setup=lambda n: [list(range(10))] * n,
kernels=[
forfor, sum_brackets, functools_reduce, itertools_chain, numpy_flat,
numpy_concatenate
],
n_range=[2**k for k in range(16)],
logx=True,
logy=True,
xlabel='num lists'
)
考虑安装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]
从版本2.4开始,您可以使用more_itertools.collapse
( source ,由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]
这可能不是最有效的方式,但我想放一个单线(实际上是一个双线)。两个版本都可以在任意层次结构嵌套列表上运行,并利用语言功能(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
列表推导中的父级。因此,在根目录下,返回一个平面列表。
上面的一个创建了几个本地列表并返回它们,用于扩展父列表。我认为解决这个问题的方法可能就是创造一个flist
像我这样的人。
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]
虽然我现在还不确定效率。
def flatten(alist):
if alist == []:
return []
elif type(alist) is not list:
return [alist]
else:
return flatten(alist[0]) + flatten(alist[1:])
flat_list = []
for i in list_of_list:
flat_list+=i
此代码也可以正常工作,因为它只是一直扩展列表。 虽然它非常相似,但只有一个用于循环。 因此它比为循环添加2更少复杂。
flat_list = [item for sublist in l for item in sublist]
意思是:
for sublist in l:
for item in sublist:
flat_list.append(item)
比目前发布的快捷方式快。 ( l
是要压扁的列表。)
这是一个相应的功能:
flatten = lambda l: [item for sublist in l for item in sublist]
为了证据,您可以像往常一样使用标准库中的timeit
模块:
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 3: 143 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 969 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 3: 1.1 msec per loop
说明:当存在L个子列表时,基于+
的快捷方式(包括sum
隐含的用法)必然为O(L**2)
- 因为中间结果列表持续变长,在每个步骤都有新的中间结果列表对象被分配,并且必须复制先前中间结果中的所有项目(以及最后添加的一些新项目)。 所以(为了简单而没有实际的失去一般性)说你有每个项目的L个子列表:第一个I项目来回复制L-1次,第二个I项目L-2次,依此类推; 总复制数是I乘以x的总和,从1到L排除,即I * (L**2)/2
。
列表理解只生成一个列表一次,并将每个项目(从其原始居住地点到结果列表)复制一次。