编程科学/链条帮的工作
有时候,正如 CME 第九章中的 SPT 指出,你会发现自己苦苦思索如何区分像这样的复杂事物
解决方法是通过抽象细节来使表达式更简单。令 *a* 为多项式
我们可以用项的总和来表示它
现在,*y* 可以改写为
为了找到 *y* 对 *x* 的导数,我们使用 *链式法则*
换句话说,*y* 对 *x* 的微分等于(重写后的)*y* 对(新的)*a* 的微分乘以(新的)*a* 对 *x* 的微分。
在编程中,我们有
var a = term(1,:x,2) plus term(17,:x,0); var y = term(3,:a,2);
var dy/dx = y . diff(:a) times a . diff(:x);
看看 *dy/dx* 代表什么,我们有
sway> a . toString(); STRING: x^2 + 17 sway> y . toString(); STRING: 3x^2 sway> dy/dx . toString(); STRING: 6a * 2x
用手进行最终的代入(),我们得到了 *dx/dy* 的最终答案
dy/dx = 6 * (x^2 + 17) * 2x = (6x^2 + 102) * 2x = 12x^3 + 204x
如果能自动完成链式法则的代入步骤,那将是一件好事,但这样做需要一些工作,无论是概念上还是编程上。我们首先扩展项变量抽象的概念。回想一下,最初,我们将项变量硬编码为 *x*。接下来,我们允许 *term* 构造函数的调用者传入独立变量作为 Sway 符号。抽象的下一步是允许项变量本身成为一个项(或项的总和或其他任何东西)。如果我们这样做,那么项的 *diff* 方法将变成链式法则
function diff(wrtv) { term(a * n,iv,n - 1) times iv . diff(wrtv); }
显然,独立变量 *iv* 不再是一个符号,而必须是一个具有 *diff* 方法的对象。因此,为了表示形式为
的项,我们需要使用对象来表示变量 *x*。这种变量对象的构造函数将类似于 term 和 plus 构造函数。也就是说,它必须具有 *value*、*toString* 和 *diff* 方法[1]
function variable(name) { function value(x) { x; } function toString() { "" + name; } function diff(wrtv) { if (wrtv == name) { constant(1); } else { constant(0); } } this; }
寻找简单变量导数的规则是:如果与之相关的变量与独立变量匹配,则结果为 1。如果不是,则结果为 0。我们将使用常量来表示数字 0 和 1;这样,我们系统中的每一项,包括数字,都具有 *toString*、*value* 和 *diff* 方法。
为了简化我们的生活,我们可以将以下逻辑添加到 *term* 构造函数的主体中。如果传入一个符号作为独立变量,我们将将其转换为一个 *variable* 对象。[2] 这样,我们就可以像以前一样传入符号。以下是一个新的 term 构造函数的模拟
function term(a,iv,n) { function value(x) { ... } function toString() { ... } function diff(wrtv) { if (n == 0) { constant(0); } else { term(a * n,iv,n - 1) times iv . diff(wrtv); } } if (iv is :SYMBOL, iv = variable(iv)); this; }
我们还需要修改 term 的 *toString* 方法来调用 *iv'* 的可视化。以下是新的非简化版本
function toString() { "" + a + iv . toString() + "^" + n; }
让我们测试一下修改后的系统
var t = term(4,:x,3); var t' = t . diff(:x);
sway> t . toString(); STRING: 4x^3 sway> t . iv; OBJECT: <OBJECT 1958> sway> t' . toString(); STRING: 12x^2
对于简单变量来说,它似乎工作正常。现在让我们试试我们原来的问题
首先,我们制作我们的多项式
var a = term(1,:x,2) plus term(17,:x,0); var y = term(3,a,2); // not :a
现在,我们可视化它
sway> y . toString(); STRING: 31x^2 + 17x^0^2
糟糕!我们做错了什么?我们需要将 *iv* 的可视化括起来
function toString() { "" + a + "(" + iv . toString() + ")" + "^" + n; }
用 term 的新可视化重新制作 *a* 和 *y* 会得到
var a = term(1,:x,2) plus term(17,:x,0); var y = term(3,a,2);
sway> y . toString(); STRING: 3(1x^2 + 17x^0)^2
如果你使用简化的 *toString* 方法来表示项,你应该得到
3(x^2 + 17)^2
正是我们想要的!现在让我们对 *y* 求导(你需要启动你的 *times* 构造函数)
var y' = y . diff();
sway> y' . toString(); STRING: 6(x^2 + 17) * 2x
不错!
我们还有两个小问题。第一个是上面结果不是最简单的形式。不幸的是,生成最简单的形式是一个相当复杂的过程(因为不总是清楚哪种形式是最简单的)。所以我们将在此止步,并感到满意。
另一个小问题出现在我们可视化 *a* 的时候
sway> a . toString(); STRING: (x)^2 + 17
我们对括号有点过了。显然,当独立变量是一个复杂对象时,我们想要使用括号。然而,当它是一个简单变量时,我们应该避免使用括号。这项任务留作练习。
1. 解释为什么项的 *diff* 方法不再需要测试与之相关的变量是否与独立变量匹配。
2. 实现 *one* 函数。
3. 修改项的简化 *toString* 方法,使其仅在独立变量是复杂的,且系数或指数不等于 1 时才打印括号。*提示*:创建一个 *term* 方法,如果 *iv'* 的可视化是复杂的,则在 *iv'* 的可视化周围添加括号,如果它不是复杂的,则只需返回 *iv'* 的可视化。在适当的地方从 *toString* 调用此方法。
4. CME 第 100 页,1,8 使用 Sway
5. CME 第 100 页,2,3,5,8 使用纸笔
- ↑ 这就是面向对象编程方法的核心:相关的对象具有相同的方法,但方法针对特定的对象进行了定制。
- ↑ 这个小技巧说明了计算机程序设计中的一个重要原则:尽可能为你的代码用户做更多的事情。我们可以强制用户传入一个变量对象,或者我们可以允许用户像以前一样传入一个符号,并自己完成工作。