高阶导数
有时在一个函数中,我们需要接受一个函数作为参数,返回一个函数作为一个返回值,如果一个函数里面对函数进行操作,我们称之为高阶导数。
函数作参数
例如下面的代码:
- 由1到n的连续和
- 由1到n立方的连续和
- 8 1 ∗ 3 + 8 3 ∗ 5 + 8 5 ∗ 7 + … \frac{8}{1*3 }+\frac{8}{3*5}+\frac{8}{5*7}+\dots 1∗38+3∗58+5∗78+…
def sum_naturals(n):
total, k = 0, 1
while k <= n:
total, k = total + k, k + 1
return total
def sum_cubes(n):
total, k = 0, 1
while k <= n:
total, k = total + k*k*k, k + 1
return total
def pi_sum(n):
total, k = 0, 1
while k <= n:
total, k = total + 8 / ((4*k-3) *(4*k-1)), k + 1
return total
我们观察后可以发现,这三个代码实现极其的相似,具体可以概况为以下的形式
def <name>(n):
total, k = 0 , 1
while k <= n:
total, k = total + <term>(k), k + 1
return total
那么如何抽象函数进行实现,例如第一个例子:
def summation(n, term):
total, k = 0, 1
while k <= n:
total, k = total + term(k), k + 1
return total
def identity(x):
return x
def sum_naturals(n):
return summation(n, identity)
其他例子只需自己定义term函数即可
函数的广泛应用
例如我们要求黄金分割比例,接近1.6的一个数,我们可以采取update的一个函数不断进行更新逼近黄金比例,使用close函数比较是否满足既定目标。那么我们的框架就出来了。
def improve(update, close, guess=1):
while not close(guess):
guess = update(guess)
return guess
接下来才是其他函数的设计
def golden_update(guess):
return 1/guess + 1
def square_close_to_successor(guess):
return approx_eq(guess * guess, guess + 1)
def approx_eq(x, y, tolerance=1e-15):
return abs(x - y) < tolerance
我们直接调用即可
improve(golden_update, square_close_to_successor)
总结以下上面的例子:
- 对函数取个好名和函数的抽象化可以降低问题的复杂性
- 将大问题拆解为小问题从而易于实现
函数的嵌套
上面我们讨论了将函数作为参数进行传递,但是这种方法会使你的全局空间堆满小函数,还有一个问题就是我们受限于参数的传入,比如上面的update函数,我们一定要传入一个参数,不然会报错。那么嵌套函数来了。
比如说我们要计算一个数字的平方根。我们当然可以使用库函数sqrt()计算,现在提供另一种计算方法
反复计算sqrt_update可以逼近平方根。
def average(x, y):
return (x + y)/2
def sqrt_update(x, a):
return average(x, a/x)
如果按照以前的方法,我们还要定义一个close()函数,然后把两个函数传入improve函数里面去。现在提供一种新的方法。
def sqrt(a):
def sqrt_update(x):
return average(x, a/x)
def sqrt_close(x):
return approx_eq(x * x, a)
return improve(sqrt_update, sqrt_close)
那么为什么要这样写,好处又是什么?
这涉及了作用范围以及环境的问题,在先前的方法中,我们将所以的函数都定义在全局环境里,作用范围为全局,而第二种方法定义在函数里面,作用范围为函数里面,不影响外层环境,层次更加清晰。
函数作为返回值
函数作为返回值时会保持它所在的环境,它也很好玩。
比如说当年我高中学的复合函数
h
(
x
)
=
f
(
g
(
x
)
)
h(x)=f(g(x))
h(x)=f(g(x))
def compose1(f, g):
def h(x):
return f(g(x))
return h
给个例子
def square(x):
return x * x
def successor(x):
return x + 1
def compose1(f, g):
def h(x):
return f(g(x))
return h
整活开始
牛顿法
听着很高大尚,实际上我们高中就学过关于导数的定义,用一条切线逼近曲线。而牛顿法可以通过不停的迭代得到函数的零点
我们需要一个函数 f 和它的导数 df ,更新方法为
def newton_update(f, df):
def update(x):
return x - f(x) / df(x)
return update
那么如果我们想要找到函数的零点,我们就需要检查检查f(x)是否逼近0
def find_zero(f, df):
def near_zero(x):
return approx_eq(f(x), 0)
return improve(newton_update(f, df), near_zero)
那么比如说我要计算 f ( x ) = x 2 − a f(x)=x^2-a f(x)=x2−a的零点,我们可以找到它的导数 d f ( x ) = 2 x df(x)=2x df(x)=2x,我们可以得到代码如下
def square_root_newton(a):
def f(x):
return x * x - a
def df(x):
return 2 * x
return find_zero(f, df)
到了这里大家发现没有,这种函数的抽象,这种设计方法非常巧妙,我是学不会了,希望大家也可以写出这样优质的代码。
注:本文为作者学习compsing programs 的笔记