关键词: 实验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这样的、把两个方法实现放在一个类体中,编写独立的类(或者匿名类),是可行的选择。
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().
3.添加过滤器
在accumulate()的基础上,可以在累积计算中添加过滤器(filter)功能,例如求[a,b]之间,每个自然数的立方,并含有7的数之和。
本文整合了我的若干博客内容,原理部分我没有写太多(避免学生考试时直接抄我的博客)!