Python 合并map()和reduce(),不难发现基于一些简单的定义就可以创建高阶函数。下面介绍如何组合map()
函数和reduce()
函数生成map-reduce
函数。
from typing import Callable, Iterable, Any
def map_reduce(
map_fun: Callable,
reduce_fun: Callable,
source: Iterable) -> Any:
return reduce(reduce_fun, map(map_fun, source))
这个由map()
函数和reduce()
函数组成的函数有3个参数:一个映射操作,一个归约操作,以及一个待处理的数据序列。
上面的定义非常宽泛,很难从中看出对数据类型的要求。由于映射和归约可能是非常复杂的操作,下面更严格地定义map-reduce
函数:
from typing import Callable, Iterable, TypeVar
T_ = TypeVar("T_")
def map_reduce(
map_fun: Callable[[T_], T_],
reduce_fun: Callable[[T_, T_], T_],
source: Iterable[T_]) -> T_:
return reduce(reduce_fun, map(map_fun, source))
该定义多了几项约束。首先,可迭代对象需要包含类型一致的数据,我们把这个类型绑定为T_
类型变量;然后,map()
函数接收类型为T_
的参数,并生成相同类型的结果;最后,归约函数接收两个T_
类型的参数,返回一个同类型的参数。对于一个简单的数值计算应用来说,使用类型变量T_
施加类型约束效果很好。
更多时候需要更严格地定义映射函数,例如Callable[[T1_], T2_]
就体现了映射函数的核心特点:输入类型T1_
和输出类型T2_
可能是不同的。归约函数则需要保证输入和输出函数类型一致:Callable[[T2_, T2_], T2_]
。
可以分别提供映射函数和归约函数来得到计算平方和的归约定义,如下所示:
def sum2_mr(source: Iterable[float]) -> float:
return map_reduce(
lambda y: y ** 2, lambda x, y: x + y, source)
这里使用了lambda y: y ** 2
作为平方计算的映射函数,归约部分使用lambda x, y: x + y
作为参数。无须专门指定初始值,因为它就是平方函数映射后序列的第一个值。
上面的参数lambda x, y: x + y
相当于+
运算符,Python的operator
模块中将所有算术运算符定义为短函数,下面用它简化上面的map-reduce
定义:
from operator import add
def sum2_mr2(source: Iterable[float]) -> float:
return map_reduce(lambda y: y ** 2, add, iterable)
这里使用了operator.add
方法代替匿名函数来对数据进行求和。
计算可迭代对象中数值的个数如下所示:
def count_mr(source: Iterable[float]) -> float:
return map_reduce(lambda y: 1, lambda x, y: x+y, source)
首先通过lambda y: 1
将每个元素映射为1,再通过匿名函数或者operator.add
做归约汇总,就得到了元素的个数。
总的说来,可以使用reduce()
函数创建任何类型的归约,将大型数据集转换为单个数值。不过在使用reduce()
函数时,需要注意一些限制。
避免像下面这样使用reduce()
函数:
reduce(operator.add, list_of_strings, "")
该函数能运行,Python可以把add
运算符应用于字符串集合,但使用"".join (list_of_strings)
效率更高。通过timit
测试发现使用reduce()
组合字符串的时间复杂度是 O(n^2),且非常慢。在实际应用中,如果不仔细研究运算符处理复杂数据集的机制,很难发现效率的瓶颈在哪里。