函数式编程与列表推导式

4

马克·卢茨在他的书《学习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)]
>>>

稍后,他指出尽管复杂且嵌套,但可以实现“映射和筛选等效”。

我找到的最接近的方法如下:
>>> 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)]]
>>> 

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

1
取决于你对“等价”的定义。list(filter(lambda i:i[0]%2==0 and i[1]%2==1, map(lambda x:(x//5, x%5), range(25)))) 算不算? - Aran-Fey
我理解“等效”为输入和输出完全相同。输入是两个相同的范围[0,1,2,3,4]。 - Vladimir Zolotykh
4个回答

4

这是对@Kasramvd的解释的补充说明。

Python中的可读性很重要,它是语言的特点之一。许多人认为列表推导式是唯一易读的方式。

然而,有时候,特别是当您使用多个条件迭代时,将标准逻辑分开更清晰。在这种情况下,使用函数方法可能更可取。

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

1
重要的一点是你必须注意到,嵌套列表推导式的时间复杂度是O(n2)。这意味着它正在遍历两个范围的乘积。如果你想使用mapfilter,你必须创建所有的组合。你可以在过滤之前或之后做到这一点,但无论你做什么,都不能用这两个函数得到所有的组合,除非你改变范围和/或修改其他内容。
一个完全可行的方法是使用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级别上执行。当您使用像Python/高级操作这样的lambda函数打破链式结构时,您的代码将不会比列表推导更快。


1
我认为表达式[(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)]
官方Python文档中,我们可以获取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)

0

范围支持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)]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接