面向数学家的函数式编程

本教程讨论一些函数式编程技术,数学家或使用Python进行科学计算的人可能会对这些技术感兴趣。我们首先简要概述过程性编程和面向对象编程,然后讨论函数式编程技术。在此过程中,我们将简要回顾一下对函数式编程的内置支持,包括 filterlambdamapreduce 。本教程最后提供了一些有关使用Python进行函数式编程的详细信息的参考资料。

编程风格

Python支持几种风格的编程。您可以通过将程序编写为指令列表来以过程化风格进行编程。假设您想在整数上实现加法和乘法。这样做的程序性程序如下:

sage: def add_ZZ(a, b):
....:     return a + b
...
sage: def mult_ZZ(a, b):
....:     return a * b
...
sage: add_ZZ(2, 3)
5
sage: mult_ZZ(2, 3)
6

Python型模块 operator 将几个常见的算术运算符和比较运算符定义为命名函数。加法是在内置函数中定义的 operator.add 中定义了乘法。 operator.mul 。上述示例可按如下方式完成:

sage: from operator import add
sage: from operator import mul
sage: add(2, 3)
5
sage: mul(2, 3)
6

另一种常见的编程风格称为面向对象编程。可以将对象视为同时封装数据和功能的代码。您可以在以下面向对象的实现中封装整数加法和乘法:

sage: class MyInteger:
....:     def __init__(self):
....:         self.cardinality = "infinite"
....:     def add(self, a, b):
....:         return a + b
....:     def mult(self, a, b):
....:         return a * b
...
sage: myZZ = MyInteger()
sage: myZZ.cardinality
'infinite'
sage: myZZ.add(2, 3)
5
sage: myZZ.mult(2, 3)
6

使用MAP进行函数式编程

函数式编程是将程序分解为各种功能的另一种编程风格。Python内置函数 mapreducefilter 允许您以函数式风格进行编程。请注意,在Python3中(与Python2相比),这些函数具有不同的行为,并且 reduce 已删除:如果要使用 reduce 在Python3中,您必须从导入 functools

功能::

map(func, seq1, seq2, ...)

接受一个函数 func 和一个或多个序列,并应用 func 这些序列中的元素。特别是,您最终得到的列表如下::

[func(seq1[0], seq2[0], ...), func(seq1[1], seq2[1], ...), ...]

在许多情况下,使用 map 允许您以简洁的方式表达程序的逻辑,而无需使用列表理解。例如,假设您有两个整数列表,并且希望按元素将它们相加。实现这一目标的清单理解如下:

sage: A = [1, 2, 3, 4]
sage: B = [2, 3, 5, 7]
sage: [A[i] + B[i] for i in range(len(A))]
[3, 5, 8, 11]

或者,您也可以使用Python内置加法函数 operator.add 与.一起 map 要达到同样的效果::

sage: from operator import add
sage: A = [1, 2, 3, 4]
sage: B = [2, 3, 5, 7]
sage: list(map(add, A, B))
[3, 5, 8, 11]

的优势 map 您不需要像在上面的列表理解中那样显式定义for循环。

使用lambda定义小函数

有时,您想要编写一个简短的单行函数。您可以重写上述加法函数,如下所示:

sage: def add_ZZ(a, b): return a + b
...

或者,您可以使用 lambda 语句以更清晰的方式做同样的事情。上面的加法和乘法函数可以使用 lambda 详情如下:

sage: add_ZZ = lambda a, b: a + b
sage: mult_ZZ = lambda a, b: a * b
sage: add_ZZ(2, 3)
5
sage: mult_ZZ(2, 3)
6

一旦你们结合在一起,事情就会变得更有趣 maplambda 陈述。作为练习,您可以尝试编写一个简单的函数,该函数实现 Chinese Remainder Theorem 。您可以将列表理解与以下内容一起使用 maplambda 如下所示。在这里,参数 A 是一个整数列表, M 是模数的列表。**

