人们都说 python 语言简洁优雅,表达力强。下面我们通过温州中学 python 训练平台上的一些题目来~~体会它的简洁性~~(完成一些最短代码挑战)。
警告⚠:请不要直接复制本文中的代码提交到 OJ 上。
if-else
表达式
if-else
不仅可以作控制语句,也可作表达式:
判断能否构成三角形
a, b, c = map(int, input().split())
print('yes' if a+b > c and a+c > b and b+c > a else 'no')
也可以堆叠:
密码翻译
conv = lambda c: 'a' if c == 'z' else 'A' if c == 'Z' else chr(ord(c)+1) if c.isalpha() else c
print(''.join(map(conv, input())))
注意过多的堆叠可能导致代码可读性下降。在本文所展示的大多代码中,为了完成最短代码挑战,还使用了短变量名,省略中间变量,lambda 函数等手段。读者在实践中应当考虑权衡代码可读性。
可迭代对象与函数式编程初探
让我们从读入一行空白字符分割的整数说起:a, b, c = map(int, input().split())
input()
从标准输入中读入一行;_.split()
将其按照空白字符将其分割成一个字符串的列表;map(int, _)
将 int
函数作用于列表中每一个元素(将每一个元素转换为整数型),并返回一个迭代器。将其解包并赋值给 a, b, c
:
>>> input()
11 45 14
'11 45 14'
>>> _.split()
['11', '45', '14']
>>> map(int, _)
<map object at 0x7f290437cca0>
>>> list(_) # 为了查看迭代器的所有元素,将其转换为列表
[11, 45, 14]
>>> a, b, c = _
>>> print(a, b, c)
11 45 14
下面介绍 map
和 filter
的语法。
map
的作用就是对一个序列中的每一个元素作一个变换,其名字可以理解为映射。简单的 map
语法是 map(func, iter)
,其中 func
是一个函数对象,iter
是一个可迭代对象。假如 iter
对应的序列为 [a0, a1, a2, ...]
那么 map
的返回值就对应 [func(a0), func(a1), func(a2), ...]
。
filter
的语法是 filter(predicate, iter)
,返回值也是一个迭代器。predicate
是一个接受一个参数,返回一个布尔值的函数对象。它将 iter
中每一个使 predicate
取值为真的元素筛选出来。比如:
>>> def is_odd(x):
... return x%2 == 1
...
>>> list(filter(is_odd, [c, b, a]))
[45, 11]
再比如:
讲话模式
import re
s, d = input().lower(), {}
for wd in re.findall('[a-z]+', s):
d[wd] = d.get(wd, 0) + 1
mf = max(d.values())
print(min(filter(lambda x: d[x] == mf, d)), mf)
解释:当上述程序运行到末两行时,d
已经是一个含有所有单词及其对应出现次数的字典。mf
是单词出现次数的最大值。lambda x: d[x] == mf
是一个函数对象,传入参数 x
,如果字典 d
中 x
对应的值(单词 x
的出现次数)等于 mf
(出现次数最多),它返回真。filter(_, d)
则从字典的键中选出“出现次数最多”的键(单词),并 min(_)
取出字典序最小者。
诶,好像节省的码量不多(
请注意:程序中的正则表达式
[a-z]+
不能准确描述题目中所定义的单词,但暂时能通过所有数据。
正则表达式博大精深,在此篇幅所限不作介绍。
可见,python 的函数式编程非常依赖迭代器的概念。基于迭代器的内置函数还有很多。
潜伏者
def q(): print('Failed'); exit()
p1, p2, p3, d, s = input(), input(), input(), {}, set()
for u, v in zip(p1, p2):
if u in d and d[u] != v: q()
d[u] = v
for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
if c not in d or d[c] in s: q()
s.add(d[c])
print(''.join(d[c] for c in p3))
解释:第三行中, zip(p1, p2)
将两个可迭代对象同时遍历,节省了枚举下标访问带来的时间开销、码量(有时,还有更多变量带来的含义混乱)。具体地,zip(p1, p2)
会返回一个迭代器,对应序列 [(p1[0], p2[0]), (p1[1], p2[1]), (p1[2], p2[2]), ...]
,for
会遍历这个序列并将每个元组解包赋值给 u, v
。
重组最大数
print(''.join(sorted(input(), reverse=1)))
解释:sorted(x)
返回 x
中所有元素升序排序后的列表。
生成器表达式和列表推导式
方括号中含有 for 的表达式,称为列表推导式,可以推导出一个含有自变量的表达式的列表,如:
>>> [2*x + 1 for x in [a, b, c]]
[23, 91, 29]
方括号换成圆括号,则是生成器表达式,生成器返回的是迭代器而不是列表,这样就可以支持惰性求值,加快运行速度。一个特性是生成器表达式的圆括号可以与函数调用的括号共用。
寻找完美数
a, b = int(input()), int(input())
print('\n'.join(str(x) for x in [6, 28, 496, 8128] if a <= x <= b))
解释:[6, 28, 496, 8128]
预先算好数据范围内的所有完美数。(str(x) for x in _ if a <= x <= b)
将列表中在给定范围内的数字选出来,转成字符串。
诶,好像 map 和 filter 能做到的,生成器表达式和列表推导式都能做,那么要 map 和 filter 有何用呢?
数字统计
l, r = map(int, input().split())
print(sum(str(x).count('2') for x in range(l, r+1)))
**(11月8日更新)**极端的运用:
彩色图像转成黑白图像
import sys
raw = sys.stdin.readlines()
n, m = map(int, raw[0].split())
bw = [''.join(map(lambda r, g, b: str(+(r*0.299 + g*0.587 + b*0.114 < 132)),
*[map(int, ln.split(','))]*3)) for ln in raw[1:1+n]]
print('\n'.join(bw))
for ln in raw[n+2:]:
i, j = map(int, ln.split())
print(bw[i-1][j-1])
解释(图像的一行是如何处理的):
- 正号添加在逻辑值前面,可以将其提升为整数(0 或 1),所以
conv = lambda r, g, b: str(+(r*0.299 + g*0.587 + b*0.114 < 132))
是一个接受三个参数,计算黑白值并返回字符串'0'
或'1'
的函数。 ln
是输入文件中的一行;it = map(int, _.split(','))
是一个迭代器,含有这一行中以逗号分隔的整数;[it]*3
是含有三个这个迭代器的列表;map(conv, *_)
将该列表解包传给map
,相当于map(conv, it, it, it)
。由于 python 保证参数的求值顺序是从左到右,且刚刚的列表乘法是元素的浅拷贝,所以三个元素是同一个迭代器——这非常关键,所以map
会三次访问it
,conv
就按顺序得到连续的三个整数(而不是同一个整数三次)。(如果你能看懂这里 more-itertools 中的grouper
函数,那么你应该能看懂这里的。)''.join(_)
,就能得到一个表示黑白图像的一行的 01 字符串。
参考资料
最后,欢迎读者来信(qq邮箱)讨论。