实验3 累积函数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yqj2065/article/details/78883690

关键词: 实验4 求和方法的一般化 , 模板方法模式,通用函数,高阶函数

若干个函数拥有相似的算法或代码结构,可以从中提取一个算法的框架,从而获得一般性的、通用的函数/方法 。

1. 求和函数

考虑数列或级数求和(或求近似值)的若干例子。
求[a,b]之间自然数的和;//
求调和级数前n项的和,H(n)= 1/1 + 1/2 + 1/3 + 1/4 + ... + 1/n
求Pi,pi/8  = 1/(1*3)+1/(5*7)+1/(9*11)+...
求函数的定积分
程序员可以快速地编写上述问题的代码,如下面的例程4-1所示。
package chap4.templateMethod.sum;
import static yqj2065.util.Print.pln;
/**
 * 按照SICP 1.3.1的命名, 
 * sum_integers求代数和, sum_cubes求立方数的代数和,
 * pi求1/(1*3)+1/(5*7)+1/(9*11)+...的前n项的和
 *
 * @author yqj2065
 */

public class TestSum{ 
    //1、若干的函数拥有相似的代码结构
    public static void test() {
        pln("代数和" + sum_integers(1, 10));
        pln("pi=" + pi(10000));
        /*pln("pi=" + 8*new Sum_pi().sum(1, 10000));
        Accumulate.IItem item = x -> 1.0 / (x * (x + 2));
        Accumulate.INext next = x -> x + 4;
        double pi = 8 * Accumulate.getSum(1, 10000, next, item);
        pln("pi=" + pi);*/
    } 
    
    //////////////////////求[a,b]之间自然数的和//////////////////////////
    public static int sum_integers(int a, int b) {
        int sum = 0;
        for (int i = a; i <= b; i++) {
            sum += i;
        }
        return sum;
    }

    //////////////////////////立方数的代数和//////////////////////////////
    private static double cube(int x) {
        return x * x * x;
    }

    public static int sum_cubes(int a, int b) {
        int sum = 0;
        for (int i = a; i <= b; i++) {
            sum += cube(i);
        }
        return sum;
    }

    /**
     * 求Pi,the sum of a sequence of terms in the series
     * 1/(1*3)+1/(5*7)+1/(9*11)+...
     *
     * @param x
     * @return
     */
    private  static double item(int x) {
        return 1.0 / (x * (x + 2));
    }

    public static double pi(int n) {
        double sum = 0;
        for (int i = 1; i <= n; i += 4) {
            sum += item(i);
        }
        return sum * 8;
    }

    //////////////////////////////////调和级数前n项的和
    /**
     * 调和级数Harmonic numbers, H(n)= 1/1 + 1/2 + 1/3 + 1/4 + ... + 1/n
     *
     * @param n
     * @return
     */
    public static double harmonic(int n) {
        double sum = 0.0;
        for (int i = 1; i <= n; i++) {
            sum += 1.0 / i;
        }
        return sum;
    }

}

若干个求和函数拥有相似的代码结构,代码中不同/变化的部分主要有2处:步进和累加项,可变的两部分设计为抽象方法。注意,有些代码结构的不同部分,如求和函数返回值不同,则取更大的数据类型;参数个数不同,则取最多的个数;如求PI时返回sum*8,而乘以系数,可以留给调用者自己添加。

很容易从中提取通用的、一般性的方法,代码中不同/变化的部分主要有2处: 步进和累加项,可变的两部分设计为抽象方法。例程4-2所示的模板方法getSum(int a, int b),可变的两部分设计为抽象方法并放在父类型Sum中,由Sum派生各种级数求和的子类型,例如Harmonic/调和级数。
package chap4.templateMethod.sum;


/**
 * 模板方法模式
 *
 * @author yqj2065
 */
