BUAA_OO_Unit1单元博客

齐欣宇22371342 学生 2024-03-22 19:03:10

目录

  • 度量分析
  • 度量
  • 计算OO度量
  • 类图
  • 设计考虑
  • 优点
  • 缺点
  • 架构设计
  • hw1
  • hw2
  • hw3
  • 架构拓展
  • Bug分析
  • hw1
  • hw2
  • hw3
  • 如何发现别人的BUG
  • hw1 hw2
  • hw3
  • 自己的优化
  • 心得体会
  • 未来方向

度量分析

度量

代码规模:

img

img

计算OO度量

如下

img

img

类图

img

设计考虑

Operate类是实现递归下降的类
而Handle则负责处理运行中遇到的各种问题
如项的分割,因子的分割等
函数的分析处理交给Function类实现
有关表达式的操作则放在Pack类里

优点

优点不多,我觉得将Factor分类处理,以及Pack子类继承可算两个
同时一些功能性方法被我封装在了Handle类中
以及函数有自己的分析类
结构还是比较清晰的
类之间也有独立性

缺点

对继承的理解还是不算深刻
子类并没有实现对父类的重写,并没有体现出子类的独特
同时对表达式和项的求导放在了Handle中,这导致了程序的耦合
应该在Pack及其子类中进行重写

架构设计

hw1

在第一次hw中,我对项的理解并不透彻
在架构中出现了并不算方便的处理
如在对单项式的处理中,我将常量和变量分开存储,即:

    private HashMap<Integer, BigInteger> para;
    private BigInteger myConst;

这其实忽略了常数就是x的指数为0的情况,导致写了很多不必要的冗余功能,包括getConst等
同时在实现项的加法,因子的乘法时要同时考虑const和para,显得颇为复杂
这一点在hw2中有所改进

其次,递归下降虽然在我的架构中有所体现,但个人感觉并不够
虽然我构建了Expr Term Factor三个类

Expr = Term + Term + ...
Term = Factor * Factor * ...

但是每一个类并无太大区别,都分别存储了常量和变量
这样更像是对相同的类进行了不同的取名

而且相比于其他人生成语法树的解析过程
我单纯地只是在Operate类中进行了

public Expr opeExpr(String input)
{
    //...
}
public Term opeTerm(String input)
{
    //...
}
public Factor opeFactor(String input)
{
    //...
}

将表达式分层解析的操作
这样做导致一些功能集成在同一类中
并不够独立而在之后出现很多问题

hw2

  1. 在第二次作业中,我们新增了指数函数的表达形式
    即: exp(表达式)
    这一点即完全推翻了我在hw1中对多项式的存储方式
    首先,myConst不再适用,因为常量包括exp(常数)
    不能再单纯地使用BigInteger存储
    其次,变量的存储形式para也不再适用
    因为exp的指数中也可能出现变量
    因此在这里对单项式做了一个统一的形式:
    c1*exp(index)*x^c2
    故使用Hashmap:

        private HashMap<BigInteger, HashMap<String, BigInteger>> para;
    

    其中用x的指数c2作为key,exp的指数index作为value中哈希表的key
    由于exp指数需要转换为String类型,这里还需要写出多项式转String的方法

    public String packToString(Pack pack)
    {
        // TODO
        }
    
  2. 同时我们新增自定义函数
    最开始的想法是直接进行字符串层次的替换
    即: 在读入input后,就将所有的函数进行实参的代入替换
    但这样并不算方便,且与递归下降的架构并不契合
    因此选择先单纯将函数作为因子
    只在对因子进行处理时,对函数进行代入替换,即:

    public Factor opeFactor(String input)
    {
         ...
         else if(handle.isFunction(input))
         {
             String str = handle.funcReplace(input);
             expr = this.opeExpr(str);
             ...
         }
         ...
    }
    

    这样就做到了将对函数的处理融合在递归下降的架构中

  3. 在hw1中我们将功能全部集成在了Operate类中
    这并不够独立,因此在这一次作业中我进行了一次分割
    新建Handle类,将所有功能性方法全部集成在此类中
    而Operate类则变成单纯地对多项式地进行解析的类

hw3

本次作业新增dx求导算子
同样只需要在因子层次进行特判:

public Factor opeFactor(String input)
{
    ...
    else if(handle.isDerive(input))
    {
        ...
    }
    ...
}

之后进行一次求导计算即可
而对于新增的函数可互相调用的功能
在上一次作业的递归下降就已经解决,这里不做处理亦可

架构拓展

根据现有的计算需求,可能出现的拓展有:

  1. 特殊函数,如三角函数的计算求导等
    三角函数可以看作新的特殊因子,可以类似一个exp指数函数
    那么就需要在para中新增三角函数的String内容
    因此可能需要对单项式的存储方法进行重构
    构建新的Unit类专门存储单项式的各项信息

  2. 出现新定义运算
    由于递归下降的优先级问题,新定义运算最好是优先级高于乘法
    由此可以在Factor中再进行一次下降
    根据新定义运算,拆分成

    Factor = Basic # Basic # ...

    之后再进行处理即可

Bug分析

hw1

在第一单元我出现了两个致命bug
这导致我的强测扣了50分
T T
1.在最后对多项式的输出中,我为了实现输出的优化,在对x的系数输出中进行了一个判断

