OO_JAVA_表达式求导_第一弹

---------------------------------------------------表达式提取部分

词法分析

​ 首先,每一个表达式内部都存在不可分割的字符组,比如一个不止一位的数字,或是一个sin三角函数,这样不能分离的字符组我称之为词法单元,依照其定义,可以将第三次作业的表达式分割成如下词法单元:

SPACE:即空格和TAB字符的组合

纯数字:即纯粹由0-9字符集组成

运算符:-、+、*、^,这些都是运算符

三角函数:sin或cos内部不可存在空格

左括号:(

右括号:)

​ 将一串字符解析成词法单元列表的形式,就已经是一层处理了,这一步就可以排查出一些错误,直接抛出异常,然后我们得到了一个由词法单元组成的列表,现在就是采用递归递降的方法线性解析这个列表并生成表达树了。

语法分析

仔细阅读指导书,可以归纳出如下定义:

Num = [-|+]纯数字

Factor = Num | x [^ Num] | sin ( Factor) [^ Num] | cos ( Factor) [^ Num] | (Expr)

Item = [-|+]Factor (*Factor)*

Expr = [-|+]Item ([-|+]Item)*

** 其中[]表示其中的字符存在或不存在皆可;()*表示可选重复0至任意次;| 表示或 **

除Num单元内部,其它语法单元内部可以任意插入SPACE

写作分析函数的原则有两条:

采用分层次的设计思路,从归纳的语法定义就可以看出,需要写一个处理Expr的函数,一个处理Item的函数……需要子结构时调用相应函数即可。

只处理本层次需要处理的字符,遇到非法字符,返回上层调用或者抛出异常;

当然,有人会说,这样只是写一堆递归函数(o(╯□╰)o,我就是这么写的),没有实行面对对象,其实,递归递降也是可以面对对象的。

模式匹配

首先需要一个Parser接口,实现如下(不一定)函数(为清晰起见,写的很简单)

public interface Parser {

public Atom toAtom();

public Integer getIndex();

public void addParser(Parser parser);

}

分别是一个转换函数,一个获取新遍历下标的函数,一个添加子Parser的函数。

根据不同层级Parser的需要,完成相应函数的重写(Override)即可,举个栗子:

public class ExprParser implements Parser {

ArrayList itemList;

// Integer endIndex; //

private ExprParser() {

itemList = new ArrayList<>();

}

@Override

public Atom toAtom() {

Atom root = new Atom(Atom.Type.PLUS, BigInteger.ZERO);

for (Parser parser:itemList) {

root.addChild(parser.toAtom());

}

return root;

}

@Override

public Integer getIndex() {

return endIndex;

}

public static Parser newParser(String string) {

Parser parser = new ExprParser();

for (int i = 0; i < string.length();) {

if (string.charAt(i) == '-' || string.charAt(i) == '+' ||

string.charAt(i) != ' ' && string.charAt(i) != '\t') {

Parser itemParser = ItemParser.newParser(string.substring(i));

parser.addParser(itemParser);

i = itemParser.getIndex();

} else {

i += 1;

}

}

return parser;

}

@Override

public void addParser(Parser parser) {

itemList.add(parser);

}

}

依旧格式简易,但是只是为了说明怎么重写这样的函数,Atom即最后生成的表达树的节点类或者接口,随个人意。

另外没有显式抛出异常,真正写时需要在返回parser时检查item List是否为空,若为空,则显式抛出异常,进行异常处理。

现在说明一下,这个newParser工厂函数为什么这么写,为了清楚起见,我把Expr的定义重新拉过来:

Expr = [-|+]Item ([-|+]Item)*

这个语法定义告诉我们Expr线性扫描,检查是否有减号或是加号,然后需要获取一个Item,这时调用ItemParser的工厂函数即可,格式异常由不同层级的Parser实现类的工厂函数负责抛出,只要语法分析合理,这样写作是简单无误的。

以此类推,相信读懂了上述抽象的思维方式,写出Item和Factor的Parser对你易如反掌!

OO_JAVA_表达式求导_第二弹

------------------------------------------------表达树的构建

