python列表理解;压缩列表列表?


77

伙计们 我正在尝试找到问题的最优雅解决方案,并且想知道python是否为我想做的事情内置了任何东西。

我正在做的是这个。我有一个列表,A并且我有一个f接受项目并返回列表的函数。我可以使用列表推导来转换所有内容,A例如:

[f(a) for a in A]

但这返回一个列表列表;

[a1,a2,a3] => [[b11,b12],[b21,b22],[b31,b32]]

我真正想要的是获取扁平化的列表;

[b11,b12,b21,b22,b31,b32]

现在,其他语言也有它。传统上flatmap用函数式编程语言来称呼它,而.Net则称它为SelectMany。python有类似的东西吗?有没有一种巧妙的方法可以将函数映射到列表并展平结果?

我要解决的实际问题是:从目录列表开始,查找所有子目录。所以;

import os
dirs = ["c:\\usr", "c:\\temp"]
subs = [os.listdir(d) for d in dirs]
print subs

currentliy给了我一个列表列表,但是我真的想要一个列表。

Answers:


116

您可以在单个列表理解中嵌套嵌套的迭代:

[filename for path in dirs for filename in os.listdir(path)]

(至少在功能上)等效于:

filenames = []
for path in dirs:
    for filename in os.listdir(path):
        filenames.append(filename)

65
尽管很聪明,但是很难理解并且很难理解。
Curtis Yallop 2014年

2
并没有真正按照要求回答问题。这是一种解决方法,可以避免最初遇到此问题。如果您已经有一个列表列表,该怎么办。例如,如果您的列表列表是多处理模块的map函数的结果,该怎么办?也许itertools解决方案或reduce解决方案是最好的。
Dave31415

22
Dave31415:[ item for list in listoflists for item in list ]
风铃草

9
“可读性”是一个主观判断。我发现此解决方案可读性强。
Reb.Cabin,2015年

9
我认为它也是可读的,直到我看到术语的顺序... :(
cz

82
>>> from functools import reduce
>>> listOfLists = [[1, 2],[3, 4, 5], [6]]
>>> reduce(list.__add__, listOfLists)
[1, 2, 3, 4, 5, 6]

我猜itertools解决方案比这更有效,但是感觉很pythonic。

在Python 2中,它避免了仅出于单个列表操作(因为reduce是内置的)的目的而导入库。


5
这绝对是最好的解决方案。
Connor Doyle

我应导入,以所谓的reduce,是它pandasscipy还是functools
沙戎

1
在Python 2中,它是内置的。对于Python3,functools版本是同一回事。
朱利安

该死的 !那真是天才
Tilak Maddy


27

这个问题提出了flatmap。提出了一些实现,但是它们可能不必要创建中间列表。这是一个基于迭代器的实现。

def flatmap(func, *iterable):
    return itertools.chain.from_iterable(map(func, *iterable))

In [148]: list(flatmap(os.listdir, ['c:/mfg','c:/Intel']))
Out[148]: ['SPEC.pdf', 'W7ADD64EN006.cdr', 'W7ADD64EN006.pdf', 'ExtremeGraphics', 'Logs']

在Python 2.x中,itertools.map代替map


18

您可以直接执行以下操作:

subs = []
for d in dirs:
    subs.extend(os.listdir(d))

是的,这很好(尽管不如@Ants'好),所以我给它+1以表示其简单性!
亚历克斯·马丁里

16

您可以使用常规加法运算符来连接列表:

>>> [1, 2] + [3, 4]
[1, 2, 3, 4]

内置函数sum将按顺序添加数字,并且可以选择从特定值开始:

>>> sum(xrange(10), 100)
145

结合以上内容以拼合列表列表:

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

您现在可以定义flatmap

>>> def flatmap(f, seq):
...   return sum([f(s) for s in seq], [])
... 
>>> flatmap(range, [1,2,3])
[0, 0, 1, 0, 1, 2]

编辑:我刚刚在评论中看到了批评另一个答案,我认为使用此解决方案,Python会不必要地构建和垃圾收集许多较小的列表是正确的。因此,可以说的最好的一点是,如果您习惯了函数式编程,它就非常简单明了:-)


这是拉平列表的最酷方法。
Ulrich Scheller

12
import itertools
x=[['b11','b12'],['b21','b22'],['b31']]
y=list(itertools.chain(*x))
print y

itertools将在python2.3及更高版本上运行


8
subs = []
map(subs.extend, (os.listdir(d) for d in dirs))

(但蚂蚁的答案更好;为他+1)


为此使用reduce(或sum,可以节省许多字符和一个导入;-),这是错误的-您一直无用地扔掉旧列表,为每个d新建一个。@Ants的答案正确(@Steve的聪明才能接受它!)。
亚历克斯·马丁里

您一般不能说这是一个不好的解决方案。这取决于性能甚至不是问题。除非有必要进行优化,否则简单会更好。这就是为什么reduce方法可能最适合许多问题的原因。例如,您有一个缓慢的函数,该函数会生成数百个对象的列表。您想通过使用多重处理“地图”功能来加快速度。因此,您创建了4个流程,并使用reduce对其进行平面映射。在这种情况下,reduce函数很好并且可读性强。就是说,您最好指出为什么这可能不是最佳的。但这并不总是次优的。
Dave31415

4

您可以这样尝试itertools.chain()

import itertools
import os
dirs = ["c:\\usr", "c:\\temp"]
subs = list(itertools.chain(*[os.listdir(d) for d in dirs]))
print subs

itertools.chain()返回一个迭代器,因此传递给list()


3

Google为我带来了下一个解决方案:

def flatten(l):
   if isinstance(l,list):
      return sum(map(flatten,l))
   else:
      return l

2
如果它也处理生成器表达式会更好一些,并且如果您解释了如何使用它会更好很多……
ephemient


1

您可以使用pyxtension

from pyxtension.streams import stream
stream([ [1,2,3], [4,5], [], [6] ]).flatMap() == range(7)

0
If listA=[list1,list2,list3]
flattened_list=reduce(lambda x,y:x+y,listA)

这样就可以了。


如果子列表很大,这是一个非常低效的解决方案。+两个列表之间的运算符为O(n + m)
juanpa.arrivillaga
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.