函数式编程 vs 列表理解

Functional programming vs list comprehension

Mark Lutz 在他的书中 "Learning Python" 举了一个例子:

>>> [(x,y) for x in range(5) if x%2==0 for y in range(5) if y%2==1]
[(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]
>>>

稍后他评论说 'a map and filter equivalent' 虽然复杂且嵌套,但这是可能的。

我最后得到的最接近的是:

>>> list(map(lambda x:list(map(lambda y:(y,x),filter(lambda x:x%2==0,range(5)))), filter(lambda x:x%2==1,range(5))))
[[(0, 1), (2, 1), (4, 1)], [(0, 3), (2, 3), (4, 3)]]
>>> 

元组的顺序不同,必须引入嵌套列表。我很好奇什么是等效的。

您必须注意的一个重点是您的嵌套列表理解是 O(n2) 阶的。这意味着它循环遍历两个范围的乘积。如果要使用 mapfilter,则必须创建所有组合。您可以在过滤之后或之前执行此操作,但是无论您做什么,都不能将所有这些组合与这两个功能结合起来,除非您更改范围 and/or 修改其他内容。

一种完全实用的方法是使用 itertools.product()filter,如下所示:

In [16]: from itertools import product

In [17]: list(filter(lambda x: x[0]%2==0 and x[1]%2==1, product(range(5), range(5))))
Out[17]: [(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]

另请注意,使用具有两次迭代的嵌套列表理解基本上比多个 map/filter 函数更具可读性。当您的函数只是内置时,使用内置函数的性能比列表理解更快,因此您可以确保所有函数都在 C 级别执行。当你用类似 lambda 函数的东西打破链时,它是 Python/higher 杠杆操作,你的代码不会比列表理解更快。

要附加到 的注释。

可读性在 Python 中很重要。这是语言的特点之一。许多人会认为列表理解是唯一可读的方式。

然而,有时,尤其是当您处理条件的多次迭代时,将 criterialogic 分开会更清楚。在这种情况下,使用功能方法可能更可取。

from itertools import product

def even_and_odd(vals):
    return (vals[0] % 2 == 0) and (vals[1] %2 == 1)

n = range(5)

res = list(filter(even_and_odd, product(n, n)))

我认为表达式 [(x, y) for x in range(5) if x % 2 == 0 for y in range(5) if y % 2 == 1] 中唯一令人困惑的部分是隐藏了隐式 flatten 操作。

让我们先考虑表达式的简化版本:

def even(x):
    return x % 2 == 0

def odd(x):
    return not even(x)

c = map(lambda x: map(lambda y: [x, y], 
                      filter(odd, range(5))), 
        filter(even, range(5)))

print(c)
# i.e. for each even X we have a list of odd Ys:
# [
#   [[0, 1], [0, 3]],
#   [[2, 1], [2, 3]],
#   [[4, 1], [4, 3]]
# ]

但是,我们需要完全相同但扁平化的列表 [(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]

official python docs我们可以得到flatten函数的例子:

from itertools import chain
flattened = list(chain.from_iterable(c))  # we need list() here to unroll an iterator
print(flattened)

这基本上等同于以下列表理解表达式:

flattened = [x for sublist in c for x in sublist]
print(flattened)

# ... which is basically an equivalent to:
# result = []
# for sublist in c:
#   for x in sublist:
#     result.append(x)

范围支持 step 参数,所以我想出了这个解决方案,使用 itertools.chain.from_iterable 来展平内部列表:

from itertools import chain
list(chain.from_iterable(
    map(
        lambda x:           
            list(map(lambda y: (x, y), range(1, 5, 2))),
        range(0, 5, 2)
        )
))    

输出:

Out[415]: [(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]