java 写代码求导_OO_JAVA_表达式求导
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接口,实现如下(不一定)函数(为清晰起见,写的很简单)
package util;
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,但是在工程管理和逻辑架构设计上,还是有所问题的。
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或其它)。
现在问题来了,为什么要这么写呢?什么时候要写成哪一种形式呢?
这两种写法的主要区别在于逻辑的分离性与可控性:前者循环方式,循环流程完全由你掌控,可操作性极大,同时脑力负担也大一些;后者将循环体隐藏在Stream API的内部,你不需要在控制循环的方方面面,可以一层层地分离逻辑,减轻脑力负担,高效清晰地编写代码。
在循环体内部流程非常简单时,比如上述栗子,你选择哪一种都没有什么问题,无非是代码行数和内部执行效率的区别,但是,在具化到我要讲的classification过程时,也就是可以尽量分离循环体内部操作时,选择Stream API可以清晰地简单地生成新列表,下面就对此说明一下吧。
Classification可以分为四步:
实现一个函数(满足Predicate接口定义),也就是一个泛化意义上的相等函数,用来判断两项能否合并;
根据先前实现的接口,采用Stream的filter过滤器API,对每个元素,过滤出与之相等(可合并)的元素组成列表;
依照先前产生的列表,统统插入到Map中,为保证key的唯一,采用每一个列表的toString方法结果作为key;
将上述生成的Map的values取出,返回一个列表的列表,实现classification的功能。
上述过程就是一个逻辑不断分离的过程,对应到相加项和相乘项,唯一不同的就是Predict接口,也是这么思维的意义所在。
这里把后三步的合并加法与合并乘法共通的代码贴出来:
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_表达式求导相关推荐
- java求导数_OO_JAVA_表达式求导
OO_JAVA_表达式求导_第一弹 ---------------------------------------------------表达式提取部分 词法分析 首先,每一个表达式内部都存在不可 ...
- 【Java版oj】逆波兰表达式求值
目录 一.原题再现 二.问题分析 三.完整代码 一.原题再现 150. 逆波兰表达式求值 有效的算符包括 +.-.*./ .每个运算对象可以是整数,也可以是另一个逆波兰表达式. 注意 两个整数之间的除 ...
- 【Java】LeetCode 150. 逆波兰表达式求值 (后缀表达式)
题目: 根据 逆波兰表示法,求表达式的值. 有效的算符包括 +.-.*./ .每个运算对象可以是整数,也可以是另一个逆波兰表达式. 说明: 整数除法只保留整数部分. 给定逆波兰表达式总是有效的.换句话 ...
- iOS仿写计算器中缀转后缀 表达式求值
github地址 iOS计算器 准备工作 由于OC没有自带的栈文件,只能链表模拟栈 typedef struct {char data[50];int top; }Stack;//符号栈 //之前把D ...
- 【Java】基于栈的算术表达式求值
定义异常类 public class ExpressionException extends RuntimeException {private static final long serialVer ...
- c语言作业算术表达式求值,算术表达式求值演示(C语言版)
//头文件预处理命令 #include #include //----------函数结果状态代码----------------- #define TRUE 1 #define FALSE 0 #d ...
- java写100以内的素数_求出100以内的素数(java实现)
j package test1; //2018/11/30 //求100以内的所有素数 public class Main10 { public static void main(String[] a ...
- 学数据库你竟然不用用JAVA写代码,可惜你遇到了我! JAVA连接数据库(JDBC)的安装使用教程
Step 1 你得有Eclipse 没有出门右拐,我教不了你. Step 2 你得有Mysql MySQL的详细安装过程,我在另一篇博客中给出.戳我 Step 3 安装JDBC 可以去官网下,如果用的 ...
- 生日在java中怎么写_如何用java写代码:输入自己的生日,距离今天已过多少天,或者还有...
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 从新发过 import java.util.*; import java.io.*; public class birthday{ public stat ...
- JAVA写代码学费,java编程培训班学费是多少
JavaWeb 01HTML5入门 -HTML的作用 -全局标签 -绝对路径与相对路径 -常见标签 -字符集 -图片整合 -表单标签 -布局指定的网页 02CSS3入门 -css声明类型 -常见的各种 ...
最新文章
- 使用cmake组建跨平台项目
- 计算机视觉(CV)的算法有哪些,具体都有哪些特点?
- 批处理编程的异类——时钟(Clock)
- 关于java_关于Java基础
- ios 自定义View 卡片滑动切换效果
- sap未分摊差异怎么处理_物料主数据价格控制及差异问题
- spark学习-51-Spark的task任务的运行
- 救人无数七个心理寓言
- 3)Thymeleaf th:* 设置/修改属性值详解
- 原生js写简单轮播图方式1-从左向右滑动
- python基础:列表(list)
- 免上传音乐外链(QQ音乐)
- Python —— excel 创建 复制 删除,获取最大行列
- 【路径规划】基于粒子群算法机器人避障路径规划matlab代码
- leetcode (Arranging Coins)
- docker(十)—— Windows系统下安装docker
- 【Matlab】Simulink系统仿真(了解)
- Git Commit emoji Guide
- 操作系统实验| Linux系统调用的实现
- 透明 Toolbar 透明任务栏实现
热门文章
- vue+i18n 切换语言后无法刷新数据 的 解决方案
- java getbytes_JAVA的getBytes()方法
- 微信小程序-wxs实现时间格式化过滤器(正则)
- java求圆柱体体积面积(接口继承、字符串常用方法)
- 如何才能方便快捷的将qlv格式转换成mp4格式呢
- 深度相机原理揭秘--双目立体视觉
- Google Chrome浏览器翻译失败
- iphone禁止自动连接wifi操作方法「苹果教程」
- 基于RNN生成古诗词
- 我有几张阿里云幸运券分享给你,用券购买或者升级阿里云相应产品会有特惠惊喜哦!把想要买的产品的幸运券都领走吧!快下手,马上就要抢光了。