sage: def crt(A, M):
....:     Mprod = prod(M)
....:     Mdiv = list(map(lambda x: Integer(Mprod / x), M))
....:     X = list(map(inverse_mod, Mdiv, M))
....:     x = sum([A[i]*X[i]*Mdiv[i] for i in range(len(A))])
....:     return mod(x, Mprod).lift()
...
sage: A = [2, 3, 1]
sage: M = [3, 4, 5]
sage: x = crt(A, M); x
11
sage: mod(x, 3)
2
sage: mod(x, 4)
3
sage: mod(x, 5)
1

为了在环上产生一个随机矩阵,比如说 ZZ ,您可以从定义一个矩阵空间开始,然后获得该矩阵空间的随机元素:

sage: MS = MatrixSpace(ZZ, nrows=5, ncols=3)
sage: MS.random_element()  # random
<BLANKLINE>
[ 6  1  0]
[-1  5  0]
[-1  0  0]
[-5  0  1]
[ 1 -1 -3]

或者,您可以使用函数 random_matrix **

sage: random_matrix(ZZ, nrows=5, ncols=3)  # random
<BLANKLINE>
[  2 -50   0]
[ -1   0  -6]
[ -4  -1  -1]
[  1   1   3]
[  2  -1  -1]

下一个示例使用 map 要构造随机整数矩阵列表,请执行以下操作:

sage: rows = [randint(1, 10) for i in range(10)]
sage: cols = [randint(1, 10) for i in range(10)]
sage: rings = [ZZ]*10
sage: M = list(map(random_matrix, rings, rows, cols))
sage: M[0]  # random
<BLANKLINE>
[ -1  -3  -1 -37   1  -1  -4   5]
[  2   1   1   5   2   1  -2   1]
[ -1   0  -4   0  -2   1  -2   1]

如果您希望对矩阵的条目进行更多控制,而不是 random_matrix 功能许可,您可以使用 lambda 与.一起 map 详情如下:

sage: rand_row = lambda n: [randint(1, 10) for i in range(n)]
sage: rand_mat = lambda nrows, ncols: [rand_row(ncols) for i in range(nrows)]
sage: matrix(rand_mat(5, 3))  # random
<BLANKLINE>
[ 2  9 10]
[ 8  8  9]
[ 6  7  6]
[ 9  2 10]
[ 2  6  2]
sage: rows = [randint(1, 10) for i in range(10)]
sage: cols = [randint(1, 10) for i in range(10)]
sage: M = list(map(rand_mat, rows, cols))
sage: M = list(map(matrix, M))
sage: M[0]  # random
<BLANKLINE>
[ 9  1  5  2 10 10  1]
[ 3  4  3  7  4  3  7]
[ 4  8  7  6  4  2 10]
[ 1  6  3  3  6  2  1]
[ 5  5  2  6  4  3  4]
[ 6  6  2  9  4  5  1]
[10  2  5  5  7 10  4]
[ 2  7  3  5 10  8  1]
[ 1  5  1  7  8  8  6]

将序列简化为一个值

该功能 reduce 获取两个参数的函数,并将其应用于给定的序列,以将该序列减少为单个值。该功能 sum 是一个示例,它是 reduce 功能。下面的示例代码使用 reduce 和内置函数 operator.add 将给定列表中的所有整数相加。这之后是使用 sum 要完成相同的任务::

sage: from functools import reduce
sage: from operator import add
sage: L = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
sage: reduce(add, L)
55
sage: sum(L)
55

在下面的示例代码中,我们将向量视为实数列表。这个 dot product 然后使用以下函数实现 operator.addoperator.mul ,与内置的Python函数结合使用 reducemap 。然后,我们将展示如何 summap 可以组合在一起产生相同的结果。**

sage: from functools import reduce
sage: from operator import add
sage: from operator import mul
sage: U = [1, 2, 3]
sage: V = [2, 3, 5]
sage: reduce(add, map(mul, U, V))
23
sage: sum(map(mul, U, V))
23

或者,您可以使用Sage对点产品的内置支持::

sage: u = vector([1, 2, 3])
sage: v = vector([2, 3, 5])
sage: u.dot_product(v)
23

下面是中国剩余定理的一个实现,不使用 sum 和以前一样。下面的版本使用 operator.add 并定义了 mul3 乘三个数而不是两个。**

