我想知道是否有捷径可以从 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'
给定一个列表l
,
flat_list = [item for sublist in l for item in sublist]
意思是:
flat_list = []
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)
-由于中间结果列表越来越长,因此每一步都有一个新的中间结果将分配 list 对象,并且必须复制上一个中间结果中的所有项目(以及最后添加的一些新项目)。因此,为简单起见,而又不失去一般性,请说您有 I 个项目的 L 个子列表:第一个 I 项目来回复制 L-1 次,第二个 I 项目 L-2 次,依此类推;等等。总拷贝数是 I 乘以 x 的 x 的总和(从 1 到 L 排除在外),即I * (L**2)/2
。
列表理解只生成一次列表,然后将每个项目(从其原始居住地复制到结果列表)也恰好复制一次。
您可以使用itertools.chain()
:
>>> import itertools
>>> list2d = [[1,2,3], [4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain(*list2d))
或者,您可以使用itertools.chain.from_iterable()
,而无需使用*
运算符将列表拆包:
>>> import itertools
>>> list2d = [[1,2,3], [4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain.from_iterable(list2d))
可以说这种方法比[item for sublist in l for item in sublist]
更具可读性,并且看起来也更快:
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;import itertools' 'list(itertools.chain.from_iterable(l))'
20000 loops, best of 5: 10.8 usec per loop
$ python3 -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 5: 21.7 usec per loop
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 5: 258 usec per loop
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;from functools import reduce' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 5: 292 usec per loop
$ python3 --version
Python 3.7.5rc1
作者注意 :这是低效的。但是很有趣,因为类人猿很棒。它不适用于生产 Python 代码。
>>> sum(l, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
这只是对第一个参数中传递的 iterable 元素求和,将第二个参数视为和的初始值(如果未给出,则使用0
代替,这种情况下会出现错误)。
因为你正在总结嵌套列表时,实际上得到[1,3]+[2,4]
作为结果sum([[1,3],[2,4]],[])
其等于[1,3,2,4]
。
请注意,仅适用于列表列表。对于列表列表列表,您将需要其他解决方案。
我用perfplot (我的一个宠物项目,本质上是timeit
的包装)测试了大多数建议的解决方案,然后发现
functools.reduce(operator.iconcat, a, [])
成为最快的解决方案。 ( operator.iadd
同样快。)
复制剧情的代码:
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 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 numpy_concatenate(a):
return list(numpy.concatenate(a))
perfplot.show(
setup=lambda n: [list(range(10))] * n,
kernels=[
forfor, sum_brackets, functools_reduce, functools_reduce_iconcat,
itertools_chain, numpy_flat, numpy_concatenate
],
n_range=[2**k for k in range(16)],
xlabel='num lists'
)
from functools import reduce #python 3
>>> 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
而不是返回有用的值( reduce()
期望的值)。
做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]
这是适用于数字 , 字符串 , 嵌套列表和混合容器的通用方法。
码
#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
注意事项 :
yield from flatten(x)
可以替换for sub_x in flatten(x): yield sub_x
collection.abc
到typing
模块。 演示版
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']
参考
>>> from django.contrib.admin.utils import flatten
>>> l = [[1,2,3], [4,5], [6]]
>>> flatten(l)
>>> [1, 2, 3, 4, 5, 6]
... 熊猫 :
>>> from pandas.core.common import flatten
>>> list(flatten(l))
... Itertools :
>>> import itertools
>>> flatten = itertools.chain.from_iterable
>>> list(flatten(l))
... Matplotlib
>>> from matplotlib.cbook import flatten
>>> list(flatten(l))
... Unipath :
>>> from unipath.path import flatten
>>> list(flatten(l))
... Setuptools :
>>> from setuptools.namespaces import flatten
>>> list(flatten(l))
如果要在不知道嵌套深度的情况下展平数据结构,则可以使用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
,它本身只是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 的答案,其中不包括此答案中提供的功能):
这是一个对数对数图,可以容纳跨度很大的值。对于定性推理:越低越好。
研究结果表明,如果迭代只包含几个内部 iterables 那么sum
将是最快的,但是长期 iterables 只有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 免责声明:我是该图书馆的作者
我收回我的声明。总和不是赢家。尽管列表较小时速度更快。但是,列表较大时,性能会大大降低。
>>> 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
sum 版本仍在运行一分钟以上,尚未处理!
对于中型列表:
>>> 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
使用小清单和时间: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
似乎与operator.add
混淆了!将两个列表加在一起时,正确的术语是concat
,而不是 add。 operator.concat
是您需要使用的。
如果您认为功能正常,那么就这么简单:
>>> from functools import reduce
>>> 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