BUAA-OO 前三次作业“表达式求导" 总结与思考

写在前面

  OO作业已经做了三周,有种“一周蜕一层皮”的感觉,还是挺酸爽的。之前虎头蛇尾的博客经历告诉我,及时发博文总结反思是必要的,很开心能在博客园建一个专属的技术博客。希望这些拙劣的碎碎念可以给自己也给大家提供帮助和启迪,也期待和大家共同交流!

  PS. 讲一个悲伤的故事,这是这个博文的第三稿...前两稿都被博客园和typora无情吞掉了,于是在默写第三遍之后有些话就省去了...希望以后的博客作业能被电脑温柔对待;(

一、需求分析

用JAVA实现多项式的合法性判断和求导运算。

1)多项式是带常数的幂函数之和

2)多项式是带常数的幂函数和基本三角函数的乘积项之和

3)在(2)的基础上,三角函数的自变量可以进行嵌套

二、思路分析

1、基于度量的程序结构分析

类关系图(利用UML Support插件)

第一次

第二次

第三次

 

代码行数统计(利用Statistic插件)

第一次

 

第二次

 

 

第三次

 

代码设计复杂度(利用MetricsReloaded插件)

ev(G)基本复杂度,用来衡量程序非结构化程度
iv(G)模块设计复杂度,用来衡量模块判定结构
v(G)独立路径条数

第一次

 

第二次

 

第三次

2、BUG分析

第一次:

强测中得分 100

互测:由于被 hack 扣分 1 分。这其中包括了 13 个错误,同时hack 他人成功 8 次,得分 18.6 分。

自己错误:1)未判断其他空白字符的不合法性 2)处理第一项前面的符号时,未考虑空白字符\t的情况

他人错误:正则表达式疏漏,特别是对符号和数字间的空格的特殊处理。例如 -   -   5

第二次:

在强测中得分 93.8423

未被hack。hack 他人成功 7 次,得分 0.625 分。

他人错误:正则表达式疏漏,特别是三角函数特殊位置空白字符的判断。例如cos ( x ),      -+x

第三次:

在强测中得分 84.7059

由于被 hack 扣分 2 分。这其中包括了 3 个错误。同时, hack 他人成功 25 次,得分 16.75 分

自己错误:1)某一处合并同类项写挂了,导致求导结果不对 2)乘积项输出时,若为空串应直接return,否则会执行下一条charAt(0)语句导致访问越界。例如:+-sin ((x-x) ) ^ +07

他人错误:同前两次。随机数据都能一串三,也警醒我们自测的重要性。

 

三、知识技能总结

1、正则表达式

(1)正则表达式的组命名:

//正则表达式的组命名
        String sin = "(sin\\((?<quad>.*)\\)(\\^" + num + ")?)";
        String cos = "(cos\\((?<quad>.*)\\)(\\^" + num + ")?)";
//调用
 r = Pattern.compile(sin);
 m = r.matcher(str);
 if (m.matches()) return isFactor(m.group("quad"));     

(2)正则表达式的回溯

java的正则表达式,相当于把我们在C语言中用栈处理表达式运算符的过程封装起来,所以在匹配过程中要考虑到爆栈问题。正则表达式量词匹配模式区别就在于回溯(弹栈)方式的不同。具体如下:

 

 1     private String matchProduct(String str) {
 2         String num = "[+-]?\\d+";
 3         String pow = "x(\\^" + num + ")?";
 4         String sin = "sin\\(x\\)(\\^" + num + ")?";
 5         String cos = "cos\\(x\\)(\\^" + num + ")?";
 6         String factor =
 7                 "(" + num + ")|(" + pow + ")|(" + sin + ")|(" + cos + ")";
 8         String product =
 9                 "^[+-]{1,2}(\\d+\\*)?((" + factor + ")\\*)*+(" + factor + ")";
10         Pattern r = Pattern.compile(product);
11         Matcher m = r.matcher(str);
12         if (m.find()) {
13             return m.group();
14         } else {
15             return "";
16         }
17     }
我觉得比较好的正则匹配写法,用到了占有型匹配

