技术赋能#2:Python可迭代对象的相关函数(1)

Python内置了许多有用的工具函数与类,用于处理可迭代对象与迭代器产生的数据流。本文将对这些工具函数或类作简要介绍,包括mapfilterenumeratezipsumall/anymin/maxsorted

本文是本系列的上一篇文章“技术赋能#1:Python的可迭代对象、迭代器与生成器”的延续,建议先了解这一篇再继续阅读。

map函数

map是Python中常用的一个函数(类),它接受一个函数与一个或多个可迭代对象作为输入,并输出一个迭代器(map实例)。

例如,当我们需要得到一个数列的平方时,我们可以使用map函数这样编写代码:

1
2
3
4
5
6
7
>>> def square(x):
... return x**2
>>> m = map(square, range(10))
>>> for i in m:
... print(i, end=" ")
...
0 1 4 9 16 25 36 49 64 81

这样写有点繁琐了,为了简化下文的代码,本文将使用Python提供的“lambda表达式(匿名函数)”与“序列解包”两个语法。上面的代码等价于:

1
2
>>> print(*map(lambda x: x**2, range(10)))
0 1 4 9 16 25 36 49 64 81

上面提到的两种语法如果你没学到也没关系,你只需要知道lambda x: x**2和前面的square函数的定义相同,print*+可迭代对象可以代替上面的那个循环。

如果设输入map的第一个参数为函数f(x)f(x),第二个参数为数列a1,a2,,an\langle a_1, a_2, \ldots, a_n\rangle,则这个map调用的输出为f(a1),f(a2),,f(an)\langle f(a_1), f(a_2), \ldots, f(a_n)\rangle。在上面的例子中,数列{ai}\{a_i\}为 0-9 的自然数列,函数 f(x)=x2f(x)=x^2 ,因此输出是 0-9 内各个自然数的平方。

当然,在Python中map接受的序列(可迭代对象)当然不止数列,字符串与字符串序列也是可以的,下面这个例子将列表与字符串中的小写字母全部转化成大写字母(字符串也是可迭代对象哦):

1
2
3
4
>>> print(*map(lambda s:s.upper(), ["Alpha", "Beta", "Gamma"]))
ALPHA BETA GAMMA
>>> print(*map(lambda s:s.upper(), "Alpha"))
A L P H A

此外,输入和输出不一定要保持相同的数据类型,这一段代码可以将输入的字符串形式的数字转化为整型变量的序列:

1
2
>>> print(*map(int, "1,1,2,3,5,8,13,21".split(",")))
1 1 2 3 5 8 13 21

上文提到,map也可以接受多个可迭代对象作为输入,此时函数f的参数数量需要与可迭代对象的数量相同,当多个输入序列长度不一样时,输出序列的长度与较短的序列的相同。这个例子使用map函数将两个序列对应位置的数相乘:

1
2
>>> print(*map(lambda x,y: x*y, [1,2,3,4,5], [6,7,8,9,10,11]))
6 14 24 36 50

filter函数

filter的参数形式与map相似,其接受一个函数f一个可迭代对象作为输入,输出一个迭代器(filter实例)。其中输入函数f的返回值必须是布尔类型(True/False)或是能隐式转换为布尔类型,函数值为True的值将留在输出序列中,为False的值将被筛去。下面两个例子分别筛选出了输入序列中的偶数与素数(O(n2)O(n^2)的暴力算法,考试时不要使用哦):

1
2
3
4
>>> print(*filter(lambda n:n%2==0, range(20)))
0 2 4 6 8 10 12 14 16 18
>>> print(*filter(lambda n: n==2 or n>2 and all(n%i for i in range(2,int(n**0.5)+1)), range(100)))
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

enumerate函数

enumerate函数也是Python中很常用的一个函数,它接受一个可迭代对象和一个可选参数start作为输入,返回值是元组的可迭代对象,每个元组的第二项为原序列的内容,第一项为这一项在序列中的编号。在Python中它常常伴随for循环出现,例如:

1
2
3
4
5
6
7
8
9
>>> lst = ["apple", "pear", "peach", "banana", "grape"]
>>> for i, name in enumerate(lst):
... print(i, name)
...
0 apple
1 pear
2 peach
3 banana
4 grape

可选参数start制定了初始的计数值,默认为0:

1
2
3
4
5
6
7
8
>>> for i, name in enumerate(lst, start=5):
... print(i, name)
...
5 apple
6 pear
7 peach
8 banana
9 grape