public abstract class Sum  {
    public final double getSum(int  a, int  b) {//template Method
        double sum = 0;
        for (; a <= b; a = next(a)) {
            sum += item(a);
        }
        return sum;
    }
    public abstract double item(int x);
    public abstract int next(int x);
}

class Sum_pi extends Sum {// pi/8    
    @Override public double item(int x) {
        return 1.0 / (x * (x + 2));
    }

    @Override public int next(int i){
        return i + 4;
    }
}

class Harmonic extends Sum {
    @Override    public int next(int i){        return i+1;    }
    @Override    public double item(int x) {        return 1.0 / x;    } 
}

这个例子符合[GoF•5.10]关于模板方法模式的定义,如果程序员需要Harmonic这样的、把两个方法实现放在一个类体中,编写独立的类(或者匿名类),是可行的选择。


但是需要注意的是,Sum存在 2个抽象方法的组合问题,特别是考虑求函数的定积分时,item(double x) 的实现有无限的可能性。从设计角度看,程序员通常不在意GoF的模板方法模式中的抽象方法是否分离出去。换言之,更多时候,各种求和计算没有作为类而独立存在的必要,因此将 Sum作为Context类而2次使用策略模式,重构得到新的类型Accumulate。
package chap4.templateMethod.sum;
public final class Accumulate{
    public interface IItem {
        double item(int x);
    }
    public interface INext {
        int next(int x);
    }
    
    public static final double getSum(int a, int b, INext iNext, IItem iItem) {
        double sum = 0;
        for (; a <= b; a = iNext.next(a)) {
            sum += iItem.item(a);
        }
        return sum;
    }

} 

// TestSum
    public static void testAccumulate() {
        Accumulate.IItem item = x -> 1.0 / (x * (x + 2));
        Accumulate.INext next = x -> x + 4;
        double pi = 8 * Accumulate.getSum(1, 10000, next, item);
        pln("pi=" + pi);    
    }

2.求积与累积函数

按照上面的样子,可以编写通用的函数getProduct()求积。

public static final double getProduct(int a, int b, INext iNext, IItem iItem) {
        double r = 1;
        for (; a <= b; a = iNext.next(a)) {
            r *= iItem.item(a);
        }
        return r;
    }
比较求和与求积,代码中变化的部分有2处:累积变量的初始值(求和时为0,求积时为1)和累加计算的操作符。更进一步,从求和与求积中可以提炼出更一般性的函数——累积函数accumulate。
累加计算的操作符,分别为+和*。Java中不能够直接将+和*作为函数的参数,需要用函数替换+和*,可以使用例程3-1中定义的DoubleOP。


在getSum()基础上,可以利用下面的公式计算定积分。首先,需要将Accumulate所有int修改为double。【有些学生只copy代码不看文字,这里红色警示】
   

          函数f 的a到b定积分(不知道网页中能不能编辑数学公式) =  [f(a+dx/2) + f(dx + a+dx/2)+ (2dx + a+dx/2)+…] * dx

    public static  final double accumulate(double defaultValue,DoubleOP how_op,double a, double b, INext iNext, IItem iItem) {
        double value = defaultValue;
        for (; a <= b; a = iNext.next(a)) {  
            value = how_op.op(value, iItem.item(a));
        }
        return value;
    }

    //计算定积分
    public static final double integral(double a, double b, double dx, IItem f) {
        INext next = x -> dx + x;
        return dx * getSum(a + dx / 2, b, next, f);
    }
附注:累积计算只有两种,因此可以用char how_op替换double defaultValue,DoubleOP how_op,编写5参数的accumulate().

扫描二维码关注公众号,回复: 3355264 查看本文章

3.添加过滤器

在accumulate()的基础上,可以在累积计算中添加过滤器(filter)功能,例如求[a,b]之间,每个自然数的立方,并含有7的数之和。



本文整合了我的若干博客内容,原理部分我没有写太多(避免学生考试时直接抄我的博客)!



猜你喜欢

转载自blog.csdn.net/yqj2065/article/details/78883690