总体来说,按照设计模式上可以分为两类:本次实验还是建议第二种方式(虽然我用了第一种,,::>_<::>

extends 继承,采取一个父类实现所需方法子类重写方法,但是为了统一填充到ArrayList或者HashMap中,必须采取子类cast回父类的方式,这样子类不能有自己的属性,否则无法cast;

implements 实现,采取定义共通的接口,所有节点类重写接口中的方法,如求导方法、化简方法、toString方法。

在第三次作业中,我采用了继承的方案实现树形结构节点的构造,Atom父类实现了非常多的函数,现在的视角来看,是很不合理的,因为Atom负担了所有子类的函数,臃肿累赘,层次是分明了,只有父类Atom和子类各种Atom,但是在工程管理和逻辑架构设计上,还是有所问题的。

下面是我的Atom类图关系,其它类都继承自Atom很复杂把。

OO_JAVA_表达式求导_第三弹

-----------------------------如何将函数式思维带入化简函数

要实现化简合并,最好先完成两个准备工作(一个可选)

expand:展开(可选),有时会遇见123*(x+123)这样的情况,*与+没有按照优先级组合,而是需要根据乘法分配率展开这样的项;

flat Map:平铺,在处理字符串生成表达树以及求导表达树返回新表达树的过程中,会产生很多如1*(1*(1*x))连乘项或连加项嵌套的结果,平铺可以将之展开成为1*1*1*x的形式,利于下一步的分类和合并。

首先从宏观的角度抽象地理解化简,是怎么一回事,一共两步

classification:分类,将能化简的规整在一起,不能化简的单独存放,最终结果是一个List>,每一个List存放的是可以合并的同类项;

merge:合并,对不同的可合并List进行合并处理,再新建一个父对象,将合并结果依次填回父对象的子节点中,返回父对象。

现在简单说明一下Java8引入的Stream API和lambda表达式(实际上是Functional Interface)

举个栗子,现在我们有一串数字在列表origin中,我们要对他们都平个方,生成一个新列表,正常做法如下:

List after = new ArrayList<>();

for (Integer integer: origin) {

after.add(integer * integer);

}

但是有了Stream API和lambda表达式,就可以这么写:

List after = origin.stream().map(e -> e * e).collect(toList());

Stream流的主要功能有filter(筛选),map(映射),flatMap(平铺映射),collect(收集返回列表或Map或其它)。

需要填充函数接口的参数的来源有两种:

方法引用,比如Integer::add,就是对Integer的加法方法的一个引用,内秉其实是函数接口

lambda表达式,基本形式为(参数列表)->返回值 或 (参数列表)->{语句; return 返回值;}组成。

现在问题来了,为什么要这么写呢?什么时候要写成哪一种形式呢?

这两种写法的主要区别在于逻辑的分离性与可控性:前者循环方式,循环流程完全由你掌控,可操作性极大,同时脑力负担也大一些;后者将循环体隐藏在Stream API的内部,你不需要在控制循环的方方面面,可以一层层地分离逻辑,减轻脑力负担,高效清晰地编写代码。

在循环体内部流程非常简单时,比如上述栗子,你选择哪一种都没有什么问题,无非是代码行数和内部执行效率的区别,但是,在具化到我要讲的classification过程时,也就是可以尽量分离循环体内部操作时,选择Stream API可以清晰地简单地生成新列表,下面就对此说明一下吧。

Classification可以分为四步:

实现一个函数(满足Predicate接口定义),也就是一个泛化意义上的相等函数,用来判断两项能否合并;

根据先前实现的接口,采用Stream的filter过滤器API,对每个元素,过滤出与之相等(可合并)的元素组成列表;

依照先前产生的列表,统统插入到Map中,为保证key的唯一,采用每一个列表的toString方法结果作为key;

将上述生成的Map的values取出,返回一个列表的列表,实现classification的功能。

上述过程就是一个逻辑不断分离的过程,对应到相加项和相乘项,唯一不同的就是Predicate接口,也是这么思维的意义所在。

这里把后三步的合并加法与合并乘法共通的代码贴出来:

public List filter(Atom atom, Predicate predicate) {

return this.children.stream()

.filter(predicate)

.collect(Collectors.toList());

}

public ArrayList> classify(Predicate predicate) {

Map> map = new HashMap<>();

for (Atom atom : children) {

ArrayList list = (ArrayList) this.filter(atom, predicate);

map.put(list.toString(), list);

}

return new ArrayList<>(map.values());

}

与上述思维类似,Merge也可分步进行:

先merge所有子节点重新填充到新root节点中;

使用classification,获取新root节点分类后的子节点列表的列表;

创建新root节点,迭代前一步获取的列表,使用合并同类项函数(同类项列表=>合并后的项),将节点依次插入新的root节点中,返回。

继续拆分,上述第三步采用Stream的思路还可以拆分:

产生Stream>流;

map映射List到Atom,这里交由另一个函数接口负责;

filter筛选需要添加至最终列表的项,这里依然可以分离出去交由另一个函数接口负责;

最后collect函数收集流中的元素,返回List,若为空,返回特定值。

这里举个例子就把合并相加项的filter调用的函数接口写一下吧:

Predicate addPredicate = e -> !(

e.getType().equals(Atom.Type.CONSTANT) && e.getValue().equals(BigInteger.ZERO)

);

上述函数接口表明,如果元素e是一个常数0了话,返回false否则返回true。

综上所述,如果你理解了分离逻辑的操作后,写出清晰简洁的代码应该是易如反掌把!

java求导数_OO_JAVA_表达式求导相关推荐

  1. Matlab:Matlab编程语言应用之数学计算(求极限/渐近线求导数常微分方程求解求微分方程组的解求临界阻尼系数的解)的简介、案例实现之详细攻略

    Matlab:Matlab编程语言应用之数学计算(求极限/渐近线&求导数&常微分方程求解&求微分方程组的解&求临界阻尼系数的解)的简介.案例实现之详细攻略 目录 三.极 ...

  2. python编程求导数_SciPy函数求导数

    17. SciPy求函数的导数 在SciPy里提供了很多的方法函数可以实现对某函数进行求导和求积分的操作. SciPy的求导相对简单也容易理解.已知函数$f(x)$求其在$x_0$的导数即$f'(x_ ...

  3. c语言作业算术表达式求值,算术表达式求值演示(C语言版)

    //头文件预处理命令 #include #include //----------函数结果状态代码----------------- #define TRUE 1 #define FALSE 0 #d ...

  4. AVIATOR——轻量级JAVA表达式求值引擎

    简介 Aviator是一个高性能.轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值.现在已经有很多开源可用的java表达式求值引擎,为什么还需要Avaitor呢? Aviator ...

  5. java aviator_Aviator 表达式求值引擎开源框架

    简介¶ Aviator是一个高性能.轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值.现在已经有很多开源可用的java表达式求值引擎,为什么还需要Avaitor呢? Aviato ...

  6. 表达式求值引擎Avitor的使用

    1.简介 Aviator是一个高性能.轻量级的java语言实现的表达式求值引擎,主要用于各种表达式的动态求值. 2.Maven依赖 <dependency><groupId>c ...

  7. [数值计算-9]:一元非线性函数求导数(数值微分)- 解析法与迭代法Python法代码示例

    作者主页(文火冰糖的硅基工坊):https://blog.csdn.net/HiWangWenBing 本文网址:https://blog.csdn.net/HiWangWenBing/article ...

  8. 【sympy】用python的库 sympy 求导数

    diff(f,x)diff(f, x)diff(f,x)求导数可引入求微分方程 sympy 求微分方程.(点击可跳转) 1.一阶导数 基本格式 print(diff(f, x)) # f为所求导函数, ...

  9. 信息学奥赛一本通 1962:【13NOIP普及组】表达式求值 | 洛谷 P1981 [NOIP2013 普及组] 表达式求值

    [题目链接] ybt 1962:[13NOIP普及组]表达式求值 洛谷 P1981 [NOIP2013 普及组] 表达式求值 [题目考点] 栈 中缀表达式转后缀表达式,后缀表达式求值 中缀表达式求值 ...

最新文章

  1. 八年级计算机网络公开课,计算机网络公开课教案.doc
  2. iOS 提示更新 业务逻辑
  3. 关于ES、PES、PS以及TS码流
  4. alfs学习笔记-自动化构建lfs系统
  5. Android 组件系列-----Activity的传值和回传值
  6. ip登录打印机怎么打印_不要打印,登录。
  7. C#的变迁史08 - C# 5.0 之并行编程总结篇
  8. matlab lyap,Matlab的Lyapunov、Sylvester和Riccati方程的Matlab求解
  9. JSP笔记——7.自定义标签
  10. 数据库冷备份和热备份
  11. pointnet2(pointnet++)源码复现
  12. 3.3 自定义控件基础 之 View的绘制
  13. 老程序员应该记住的 5 件事
  14. 25 HttpClient下载图片
  15. 华为HarmonyOS手机系统如何下载手机淘宝APP领取淘宝内部隐藏优惠券?
  16. html边框缩短,有什么办法css border缩短
  17. 塑料废物管理行业调研报告 - 市场现状分析与发展前景预测
  18. 复现Reducing Complexity of HEVC: A Deep Learning Approach,复现帧内模式,HCPM
  19. Linux下固态硬盘坏块修复,固态硬盘如果发现坏块就完蛋了
  20. IE8:SCRIPT438: 对象不支持“play”属性或方法,audio.play()无法使用

热门文章

  1. 用DL深度学习神经网络绘图---对于程序来说0和1到底是什么样的
  2. linux服务器垃圾箱,如何将Linux rm命令删除的文件放入垃圾箱
  3. 一阶系统单位阶跃响应的特点_第八讲 系统的时域响应
  4. 【Matlab 图像】开闭运算 imopen imclose
  5. STM32 基础系列教程 45 - FSMC_LCD_Touch
  6. 【arduino】DIY音乐播放器,arduino音箱播放wav音乐
  7. 近端策略优化深度强化学习算法
  8. showSoftInput不起作用
  9. LVS实现web服务的负载均衡
  10. mybatis中${}和#{}的区别