zip函数

zip函数用于对多个可迭代对象进行打包,对于输入序列a1,a2,,an\langle a_1,a_2,\ldots,a_n\rangleb1,b2,,bn,,bn+t\langle b_1,b_2,\ldots,b_n,\ldots,b_{n+t}\ranglezip函数将对应位置的值打包成元组返回,并丢弃多余的值,即返回(a1,b1), (a2,b2), , (an,bn)\langle (a_1,b_1),~(a_2,b_2),~\ldots,~(a_n,b_n)\rangle

1
2
>>> print(*zip(range(0,10,2), range(1,13,2)))
(0, 1) (2, 3) (4, 5) (6, 7) (8, 9)

sum函数

顾名思义,sum函数对输入的可迭代对象求和,例如:

1
2
>>> sum(range(1,101))
5050

all/any函数

all/any函数都接受一个可迭代对象作为输入,要求这个可迭代对象内的值可以被隐式转化为布尔值,返回值为单个布尔值。all函数在逻辑上与多项的and(与逻辑)相同,当且仅当可迭代对象内的所有值都为True或相当于True时返回True,否则返回Falseany函数则在逻辑上与or(或逻辑)相同,当可迭代对象内存在True时就返回True,否则(全为False时)返回False

与Python的逻辑表达式(布尔表达式)相同,allany函数也都是短路求值的,也就是说它们会在发现条件不满足时立刻返回(对于all来说就是第一次遇到False的时候),而不是过完整个序列。下面的这段代码演示了这一特性:

1
2
3
4
5
>>> r = iter([5,4,3,2,1,0,-1,-2,-3])
>>> all(r) # 消耗 5,4,3,2,1,0 遇到0时直接返回False
False
>>> print(*r) # 获取剩余值
-1 -2 -3

此外,当输入序列为空时,all将返回Trueany则返回False

1
2
3
4
>>> all([])
True
>>> any([])
False

min/max函数

这两个函数都用于求序列中的极值,min返回最小值而max返回最大值。除了可迭代对象之外,它们还会接受可选参数keykey参数是一个函数,用法类似于下文的sortedkey参数,这里仅演示用法,例如:

1
2
3
4
5
6
>>> min(map(abs, range(-15,11)))
0
>>> max(map(abs, range(-15,11)))
15
>>> max(range(-15,11), key=abs)
-15

sorted函数

sorted函数对输入序列非原地排序,其返回值一定是一个排好序的列表,除了可迭代对象之外,它还接受reversekey两个参数。reverse参数指定是否逆序排序,这个很好理解;而key参数接受的是一个函数,这个函数接受输入序列中的值作为输入,返回一个有序对象(例如数值、字符串等),sorted函数将以返回的有序对象作为排序的标准。

在下面的示例中,第一个sorted调用对lst顺序排序,第二个对其逆序排序,第三个以每个值的绝对值作为标准进行排序(绝对值小的排在前面),第四个以 f(x)=(x2.2)2f(x)=(x-2.2)^2 为标准排序。需要分组排序时,可以让key参数接受的函数返回一个元组,例如第五个sorted调用,先按照每个数除以3的余数分组,然后对每一组按值的大小逆序排序。

Python采用的Timsort算法是稳定排序算法,也就是说标准值相同的对象会按照它们在原序列中的顺序排序(相对位置保持不变),例如第三个示例中-33的绝对值相同,但是原序列中-3在前面,所以输出的列表中-3也在3的前面。

1
2
3
4
5
6
7
8
9
10
11
>>> lst = [1,-3,2,5,4,-1,-2,-4,3,0]
>>> sorted(lst) #1
[-4, -3, -2, -1, 0, 1, 2, 3, 4, 5]
>>> sorted(lst, reverse=True) #2
[5, 4, 3, 2, 1, 0, -1, -2, -3, -4]
>>> sorted(lst, key=abs) #3
[0, 1, -1, 2, -2, -3, 3, 4, -4, 5]
>>> sorted(lst, key=lambda x:(x-2.2)**2) #4
[2, 3, 1, 4, 0, 5, -1, -2, -3, -4]
>>> sorted(range(15), key=lambda x:(x%3, -x)) #5
[12, 9, 6, 3, 0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2]

本文介绍了Python内置的一些与可迭代对象有关的函数以及它们的使用方法,在本系列的下一篇文章内我们将介绍Python标准库中的itertools库,它提供了更多与可迭代对象有关的功能,敬请期待!