跳转到内容

编程科学/链条帮的工作

来自维基教科书,开放的世界,开放的书籍

有时候,正如 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 使用纸笔

  1. 这就是面向对象编程方法的核心:相关的对象具有相同的方法,但方法针对特定的对象进行了定制。
  2. 这个小技巧说明了计算机程序设计中的一个重要原则:尽可能为你的代码用户做更多的事情。我们可以强制用户传入一个变量对象,或者我们可以允许用户像以前一样传入一个符号,并自己完成工作。


时光流逝 · 滑坡

华夏公益教科书