是否有一种简单的方法可以用列表理解来扁平化一个可迭代对象列表,或者如果没有,你们都认为什么是扁平化这样一个浅列表的最好方法,平衡性能和可读性?

我尝试用一个嵌套的列表理解来扁平化这样一个列表,就像这样:

[image for image in menuitem for menuitem in list_of_menuitems]

但是我遇到了NameError的麻烦,因为名字‘menuitem’没有定义。在google和Stack Overflow上搜索之后,我用一个reduce语句得到了想要的结果:

reduce(list.__add__, map(lambda x: list(x), list_of_menuitems))

但是这个方法是相当不可读的,因为我需要调用list(x)因为x是Django QuerySet对象。

结论:

感谢每个为这个问题做出贡献的人。以下是我所学到的一份总结。我也把它变成了一个社区维基,以防其他人想要添加或纠正这些观察。

我原来的reduce语句是多余的,最好这样写:

>>> reduce(list.__add__, (list(mi) for mi in list_of_menuitems))

这是嵌套列表理解的正确语法(聪明的总结dF!)

>>> [image for mi in list_of_menuitems for image in mi]

但这两种方法都不如使用itertools.chain有效:

>>> from itertools import chain
>>> list(chain(*list_of_menuitems))

正如@cdleary所指出的那样,使用chain.from_iterable来避免*操作符魔法可能是更好的风格,就像这样:

>>> chain = itertools.chain.from_iterable([[1,2],[3],[5,89],[],[6]])
>>> print(list(chain))
>>> [1, 2, 3, 5, 89, 6]

当前回答

似乎与operator.add!当你将两个列表相加时,正确的术语是concat,而不是add. operator。你需要使用Concat。

如果你考虑的是功能性的,它就像这样简单:

>>> from functools import reduce
>>> import operator
>>> 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非常快!但是reduce和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

其他回答

在我的脑海中,你可以消去lambda

reduce(list.__add__, map(list, [mi.image_set.all() for mi in list_of_menuitems]))

或者甚至删除地图,因为你已经有了一个列表:

reduce(list.__add__, [list(mi.image_set.all()) for mi in list_of_menuitems])

你也可以将它表示为列表的和:

sum([list(mi.image_set.all()) for mi in list_of_menuitems], [])

根据我的经验,最有效的将列表的列表扁平化的方法是:

flat_list = []
map(flat_list.extend, list_of_list)

有时与其他提出的方法进行比较:

list_of_list = [range(10)]*1000
%timeit flat_list=[]; map(flat_list.extend, list_of_list)
#10000 loops, best of 3: 119 µs per loop
%timeit flat_list=list(itertools.chain.from_iterable(list_of_list))
#1000 loops, best of 3: 210 µs per loop
%timeit flat_list=[i for sublist in list_of_list for i in sublist]
#1000 loops, best of 3: 525 µs per loop
%timeit flat_list=reduce(list.__add__,list_of_list)
#100 loops, best of 3: 18.1 ms per loop

现在,当处理更长的子列表时,效率增益会更好:

list_of_list = [range(1000)]*10
%timeit flat_list=[]; map(flat_list.extend, list_of_list)
#10000 loops, best of 3: 60.7 µs per loop
%timeit flat_list=list(itertools.chain.from_iterable(list_of_list))
#10000 loops, best of 3: 176 µs per loop

这个方法也适用于任何迭代对象:

class SquaredRange(object):
    def __init__(self, n): 
        self.range = range(n)
    def __iter__(self):
        for i in self.range: 
            yield i**2

list_of_list = [SquaredRange(5)]*3
flat_list = []
map(flat_list.extend, list_of_list)
print flat_list
#[0, 1, 4, 9, 16, 0, 1, 4, 9, 16, 0, 1, 4, 9, 16]

在Python 2或3中实现这一点的最简单方法是使用pip install morph使用morph库。

代码是:

import morph

list = [[1,2],[3],[5,89],[],[6]]
flattened_list = morph.flatten(list)  # returns [1, 2, 3, 5, 89, 6]

在Python 2.6中,使用chain.from_iterable():

>>> from itertools import chain
>>> list(chain.from_iterable(mi.image_set.all() for mi in h.get_image_menu()))

避免了中间列表的创建。

如果你要扁平化一个更复杂的列表与不可迭代元素或深度大于2,你可以使用以下函数:

def flat_list(list_to_flat):
    if not isinstance(list_to_flat, list):
        yield list_to_flat
    else:
        for item in list_to_flat:
            yield from flat_list(item)

它将返回一个生成器对象,您可以使用list()函数将其转换为列表。注意,yield from syntax可以从python3.3开始使用,但是可以使用显式迭代。 例子:

>>> a = [1, [2, 3], [1, [2, 3, [1, [2, 3]]]]]
>>> print(list(flat_list(a)))
[1, 2, 3, 1, 2, 3, 1, 2, 3]