一只开心咩  

2019面向对象程序设计第一单元总结

一.三次作业的设计思路

Ⅰ.仅含常数和幂函数的多项式求导

(1)思路一:WF判断与分项结合,避免大正则爆栈问题

​ 第一次作业中,多项式的结构具有极强的规律性,所以不少同学想到用正则表达式匹配整串,但忽略了大正则存在回溯次数过多可能会导致爆栈。因此正确的处理策略之一是,单独处理输入中的空格,正负号以及非法字符,然后用正则匹配表达式中的每一项,得到的所有项相加,若与原表达式不相同,则判为“WRONG FORMAT!”,否则,则输出求导结果。

正则匹配处理空格与符号后的每一项:

Pattern pattern = Pattern.compile("([-+]+(([-+]?(\\d+\\*)?x(\\^[-+]?\\d+)?)|([-+]?\\d+)))");

然后用Matcher中的find()函数实现分离每一项,用Matcher中的group()函数实现项处理。

(2)思路二:用ArrayList、LinkedList或HashMap实现动态插入新项

​ 笔者在第一次作业中采用的是利用泛型的LinkedList实现

 public class PolyHandler {
    //利用链表构造求导后的新多项式
    private LinkedList<String[]> polynomial;
    
    //构造函数
    PolyHandler(LinkedList<String[]> polynomial) {
        this.polynomial = polynomial;
    }
    

重点:在动态添加元素的过程中,实现同类项的合并,能够大大减少优化复杂度。

​ 笔者利用PolyHandler类中的merge函数实现求导后多项式合并新项

void merge(String[] item)

下图为第一次作业我的类图

StringMatcher用于初步判断输入是否合法

ItemHandler为项处理类

PolyHandler为多项式处理类

MainHandler为主类

Ⅱ.仅含常数、幂函数和三角函数的多项式求导

(1)思路一:本次作业仍可利用正则进行分项

​ 笔者认为,第二次作业相比第一次多项式求导作业而言,在处理非法输入与求导方面对整体架构的改变不大,因此笔者沿用了第一次作业的设计,复用了较多的类。

​ 正则分项:

 Pattern pattern = Pattern.compile("[-+](\\d+|(x(\\^[-+]?\\d+)?)|" +
                "(sin\\(x\\)(\\^[-+]?\\d+)?)|(cos\\(x\\)(\\^[-+]?\\d+)?))" +
                "(\\*[-+]?(\\d+|(x(\\^[-+]?\\d+)?)" +
                "|(sin\\(x\\)(\\^[-+]?\\d+)?)|(cos\\(x\\)(\\^[-+]?\\d+)?)))*");

emmm......看上去是不是很复杂呢,在第二次作业我们已经可以上递归下降分析了!

(2)思路二:用HashMap处理多个Key,化简时有巨大优势

​ 自定义一个类,包含多个Key,将这个类作为HashMap的Key类型,并实现这个类的哈希函数和相等性判定函数。

1.哈希函数

int hashcode()

2.相等性判定函数

boolean equals(object obj)

下图是笔者自己设计的HashKey类中重写的核心方法

注:在第二次作业中,利用HashMap实现同类项的合并相比ArrayList与LinkedList有更大优势。

下图为我第二次作业的类图

​ StringMatcher类用于判断输入是否合法

​ HashKey类为自己设计构造的HashMap的Key类型

​ Expression类为表达式处理类

​ Item类为项处理类

​ Derivation类为求导类

III.包含常数、项、幂函数与嵌套三角幂函数多项式的求导

核心思想——递归下降分析法:表达式由项构成,项由因子构成,因子的种类可以是表达式,幂函数,常数项,三角函数类。

1.表达式:读取项,用LinkedList存储项:

LinkedList<Term> parseExpression()

2.项:读取因子,用LinkedList存储因子:

Term parseTerm()

3.因子

//外包函数,分析因子种类,然后分种类爬取因子
Factor parseFactor()
//获取常数项因子
Factor getConstantFactor()
//获取幂函数因子
Factor getXFactor()
//获取正弦函数因子
Factor getSinxFactor()
//获取余弦函数因子
Factor getCosxFactor()
//获取表达式因子
Factor getExpressionFactor()

为使逻辑清晰,可把因子种类定义为枚举类型。

public enum FactorType {
    constant,x,sin,cos,expression
}

下图为第三次作业我的类图

​ StringMatcher类用于判断输入格式是否为正确格式

​ Expression类为表达式处理类

​ Term类为项处理类

​ Factor类为因子处理类

​ FactorType枚举类型定义了因子类型

二.优化策略简介


笔者会在另一篇博文中对优化策略进行详解,在此仅说下整体思路。

题外话,笔者在三次作业的优化中均取得了还不错的成绩,第一次作业满分,第二次作业第11名,第三次作业前10名

1).第一次作业优化要点:

