第一单元总结

第一单元总结

系统架构设计

​ 在三次作业中,设计的核心就在于如何存储和如何计算数据

存储:

​ 在存储这一个角度,我使用了层次化的方式去处理,虽然有三次作业,但其实可以用统一的格式表示,利用一个一般通式对每一个项进行识别与处理。一般通式(以三角为主元,第一次则视为三角为1)采用的方案为

​ 利用ArrayList可以将轻松地把这样的通式展开形成一个层次化的结构,同时为了更好的归一化,不妨让sin和cos同时继承于tri类,那么利用多态的方式就可以更轻松地组织数据。

计算:

  • 如何获取结构

    ​ 如果把每一项都进行如上式的匹配无疑是相当复杂的,所以为什么不让它自己去生成那样的结构,来节省代码的支出呢?这里就引出了计算方式,不同与递归下降依靠直接分析的方案,本方法以计算为引,通过计算来连接不同的基础对象,可以发现一个基础对象只是幂函数、常数和表达式。那么只需要对基本对象(三角和自变量,不去识别幂次,幂次当作运算来处理)和常数的方式提供一个顶层的构造函数,那么之后顶层会在计算时不断丰富自己的结构,达到简化的效果。

    ​ 这里还没有说明如和识别基础对象和如何识别表达式因子,但不妨把表达式因子先放一放。首先去考虑如何识别基础对象(幂函数和常数),由于形式多样,需要每一种形式写一个专门的识别模式去获取基本对象。当能够识别幂函数和常数后,表达式因子其实只是递归调用一次识别函数来返回一个顶层对象。

  • 如何进行四则运算

    ​ 上文提到,通过运算来连接对象,让基本对象能够在计算中丰富自己。那自然地,顶层对象要有能够自己计算的能力,于是在顶层写入四则运算方法,四则运算的具体实现这里就不再展开。只说明前置-如何处理以及乘法的结果是如何合并的。前置-可以通过在前加一个0来处理。乘法合并只需要三角部分是完全一致,就可以进行合并,所以关键是判断三角部分一致(见优化部分),而多项式的合并是比较轻松的。

  • 如何处理自定义函数和sum函数

    ​ 处理自定义函数和sum函数的核心只在一个地方,是替换还是解析,逻辑区别在于是识别(添加处理模式)还是不识别(转化为已经实现的问题)。考虑到sum函数其实不是基础的四则运算可以囊括的,它最好拥有自己处理自己的能力,否则处理就显得复杂,而自定义函数虽然形式多变,但完全在四则运算的体系里,于是可以去替换来转化问题。

  • 如何识别运算的优先级

    ​ 以运算为引必为运算所困,这里的难点就在于如何处理优先级(特别是前置-)。那既然是四则运算,不妨使用逆波兰表达式来处理这个问题,而处理前置-依靠于先压栈一个0,再压栈一个-。逆波兰表达式的应用是整个架构的精华(缺陷)所在,一方面逆波兰表达式的算法结构可以让数据自己去组织自己,而不是人为的识别(复杂的正则匹配或者抽象的文法分析),但另一方面逆波兰表达式的算法禁锢了程序的拓展性,这里并不是说本方法的扩展性较差,而是它的拓展必须依赖于顶层的设计。

  • 如何优化

    ​ 在上文中,已经搭建了相对完善和相对简单的答案的框架。为了更好的输出结果,进行一些优化。对小细节的优化在这里就不再说明。接下来说明如何处理诱导公式、三角相等判断、平方和优化,这样的优化都是对顶层进行的。

    1. 诱导公式

      ​ 要让输出最短,必然是要能合并的部分完全合并,这里就会碰到三角诱导公式怎么用的问题,比如\(sin(x-5)\)\(sin(5-x)\)如何把它们看作是一个东西,由于实际上这也会影响输出的长度,不如设定为如何进行公式变换。那么原则就是对最高幂次发起冲击,直接查看三角最高幂次的多项式最高幂次的系数,为负则进行变换(这其实是比较巧妙的方案,它同时也处理了全负的情况)。需要注意的是sin的优化又会导致整体正负性的变化,需要特殊处理(也即当作前置-)。

    2. 三角相等判断

      ​ 不难发现,在经过上一步后,式子里的三角一定是存储方式和数学式等价,那么只需要判断三角的形式、幂次和内部的expr完全相等。当然内部的expr相等可能还会出现三角,所以有的写法会形成递归,这里就采取一种递归的判断方式,比较expr的每一项来判断相等(具体实现为验证互相包含)。这样的比较采用层次化的比较,比较上层相等时去调用底层的比较方法。那么递归的底层就在多项式的比较和三角的比较,而多项式的比较是容易的,三角的比较则是先比幂次,再比内部的expr(进入递归,直到内部为多项式)。

    3. 平方和优化

      ​ 平方和的优化采用提取公因式的方式,通过提取两项的公因式后来处理剩下的平方和,当然公因式的提取必须要使用三角相等的判断。

      最后就是什么时候进行优化,在逆波兰运算后压栈的时候优化要压栈的expr对象即可。

    整体架构UML图

    ​ 下图的逻辑是非常清楚的,竖直方向上是存储的设计,水平方向上是计算的设计,其中Input类来进行计算和解析,Optimization类来负责优化,Function类来提供自定义函数计算方式。

Bug分析与代码优劣分析

Bug分析:

​ 三次作业中,共有三个bug:1.前置-未处理常数。 2.运算超时。 3.sum中对空格的处理不当

​ 其中1,3为代码细节处理不当,2为优化算法的效率太低,进一步的优化思路是将逐项判断的互相包含法修改为哈希映射法。

代码优劣分析:

  • 优势:
    • 存储方式具有天然最简性
    • 程序的鲁棒性极强,可处理按照数学逻辑输入的表达式,而不拘泥于形式化表示
    • 整体架构具有很好的层次性
  • 劣势与不足:
    • 利用代码分析工具可以得到Input类的难以理解度和复杂度较高,因为其同时兼顾了计算和解析,应当再拆分一个类去解析
    • 代码的扩展性依赖于顶层,算法的限制使得很难在解析时进行优化来减少运算
    • 新增变量和新增非四则可表示函数会让顶层难以设计,就需要更抽象的表示,对于合并不利

结语

​ 最后,简单谈一谈什么是面向对象和如何理解面向对象,可以肯定的是面向对象不是JAVA(继承、多态、接口),面向对象或许是一种思维模式,一种要求代码进行合理封装和精准归一化的思维模式,从而可以实现重用性、灵活性和扩展性的效果。例如在本次作业中表达式可以归一到一个类,所有的形式归一到四则运算,具体的运算又全部被封装。

​ 整体来讲,面向对象的思维模式指导程序关注于其最小的单元,通过对需求的合理拆分,用最小的代价获取最大的价值。复杂的系统在不断的迭代中总会变为“屎山”(个人难以理解,几乎不可重构)代码,而面向对象或许不能阻止整个过程,但它一定能延缓这一过程。

posted @ 2022-03-25 11:59  20373kai  阅读(48)  评论(1编辑  收藏  举报