近期公司有项目上使用的代码涉及调用外部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代表输入序列。比如,实现一个求平方的操作:
items = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, items))
# 等价于下面的写法
items = [1, 2, 3, 4, 5]
def f(x):
return x**2
squared = list(map(f, items))
# 等价于以下for循环的写法
items = [1, 2, 3, 4, 5] # 列表
squared = []
for i in items:
squared.append(i**2)
上面的函数,使用map内部实现的原理如下:
map()方法会将 一个函数 映射到序列的每一个元素上,生成新序列,包含所有函数返回值。也就是说序列里每一个元素都被当做x变量,放到一个函数f(x)里,其结果是f(x1)、f(x2)、f(x3)……组成的新序列。
二、reduce函数
相比于map,reduce的操作稍稍难理解一点点。它也是规定一个映射,不过不是将一个元素映射成一个结果。而是将两个元素归并成一个结果。并且它并不是调用一次,而是依次调用,直到最后只剩下一个结果为止。其语法如下:
reduce(function, iterable[, initializer])
function:代表函数
iterable:序列
initializer:初始值(可选)
以下列出个实现阶乘的示例如下:
# 导入reduce
from functools import reduce
# 定义函数
def f(x,y):
return x*y
# 定义序列,含1~10的元素
items = range(1,11)
# 使用reduce方法
result = reduce(f,items)
print(result)
依次求和的示例如下:
from functools import reduce
def f(a, b):
return a + b
print(reduce(f, [1, 2, 3, 4]))
# 其等价于以下实现
print(reduce(lambda x, y: x + y, [1, 2, 3, 4]))
其内部执行原理如下:
map和reduce相同的是接受传入的参数都是函数和list参数,不过reduce可以接受第三个参。
三、map与reduce结合
在大数据里会有一个mapreduce概念,其实在python里两个函数结合起来,确实可以达到mapreduce的效果,这里给出一个代词数量统计的示例,代码如下:
from collections import Counter
texts = ['apple bear peach grape', 'grape orange pear']
def mp(text):
words = text.split(' ')
return Counter(words)
print(reduce(lambda x, y: x + y, map(mp, texts)))
执行结果如下:
>>> print(reduce(lambda x, y: x + y, map(mp, texts)))
Counter({'grape': 2, 'apple': 1, 'bear': 1, 'peach': 1, 'orange': 1, 'pear': 1})
四、filter函数与compress函数
filter的英文是过滤,所以它的使用就很明显了。它的用法和map有些类似,我们编写一个函数来判断元素是否合法。通过调用filter,会自动将这个函数应用到容器当中所有的元素上,最后只会保留运行结果是True的元素,而过滤掉那些是False的元素。例如,以下通过推导式进行奇数提取的,就可以通过filter函数实现提取:
arr = [1, 3, 2, 4, 5, 8]
[i for i in arr if i % 2 > 0 ]
#使用filter实现如下:
list(filter(lambda x: x % 2 > 0, arr))
和filter函数类似的,还有一个compress函数,其是在itertools包里的一个函数。可以通过传给的布尔值列表,找到值为真的结果:
from itertools import compress
student = ['xiaoming', 'xiaohong', 'xiaoli', 'emily']
scores = [60, 70, 80, 40]
>>> pass = [i > 60 for i in scores]
>>> print(pass)
[False, True, True, False]
>>> list(compress(student, pass))
['xiaohong', 'xiaoli']
五、写在最后
python已经帮我们造好了很多比较容易实现的方法,可以通过引用这些方法,快速的达到我们想法实现的目标,在实际操作过程中,要尽可能的避免使用大的for循环嵌套。最后再列一个示例,可以替代for循环的总结:
numbers = [1,2,3,4,5,6]
odd_numbers = []
squared_odd_numbers = []
total = 0
# filter for odd numbers
for number in numbers:
if number % 2 == 1:
odd_numbers.append(number)
# square all odd numbers
for number in odd_numbers:
squared_odd_numbers.append(number * number)
# calculate total
for number in squared_odd_numbers:
total += number
# calculate average
#上面的内容,可以通过如下函数简单实现:
from functools import reduce
numbers = [1,2,3,4,5,6]
odd_numbers = filter(lambda n: n % 2 == 1, numbers)
squared_odd_numbers = map(lambda n: n * n, odd_numbers)
total = reduce(lambda acc, n: acc + n, squared_odd_numbers)