如何从列表理解而不是嵌套列表中获得平坦的结果?

问题描述 投票:0回答:15

我有一个列表

A
和一个函数
f
,它接受
A
的一项并返回一个列表。我可以使用列表理解来转换
A
中的所有内容,就像
[f(a) for a in A]
一样,但这会返回一个列表列表。假设我的输入是
[a1,a2,a3]
,结果是
[[b11,b12],[b21,b22],[b31,b32]]

我怎样才能得到扁平列表

[b11,b12,b21,b22,b31,b32]
?换句话说,在 Python 中,我如何才能获得函数式编程语言中传统上称为
flatmap
或 .NET 中的
SelectMany
的内容?

(在实际代码中,

A
是目录列表,
f
os.listdir
。我想构建一个子目录的平面列表。)


另请参阅:如何从列表列表中制作平面列表?了解创建列表后展平列表列表的更常见问题。

functional-programming python list-comprehension
15个回答
159
投票

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

[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)

93
投票
>>> from functools import reduce  # not needed on Python 2
>>> list_of_lists = [[1, 2],[3, 4, 5], [6]]
>>> reduce(list.__add__, list_of_lists)
[1, 2, 3, 4, 5, 6]

itertools
解决方案更高效,但这感觉非常Pythonic。


73
投票

您可以在

itertools
食谱中找到很好的答案

import itertools

def flatten(list_of_lists):
    return list(itertools.chain.from_iterable(list_of_lists))

38
投票

提出的问题

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


23
投票

你可以直接做:

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

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将用这个解决方案不必要地构建和垃圾收集大量较小的列表是正确的。因此,如果您习惯了函数式编程,那么可以说的最好的一点是,它非常简单和简洁:-)


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

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


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

itertools 将在 python2.3 及更高版本中工作


5
投票

你可以尝试

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()


4
投票

这是最简单的方法:

def flatMap(array):
  return reduce(lambda a,b: a+b, array) 

‘a+b’指的是两个列表的串联


1
投票

您可以使用pyxtension

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

0
投票

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

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

0
投票

我在寻找

flatmap
并首先发现了这个问题。
flatmap
基本上是对原始问题要求的概括。如果您正在寻找一种简洁的方法来定义可求和集合(例如列表)
flatmap
,您可以使用

sum(map(f,xs),[])

只比写的长一点点

flatmap(f,xs)

但一开始也可能不太清楚。

最明智的解决方案是将

flatmap
作为编程语言中的基本函数,但只要它不是,您仍然可以使用更好或更具体的名称来定义它:

# `function` must turn the element type of `xs` into a summable type.
# `function` must be defined for arguments constructed without parameters.
def aggregate(function, xs):
    return sum( map(function, xs), type(function( type(xs)() ))() )

# or only for lists
aggregate_list = lambda f,xs: sum(map(f,xs),[])

不幸的是,字符串不可求和,这对它们不起作用。
你可以做的

assert( aggregate_list( lambda x: x * [x], [2,3,4] ) == [2,2,3,3,3,4,4,4,4] )

但是你不能

def get_index_in_alphabet(character):
    return (ord(character) & ~0x20) - ord('A')

assert(aggregate( lambda x: get_index_in_alphabet(x) * x, "abcd") == "bccddd")

对于字符串,您需要使用

aggregate_string = lambda f,s: "".join(map(f,s))  # looks almost like sum(map(f,s),"")

assert( aggregate_string( lambda x: get_index_in_alphabet(x) * x, "abcd" ) == "bccddd" )

这显然是一团糟,需要不同的函数名称,甚至不同类型的语法。希望 Python 的类型系统将来能够得到改进。


0
投票

您还可以使用

flatten
功能使用
numpy
:

import numpy as np

matrix = [[i+k for i in range(10)] for k in range(10)]
matrix_flat = np.array(arr).flatten()

numpy 文档

flatten


-2
投票
If listA=[list1,list2,list3]
flattened_list=reduce(lambda x,y:x+y,listA)

这样就可以了。

© www.soinside.com 2019 - 2024. All rights reserved.