近期公司有项目上使用的代码涉及调用外部API接口进行多页请求,并需要把对应的数据返回后进行处理。开发人员使用了多层for嵌套循环处理,从逻辑上看,确实for循环比较简单也比较容易理解,不过性能上会略差一些。在进行响应时延过高的问题分析时,通过换用map函数代替for可以进行效率的提升。本篇就总结下python下的map、reduce方法。

一、map方法

map()方法会将 一个函数 映射到序列的每一个元素上,生成新序列,包含所有函数返回值。他传入的参数是一个函数和一个list序列。在python2.X中 map函数返回的是list列表,在python3新版本里返回的是迭代器,需要通过list(map(fun1,list1))才可以返回结果。

map(function_to_apply, list_of_inputs)

function_to_apply代表函数、list_of_inputs代表输入序列。比如,实现一个求平方的操作:

 1items = [1, 2, 3, 4, 5]
 2squared = list(map(lambda x: x**2, items))
 3# 等价于下面的写法
 4items = [1, 2, 3, 4, 5]
 5def f(x):
 6    return x**2
 7squared = list(map(f, items))
 8# 等价于以下for循环的写法
 9items = [1, 2, 3, 4, 5] # 列表
10squared = []
11for i in items:
12    squared.append(i**2)

上面的函数,使用map内部实现的原理如下:

map

map()方法会将 一个函数 映射到序列的每一个元素上,生成新序列,包含所有函数返回值。也就是说序列里每一个元素都被当做x变量,放到一个函数f(x)里,其结果是f(x1)、f(x2)、f(x3)……组成的新序列。

二、reduce函数

相比于map,reduce的操作稍稍难理解一点点。它也是规定一个映射,不过不是将一个元素映射成一个结果。而是将两个元素归并成一个结果。并且它并不是调用一次,而是依次调用,直到最后只剩下一个结果为止。其语法如下:

1reduce(function, iterable[, initializer])
2function代表函数
3iterable序列
4initializer初始值可选

以下列出个实现阶乘的示例如下:

 1# 导入reduce
 2from functools import reduce
 3# 定义函数
 4def f(x,y):
 5    return x*y
 6# 定义序列,含1~10的元素
 7items = range(1,11)
 8# 使用reduce方法
 9result = reduce(f,items)
10print(result)

依次求和的示例如下:

1from functools import reduce
2def f(a, b):
3    return a + b
4print(reduce(f, [1, 2, 3, 4]))
5# 其等价于以下实现
6print(reduce(lambda x, y: x + y, [1, 2, 3, 4]))

其内部执行原理如下:

reduce

map和reduce相同的是接受传入的参数都是函数和list参数,不过reduce可以接受第三个参。

三、map与reduce结合

在大数据里会有一个mapreduce概念,其实在python里两个函数结合起来,确实可以达到mapreduce的效果,这里给出一个代词数量统计的示例,代码如下:

1from collections import Counter
2texts = ['apple bear peach grape', 'grape orange pear']
3def mp(text):
4    words = text.split(' ')
5    return Counter(words)
6print(reduce(lambda x, y: x + y, map(mp, texts)))

执行结果如下:

1>>> print(reduce(lambda x, y: x + y, map(mp, texts)))
2Counter({'grape': 2, 'apple': 1, 'bear': 1, 'peach': 1, 'orange': 1, 'pear': 1})

四、filter函数与compress函数

filter的英文是过滤,所以它的使用就很明显了。它的用法和map有些类似,我们编写一个函数来判断元素是否合法。通过调用filter,会自动将这个函数应用到容器当中所有的元素上,最后只会保留运行结果是True的元素,而过滤掉那些是False的元素。例如,以下通过推导式进行奇数提取的,就可以通过filter函数实现提取:

1arr = [1, 3, 2, 4, 5, 8]
2[i for i in arr if i % 2 > 0 ]
3#使用filter实现如下:
4list(filter(lambda x: x % 2 > 0, arr))

和filter函数类似的,还有一个compress函数,其是在itertools包里的一个函数。可以通过传给的布尔值列表,找到值为真的结果:

1from itertools import compress
2student = ['xiaoming', 'xiaohong', 'xiaoli', 'emily']
3scores = [60, 70, 80, 40]
4>>> pass = [i > 60 for i in scores]
5>>> print(pass)
6[False, True, True, False]
7>>> list(compress(student, pass))
8['xiaohong', 'xiaoli']

五、写在最后

python已经帮我们造好了很多比较容易实现的方法,可以通过引用这些方法,快速的达到我们想法实现的目标,在实际操作过程中,要尽可能的避免使用大的for循环嵌套。最后再列一个示例,可以替代for循环的总结:

 1numbers = [1,2,3,4,5,6]
 2odd_numbers = []
 3squared_odd_numbers = []
 4total = 0
 5# filter for odd numbers
 6for number in numbers:
 7  if number % 2 == 1:
 8     odd_numbers.append(number)
 9# square all odd numbers
10for number in odd_numbers:
11  squared_odd_numbers.append(number * number)
12# calculate total
13for number in squared_odd_numbers:
14  total += number
15# calculate average
16#上面的内容,可以通过如下函数简单实现:
17from functools import reduce
18numbers = [1,2,3,4,5,6]
19odd_numbers = filter(lambda n: n % 2 == 1, numbers)
20squared_odd_numbers = map(lambda n: n * n, odd_numbers)
21total = reduce(lambda acc, n: acc + n, squared_odd_numbers)