编程的科学/重复洗涤、冲洗、重复
正如 CME 第 7 章所述,假设
也就是说,y 是 x 的某个函数。如果是这样,那么
有时表示为
其中撇号表示微分。
由于 Sway 对命名变量持宽松的态度,我们可以使用相同的表示法。考虑定义一条直线
var f = line(3,-5); //equivalent to y = 3x - 5
其中 line 构造函数只是对 plus 构造函数调用的包装器
function line(m,b) { plus(term(m,1),term(b,0)); }
然后,我们可以对我们的直线进行微分,如下所示
var f' = f . diff();
由于 f 表示一条直线,而直线的微分会产生直线的斜率,而直线的斜率始终是一个常数 m,因此我们预计评估 在不同点处始终会产生斜率
sway> f' . toString(); STRING: 3x^0 + 0x^-1 sway> f' . value(0); EVALUATION ERROR: :mathError stdin,line 9: exponentiation: cannot divide by zero
发生了什么?问题是 f' 的第二项。我们最终使用 x = 0 来评估该项。什么是x ^ -1当 x 为零时?它是 。这就是导致除零错误的原因。我们应该怎么办?每当一项的指数变为零时,我们应该在微分时强制指数保持为零。
这是一个新的 diff 版本,用于 term
function diff() { if (n == 0) { term(0,0); } else { term(a * n,n - 1); } }
它有效吗?在重新制作 f 和 f' 后,我们有
sway> f' . toString(); STRING: 3x^0 + 0x^0 sway> f' . value(0); REAL_NUMBER: 3.000000000
sway> f' . value(5); REAL_NUMBER: 3.000000000
我们看到它确实有效。
如果我们再次重复微分过程,我们会发现斜率在任何给定点的变化速度。对于一条直线,斜率从不改变,因此我们预计导数的导数(也称为二阶导数)始终为零。
var f'' = f' . diff(); sway> f'' . toString(); STRING: 0x^0 + 0x^0 sway> f'' . value(0); INTEGER: 0 sway> f'' . value(5); INTEGER: 0
已确认。
我们真的需要对可视化做点什么。它正在打印出我们实际上不需要看到的项。让我们通过使 term' 的 toString 方法更复杂来简化输出。请实现以下规则,用于 term 的 toString 方法
- 如果系数为零,则 toString 应返回 "0"
- 如果指数为零,则 toString 应返回 "" + a
- 如果系数和指数都是 1,则 toString 应返回 "x"
- 如果指数为一,则 toString 应返回 "" + a + "x"
- 如果系数为一,则 toString 应返回 "x^" + n
- 否则,toString 应返回 "" + a + "x^" + n
较早的规则优先于较晚的规则。在实现这些规则并重新制作后f, f',以及f'',我们得到以下可视化
sway> f . toString(); STRING: 3x + -5 sway> f' . toString(); STRING: 3 + 0 sway> f'' . toString(); STRING: 0 + 0
稍微好一点。现在我们需要改进 plus 以丢弃值为零的常数项。我们可以在 plus 构造函数的主体中做到这一点
- 如果第一个参数等效于零,则 plus 应返回第二个参数
- 如果第二个参数等效于零,则 plus 应返回第一个参数
- 否则,plus 应返回这个
你的逻辑应该像这样
function plus(p,q) { ... if (p is equivalent to zero) { q; } else if (q is equivalent to zero) { p; } else { this; } }
我们如何确定 plus 的参数是否等效于零?由于我们将零表示为具有零系数的项,因此我们可以使用类似于此的逻辑
if (p is :term && p . coefficient == 0)
现在,我们使用项来表示零的技巧开始露出了它的丑陋面目。我们的代码对于可能在没有阅读本文的情况下阅读我们的代码的人来说(出于维护目的)变得不明显。零是一个常数,因为它永远不会改变;零始终是零。另一方面,一项通常会改变,具体取决于自变量的值。这会导致认知失调,从而增加了理解我们代码的难度。
让我们通过构建一个 'constant' 构造函数来解决这个问题。此构造函数将具有 term 的所有方法,但将更好地表示一个永不改变的值。这是一个尝试
function constant(value) { function toString() { "" + value; } function diff() { ... } function y(x) { ... } this; }
diff 和 y 的实现留作练习,它们应该实现以下逻辑
- 常数的微分是一个常数零
- 常数的 y 值始终是常数的值,无论自变量的值如何
现在,我们可以在 plus 中像这样测试零对象
if (p is :constant && p . value == 0)
看看这段代码多么容易理解。我们现在更新 term 中的 diff 方法以利用我们新的 constant 构造函数。
function term(coefficient,exponent) { ... function diff() { if (n == 0) { constant(0); } else { term(a * n,n - 1); } } ... this; }
有了这些更改,并在重新制作后f, f',以及f''使用 plus、term 和 constant 的新版本,我们得到了以下可视化
sway> f . toString(); STRING: 3x + -5 sway> f' . toString(); STRING: 3 sway> f'' . toString(); STRING: 0
好多了。
反复对一条直线进行微分相当无趣。高阶多项式[1] 更有趣。与其像这样逐个构建一个包含许多项的高阶多项式,
var a = term(1,0); var b = plus(term(2,1),a); var c = plus(term(3,2),b); var y = plus(term(4,3),c);
我们可以整体构建它
var y = plus(term(4,3),plus(term(3,2),plus(term(2,1),term(1,0))));
或者,由于这种整个比例构建可能难以阅读,因此可以使用中缀运算符语法构建 y
var y = term(4,3) plus term(3,2) plus term(2,1) plus term(1,0);
无论你如何制作 y(这完全取决于你的喜好),我们都可以对其进行逐次微分
var y' = y . diff(); var y'' = y' . diff(); var y''' = y'' . diff(); sway> y . toString(); STRING: 4x^3 + 3^x2 + 2x + 1 sway> y' . toString(); STRING: 12x^2 + 6^x + 2 sway> y'' . toString(); STRING: 24x + 6 sway> y''' . toString(); STRING: 24
为了进行数值检查,让我们评估 y'' 在 x = 1 和 2 处的值。
sway> y'' . value(1); INTEGER: 30 sway> y'' . value(2); INTEGER: 54
对象 y''' 应该表示常数 24
sway> y''' . value(1); INTEGER: 24 sway> y''' . value(2); INTEGER: 24
一切看起来都很好。
1. 从数学上证明一项的简化可视化规则。
2. 为 minus 构造函数添加简化。简化类似于 plus,但稍微复杂一些。
3. 为 times 构造函数添加简化。如果任一参数为零,则应返回常数零,如果其中一个参数为常数一,则返回另一个参数。否则,返回 this。
4. 为 div 构造函数添加简化。如果分子(第一个参数)为常数零,则应返回常数零。如果分母(第二个参数)为常数一,则应返回分子。否则,应返回 this。
5. 使用gnuplot在sway中表示并绘制。什么是? 将其绘制在x的函数图上。它代表什么?什么是? 将其绘制在x的函数图上。它代表什么?什么是? 将其绘制在x的函数图上。
6. 使用sway或纸笔完成第82页上的1-3题。
- ↑ 多项式的阶数越高,构成该多项式的项中最大的指数就越大。