函数式编程 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) 阶的。这意味着它循环遍历两个范围的乘积。如果要使用 map
和 filter
,则必须创建所有组合。您可以在过滤之后或之前执行此操作,但是无论您做什么,都不能将所有这些组合与这两个功能结合起来,除非您更改范围 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 中很重要。这是语言的特点之一。许多人会认为列表理解是唯一可读的方式。
然而,有时,尤其是当您处理条件的多次迭代时,将 criteria 与 logic 分开会更清楚。在这种情况下,使用功能方法可能更可取。
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)]
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) 阶的。这意味着它循环遍历两个范围的乘积。如果要使用 map
和 filter
,则必须创建所有组合。您可以在过滤之后或之前执行此操作,但是无论您做什么,都不能将所有这些组合与这两个功能结合起来,除非您更改范围 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 中很重要。这是语言的特点之一。许多人会认为列表理解是唯一可读的方式。
然而,有时,尤其是当您处理条件的多次迭代时,将 criteria 与 logic 分开会更清楚。在这种情况下,使用功能方法可能更可取。
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)]