int flag = 0;
if (flag == 0)
{
    if (para.get(point).equals(BigInteger.ONE))
    {
        System.out.print("x");
    }
    flag = 1;
}
else
{
    if (para.get(point).equals(BigInteger.ONE))
    {
        System.out.print("+x");
    }
}

即:如果是第一次输出,则在是正系数的情况下省略第一个正号,否则输出正号
同时若系数是1,则省略系数
但是我忽略了部分非1系数下正号的输出
2.

public void subPara(Integer point, BigInteger bigInteger)
    {
        BigInteger big = bigInteger;
        if (para.containsKey(point))
        {
            big = big.subtract(para.get(point));
        }
        else
        {
            big = big.negate();
        }
        para.put(point, big);
    }

这是我对项的减法操作,看似无问题
但注意其中的

    big = big.subtract(para.get(point));

减数-被减数
由于偷懒导致的顺序错误,直接就导致了大量WA
不得不说十分愚蠢,甚至没进互测

hw2

这一次迭代也有一个十分微小的Bug
在中测时并不显露,但强测就把我斩于马下
1. 系数为0
一个很简单的数据可以形容这个问题

0
1-1

输出 0 对吧,但是事实上我的输出是
""
没错就是空串。由于在自己编纂数据时出现了大量系数为0的输出
私以为这些都是无效输出,于是在输出前进行了一步clean
大胆地清除了所有系数为0的项
很不巧,这就包含了常数0

2. Format Error
再仔细一看,怎么还有形式错误?
原来是对输出要求没有看清
由于太想优化,导致在输出时对同类项进行了合并
出现了非必要括号
改为老老实实输出后解决

3. 经典CPU_TIME_LIMIT

0
(((((((((((x^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8

一个数据爆了两个问题

  1. 虽然指数要求不超过8,但是可以叠乘方,导致最开始的Integer爆掉
  2. 过多的叠乘在算法不够优化的情况下计算太复杂

最开始对于乘方的简单解决办法是直接展开
即:

input : (x+1)^2 -> (x+1)*(x+1)

展开后再作为Term进行处理
但很显然这样的时间复杂度很高
因为需要将所有内容全部展开计算
故做一个简单的处理后调换一下计算顺序即可

input : (x+1)^2
->
str : x+1
index : 2

其中,先对str进行因子计算处理
计算完成后再进行index指数乘方
这样时间复杂度就很低了

hw3

可喜可贺,这一次终于进了互测
(再不进互测就要重修了QAQ)
强测的点很奇怪
Zero length BigInteger
用Print大法一看就发现了问题
一个简单的例子

1
f(x)=x^2
f(dx(x))

简单替换后是

dx(x)^2

但是在对求导因子的处理中并没有考虑到可能的指数符号
导致报错,修改后解决

如何发现别人的BUG

hw1 hw2

什么?原来有人互测都没进?
哭辽T A T

hw3

第一次进互测十分的激动(虽然只是C组
首先当然是先将自己曾经的BUG提交进去
然后发现同房的佬们完全不吃这套
(只有我一个人受伤的世界达成了
之后开始绞尽脑汁编数据,但总是过不了合法性检验,悲
故这提出几点可能的BUG方向:

  1. CPU_TIME_LIMIT
    对同房程序进行深度的疯狂嵌套,以期得到CPU_TIME_LIMIT的错误

  2. NULL_POINT
    创造各种空式,如0*x^0,或通过计算得到空式,看是否有对此的特判处理

  3. Not Valid Factor

    在因子的判断中,可能出现不属于任一因子的情况,这主要源于函数的替换
    可以设计复杂的实参,看是否会让因子解析的程序报错

自己的优化

当然简单的优化还是要做的

  1. 系数为1的特判
    1*x OR -1\*x  ->  x OR -x 
    
  2. 首个正号的省略
    flag = 0
    while(true)
    {
     if (flag == 0)
     {
         flag = 1;
         ...
     }
     else
     {
         ...
     }
    }
    
  3. 系数为0项的清除
    public void clean()
    {
     ...
     for ()
     {
         ...
         for ()
         {
             ...
             if (bigInteger.equals(BigInteger.ZERO))
             {
                 iterator.remove();
             }
             ...
         }
         ...
     }
     ...
    }
    
    此外还有exp指数中公因式的提取
    由于笨人正常写完已是心力憔悴,故三次作业中均未考虑(

心得体会

在这一整个单元的学习过程中,最让我印象深刻的就是对形式化表达的程序实现
在hw1的作业中,最开始我死板地完全依照形式化表达去处理输入的表达式
这并没有让我获得更完善的程序,而是隐藏BUG不断
在吸取了别的同学的经验后,才采取了化简输入的方法:
对连续符号进行合并,以及全部去除空白符
之后再处理就方便很多
也就是说,要对问题核心进行深度分析后,从问题导向思考解决方法
而不仅仅是题目怎么说我就怎么写

其次,程序的独立性也是需要考虑的一部分
不要将大部分功能集成在某一类中
要有对象的观念
可以归类的方法需要分门别类地实现,才能实现代码清晰,逻辑清楚
同时可扩展性和独立性也会更高
不至于牵一发而动全身

未来方向

或许可以考虑均衡一下历次作业的难度
Unit1的难度大致为
hw2 > hw1 >> hw3
这其实对很多Java不太熟悉的同学来说不算友好
或许可以考虑更加循序渐进一些

...全文
63 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