①合并同类项

②正项前移

2).第二次作业优化要点:

①合并同类项

②利用三角函数公式简化(主要采用了以下四类公式):

			1°  sin(x)^2 + cos(x)^2 = 1

​			2° 1-sin(x)^2 = cos(x)^2

​			3° 1-cos(x)^2 = sin(x)^2

​			4° sin(x)^4 - cos(x)^4 = sin(x)^2 - cos(x)^2

③正项前移

3).第三次作业优化要点:

​ 笔者认为,第三次作业的优化重点不在于利用三角函数公式简化,而在于在可控时间复杂度下利用递归下降实现同类项的合并,这便需要重写Expression类、Term类、Factor类的hashcode()和equals()函数

三.综合三次作业的方法复杂度的分析


第一次作业:

第二次作业:

第三次作业:

​ 可以看出,随着三次作业的层层递进,工程的方法数目与方法复杂度均在上升。由于二、三次作业的优化相对复杂,故我在相应的类中实现了较多复杂度较高的方法来进行相应的优化,具体方法逻辑可参考类图。

四.Bug分析


​ 三次作业我的强测全部通过,均没有出现bug,加上优化分,笔者在三次作业中的强测得分分别为100分、99.2839分、96.7647分。

​ 但在第三次作业中,我被别的玩家hack了一个点,系我在化简过程中一个if语句判断失误,去除了三角函数中表达式因子的括号,导致输出格式有问题。这也告诫我,在优化过程中,一定要考虑全面。

五.高效Debug和Hack策略分析


笔者认为,其实要想成为OO互测的顶级玩家,必须要拥有一台自己编写的评测机,千万不要偷懒。

(1)构建自己的“强测”评测机(全面覆盖):

① 自动生成随机表达式串

② 让本组成员的程序互拍,寻找不同结果。

③ 判断错误结果的来源、错误结果的种类(即Wrong Format Error、Wrong Value Error还是Wrong Derivative Error),并输出到指定文件

​ 下图是用Python实现的第三次作业的评测机。

(1)精心构造测试样例(重点针对):

​ 评测机生成的数据太随机,因此往往有些bug会疏漏掉,需要自己读懂评测屋内的代码,并分析其架构和逻辑上的失误,并构造相应样例。

​ 比如第二次作业的一个针对优化的典型hack样例:

x*sin(x)^4*cos(x)^-4+x*sin(x)^5*cos(x)^-5+x*sin(x)^2*cos(x)^-2+x*cos(x)^-3+x*sin(x)^6*cos(x)^-6

​ 第三次作业的一个典型hack样例

cos((((-+((((sin(x)))))))))

六.Applying Creational Pattern


​ 工厂模式与抽象工厂

​ 对于本次作业可以使用工厂模式来创建表达式,项,因子,我们只需定义一个创建对象的接口,让实现了该接口的子类自己决定实例化哪一个工厂类。在我们明确地计划不同条件下创建不同实例时,工厂模式将十分管用。

​ 抽象工厂模式是围绕一个超级工厂创建其他工厂。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象,这样我们就不用花费时间在选择接口上了。

七.总结与展望


​ 北航的OO课正在不断变好,感谢老师、助教和为其他为这门课默默付出的人。我会再接再厉,不断提升自己的分析能力和代码能力。

posted on 2019-03-26 18:50  一只开心咩  阅读(329)  评论(0编辑  收藏  举报