2、Hashmap

 • 第一次作业,直接用指数作为Key即可

 • 第二次作业,需要使用(a,b,c)三元对作为自定义Key

 1 import java.util.Arrays;
 2 public class Tuple {
 3     private final int x;
 4     private final int y;
 5     private final int z;
 6     public Tuple(int x, int y, int z) {
 7         this.x = x;
 8         this.y = y;
 9         this.z = z;
10     }
11     public int getX() {
12         return x;
13     }
14     public int getY() {
15         return y;
16     }
17     public int getZ() {
18         return z;
19     }
20 
21     @Override
22     public boolean equals(Object obj) {
23         if (obj == this) {
24             return true;
25         } else if (obj instanceof Tuple) {
26             return (((Tuple) obj).x == this.x)
27                 && (((Tuple) obj).y == this.y)
28                 && (((Tuple) obj).z == this.z);
29         } else {
30             return false;
31         }
32     }
33 
34     @Override 
35     public int hashCode() {
36         return Arrays.hashCode(new int[]{x, y, z});
37     }
38     @Override 
39     public String toString() {
40         return String.format("(%s, %s, %s)", x, y, x);
41     }
42 }
43 /******************************************************/
44 import java.util.HashMap;
45 public class Main {
46     private static final HashMap<Tuple, Integer> hashMap = new HashMap<>();
47     private static void showHashMapValue(Tuple tuple) {
48         if (hashMap.containsKey(tuple)) {
49             System.out.println(String.format(
50                 "Value of %s is : %s", tuple.toString(), hashMap.get(tuple)));
51         } else {
52             System.out.println(String.format(
53                 "Value of %s not found!", tuple.toString()));
54         }
55     }
56 
57     public static void main(String[] args) {
58         hashMap.put(new Tuple(1, 2, 3), 1);
59         hashMap.put(new Tuple(1, 3, 4), 2);
60         hashMap.put(new Tuple(2, 3, 5), 3);
61         showHashMapValue(new Tuple(1, 2, 3));
62         showHashMapValue(new Tuple(1, 3, 4));
63         showHashMapValue(new Tuple(2, 3, 5));
64         showHashMapValue(new Tuple(2, 4, 0));
65     }
66 }

3、考虑合并同类项的设计思路

4、对拍程序

已上传至GitHub

5、继承与接口

首先明确一点:能用接口就不用继承。

静态绑定与动态绑定

静态绑定:在程序执行前已经被绑定,也就是说在编译过程中就已经明确一个对象\类应该调用哪个方法,此时由编译器获取其他连接程序实现。

动态绑定(多态):在程序运行根据具体对象的类型进行绑定,调用对应的方法。

6、递归下降分析

 1 /*******************分解表达式************************/
 2 import java.util.ArrayList;
 3 public class ExpressionParser {
 4     private enum Status { // 表达式解析状态
 5         BEGIN, // 开始状态
 6         SIGN, // 符号状态
 7         TERM, // 项状态
 8         END, // 结束状态
 9     }
10     
11     private Status status; // 当前状态信息
12     private ArrayList<ExpressionItem> items;
13     
14     private final String string;
15     private int index;
16     
17     public ExpressionParser(String string, int index) {
18         this.string = string;
19         this.index = index;
20         this.status = Status.BEGIN;
21     }
22     
23     public Expression parse() {
24     // 起初为开始状态
25     // 如果读到了+/-,则进入符号状态,否则记录符号为+后进入项状态
26 
27     // 加减状态
28     // 读取+/-,并记录,之后进入项状态
29 
30     // 项状态
31     // 实例化一个新的TermParser,传入字符串以及当前位置
32     // 用此TermParser来生成解析出一个Term,进行存储
33     // 如果接下来还有+/-,则进入加减状态,否则进入结束状态
34 
35     // 结束状态
36     // 整理获取到的值,并返回
37     }
38 }
39 /**************分解乘积项***********************/
40 import java.util.ArrayList;
41 public class TermParser {
42     private enum Status {
43         BEGIN, // 开始状态
44         SIGN, // 符号状态
45         FACTOR, // 因子状态
46         END, // 结束状态
47     }
48     private Status status;
49     
50     private ArrayList<Factor> factors;
51     private final String string;
52     private int index;
53     
54     public TermParser(String string, int index) {
55         this.string = string;
56         this.index = index;
57         this.status = Status.BEGIN;
58     }
59     public Term parse() {
60     // 起初为开始状态
61     // 直接进入因子状态
62 
63     // 符号状态
64     // 读取*后,进入因子状态
65 
66     // 因子状态
67     // 因子状态下,实例化一个FactorParser,传入字符串和位置信息
68     // 使用该FactorParser来解析一个因子,并进行存储
69     // 如果下一个字符为*,则进入符号状态,否则进入结束状态
70 
71     // 结束状态
72     // 整理获取到的值,并返回
73     }
74 }
75 /***************分解因子*******************/
76 public class FactorParser {
77     private final String string;
78     private int index;
79     public FactorParser(String string, int index) {
80         this.string = string;
81         this.index = index;
82     }
83     public Factor parse() {
84     // 解析一个因子
85     
86     // 如果开头是数字,则连续读取一串数字,实例化Constant对象,返回
87     
88     // 如果开头是x,则读取x,实例化XFunction对象,返回
89     
90     // 如果开头是s,则读取sin(x),实例化SinFunction对象,返回
91     
92     // 如果开头是c,则读取cos(x),实例化CosFunction对象,返回
93     }
94 }

