python-fp

函数式编程

函数式编程源自于数学理论,它似乎也更适用于数学计算相关的场景,因此本文以一个简单的数据处理问题为例,逐步介绍 Python 函数式编程从入门到走火入魔的过程。

问题:计算 N 位同学在某份试卷的 M 道选择题上的得分(每道题目的分值不同)。

首先来生成一组用于计算的伪造数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# @file: data.py
import random
from collections import namedtuple

Student = namedtuple('Student', ['id', 'ans'])

N_Questions = 25
N_Students = 20

def gen_random_list(opts, n):
return [random.choice(opts) for i in range(n)]

# 问题答案 'ABCD' 随机
ANS = gen_random_list('ABCD', N_Questions)
# 题目分值 1~5 分
SCORE = gen_random_list(range(1,6), N_Questions)

QUIZE = zip(ANS, SCORE)
students = [
# 学生答案为 'ABCD*' 随机,'*' 代表未作答
Student(_id, gen_random_list('ABCD*', N_Questions))
for _id in range(1, N_Students+1)
]

print(QUIZE)
# [('A', 3), ('B', 1), ('D', 1), ...
print(students)
# [Student(id=1, ans=['C', 'B', 'A', ...

```


常规的面向过程编程风格,我们需要遍历每个学生,然后遍历每个学生对每道题目的答案并与真实答案进行比较,然后将正确答案的分数累计:


```python

import data
def normal(students, quize):
for student in students:
sid = student.id
score = 0
for i in range(len(quize)):
if quize[i][0] == student.ans[i]:
score += quize[i][1]
print(sid, '\t', score)

print('ID\tScore\n==================')
normal(data.students, data.quize)
"""
ID Score
==================
1 5
2 12
...
"""

通过创建嵌套两个 for 循环来遍历所有题目答案的判断和评分,这完全是为计算机服务的思路,虽然说 Python 中的 for 循环已经比 C 语言更进了一步,通常不需要额外的状态变量来记录当前循环的次数,但有时候也不得不使用状态变量,如上例中第二个循环中比较两个列表的元素。函数式编程的一大特点就是尽量抛弃这种明显循环遍历的做法,而是把注意集中在解决问题本身,一如在现实中我们批改试卷时,只需要将两组答案并列进行比较即可:

from data import students, QUIZE

student = students[0]

# 将学生答案与正确答案合并到一起
# 然后过滤出答案一致的题目
filtered = filter(lambda x: x[0] == x[1][0], zip(student.ans, QUIZE))

print(list(filtered))  
# [('A', ('A', 3)), ('D', ('D', 1)), ...]

正确题目的分数进行累加:

from functools import reduce

reduced = reduce(lambda x, y: x + y[1][1], filtered, 0)  
print(reduced)      

接下来进行推广:

def cal(quize):  
    def inner(student):
        filtered = filter(lambda x: x[0] == x[1][0], zip(student.ans, quize))
        reduced = reduce(lambda x, y: x + y[1][1], filtered, 0)
        print(student.id, '\t', reduced)
    return inner
map(cal(QUIZE), students)