sage: from functools import reduce
sage: def crt(A, M):
....:     from operator import add
....:     Mprod = prod(M)
....:     Mdiv = list(map(lambda x: Integer(Mprod / x), M))
....:     X = map(inverse_mod, Mdiv, M)
....:     mul3 = lambda a, b, c: a * b * c
....:     x = reduce(add, map(mul3, A, X, Mdiv))
....:     return mod(x, Mprod).lift()
...
sage: A = [2, 3, 1]
sage: M = [3, 4, 5]
sage: x = crt(A, M); x
11

使用过滤器进行过滤

Python内置函数 filter 接受一个参数和一个序列的函数。然后,它返回给定序列中所有这些项的列表,以便新列表中的任何项都会导致给定函数返回 True 。从某种意义上说,您过滤掉了满足给定函数中定义的某个条件(S)的所有项。例如,您可以使用 filter 过滤掉1到50之间(包括1和50)的所有素数。**

sage: list(filter(is_prime, [1..50]))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]

对于给定的正整数 n, the Euler phi function 计算整数的个数 a1 leq a leq n ,以便 gcd(a, n) = 1 。您可以使用列表理解来获得所有这些信息 a 是什么时候 n = 20 **

sage: [k for k in range(1, 21) if gcd(k, 20) == 1]
[1, 3, 7, 9, 11, 13, 17, 19]

一种功能方法是使用 lambda 定义一个函数来确定给定的整数是否相对质数为20。 filter 而不是列表理解,以获得所有必需的 a 的.::

sage: is_coprime = lambda k: gcd(k, 20) == 1
sage: list(filter(is_coprime, range(1, 21)))
[1, 3, 7, 9, 11, 13, 17, 19]

该功能 primroots 下面定义的返回以给定的正素数为模的所有原根 p 。它使用 filter 获取之间的整数列表 1p - 1 ,包括在内,列表中的每个整数相对于乘法群的顺序是相对质数的 (ZZ/pZZ)^{ast} 。**

sage: def primroots(p):
....:     g = primitive_root(p)
....:     znorder = p - 1
....:     is_coprime = lambda x: gcd(x, znorder) == 1
....:     good_odd_integers = filter(is_coprime, [1..p-1, step=2])
....:     all_primroots = [power_mod(g, k, p) for k in good_odd_integers]
....:     all_primroots.sort()
....:     return all_primroots
...
sage: primroots(3)
[2]
sage: primroots(5)
[2, 3]
sage: primroots(7)
[3, 5]
sage: primroots(11)
[2, 6, 7, 8]
sage: primroots(13)
[2, 6, 7, 11]
sage: primroots(17)
[3, 5, 6, 7, 10, 11, 12, 14]
sage: primroots(23)
[5, 7, 10, 11, 14, 15, 17, 19, 20, 21]
sage: primroots(29)
[2, 3, 8, 10, 11, 14, 15, 18, 19, 21, 26, 27]
sage: primroots(31)
[3, 11, 12, 13, 17, 21, 22, 24]

进一步的资源

这是一篇相当简短的关于使用Python进行函数式编程的教程。Python标准文档有一个内置函数列表,其中许多函数在函数式编程中很有用。例如,您可能想要阅读有关 allanymaxmin ,以及 zip 。Python型模块 operator 有许多内置的算术运算符和比较运算符,每个运算符都实现为一个函数,其名称反映了其预期用途。对于算术和比较运算,建议您参考 operator 模块来确定是否有满足您需求的内置函数。该模块 itertools 具有许多内置功能,可以有效地处理项目序列。

使用Python进行函数式编程的另一个有用资源是 Functional Programming HOWTO 作者:A.M.库奇林。史蒂文·F·洛特的书 Building Skills in Python 有一章是关于 Functional Programming using Collections 。另请参阅本章 Functional Programming 出自马克·皮尔格林的书 Dive Into Python

您可能还想考虑尝试使用 Haskell 用来表达数学概念。有关Haskell表达数学算法的示例,请参阅J.Gibbons的文章 Unbounded Spigot Algorithms for the Digits of Pi 发表在《美国数学月刊》上。