7、设计模式

1)工厂模式

2)单例模式

3)装饰者模式

(%cyx)

以第三次作业为例:

我们可以用Factor(因子)作为抽象父类

以具体的ExprFactor、Sin、Cos等作为被装饰者

用可以嵌套的Sin、Cos作为装饰者(注意到它们既是被装饰者也是装饰者)

调用公有的方法——求导,实现对复合函数的求导

四、自己的一点思考

1、学习方法

1)即用即学

  上学期报了java一般专业课,最后摸了一份大作业出来。但这学期正式开始用java编程时,却发现自己之前真正学到的只是偏GUI的一些语法,连正则表达式都还不会用。于是开学初想像学C那样通读语言书,却发现大多数java编程入门书晦涩生硬,也缺少有用的例子。现在再回过头来再看语法,便能理解得更加透彻了,因为在编程中摸爬滚打尝试过一些东西后,语言书变成了使用正确性的印证和内部机制的解释,而不是必须系统学习后才能开始编程的必要前提。因为不同环节侧重的理论知识不同,“即用即学”远比“在没真正使用一个东西的时候通过阅读来提前学习”效率要高。

2)从算法竞赛看工程作业

  算法竞赛追求巧妙的思维、代码短小精悍易编写,遇到特殊的边界条件直接特判来莽,总之快速AC是王道。这种写程序的策略有点像在“贪心”。

  而工程思维可能更像是“动态规划”,追求“全局最优”,而非“当前最优”。“不行的话我就在这添一添,那里删一删”的想法在这时变得很危险,乱搞和特判的补丁只会让程序看起来极其恶心。设计上的全面考虑和可扩展性在此时显得更加有智慧。

  但是,这两者有共同的要求:

1)同样注重代码实现能力,即在规定时间内完成特定功能的“硬实力”。写大作业就像长跑,配速低并不意味着我们可以在长距离长时限下允许思维怠惰。

2)对bug“零”容忍。虽说“世界上不存在没有bug的程序”,但“只要出错就爆零”的紧迫感,如果迁移到工程上或许会敦促我们把作业完成得更好。

2、调试方法

1)边写边调试

从HW1到HW3,我的强测成绩逐渐变形(笑容逐渐消失),其实用来构思的时间逐渐变长,写代码时间逐渐变长,也感到压力逐渐增大。

这些年自己写过的代码量应该有5w+,却面对上千行、面向对象的代码debug时常感到崩溃。我认为做出按照层次来编写程序会有效缓解这一问题:

输入处理(合法性检查)——>求导运算——>输出处理——>合并同类项——>输出优化(指数、系数、符号)——>进阶优化(三角函数)

每个模块编程完成后,顺手debug,既能减少工作量,又能减轻心理压力。

2)构造测试数据

在编码前就尽可能充分考虑不同数据的处理方式,而不是在周二写完代码开始debug的时候才开始造数据。

在编写代码前能够考虑得约细致,编写代码的过程就越顺利,debug的时间也会减少。

但是,对于我们初学者来说,很多实现细节在缺乏足够的经验时确实很难提前考虑到,踩坑和磨练大概是必经之路。

3)小黄鸭调试法

大概这就是“肉眼debug”的精髓。在完成程序后先前前后后捋一遍,就可以大幅度减少在输出调试和单步调试中的许多脑残、手残错误。

其实在思路卡壳不知道该怎么做的时候也可以尝试这个方法,跟身边的人讲一讲思路,就算他们无法给出可行的解决方案,自己也会对问题更加明晰。

所以欢迎大家来和我唠嗑!说不定可以互相启迪> < ~

毕竟哪个程序员都不想每天只对着二次元纸片人和小黄鸭自言自语:))

posted @ 2019-03-26 17:15  Ryan0v0  阅读(260)  评论(0编辑  收藏  举报