Inlet类,入口类,这个类的主要用途是验证用户输入的算术表达式:
package com.hy;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
// 此类用于把算术表达式送入解析器
public class Inlet {
public static void main(String[] args) throws IOException{
// 取得用户输入的表达式
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String rawExpression = null;
System.out.print("请输入算术表达式:");
rawExpression = br.readLine();
// 得到合法的算术表达式
String expression="";
for(int i=0;i<rawExpression.length();i++){
// 拿到表达式的每个字符
char c=rawExpression.charAt(i);
//System.out.print(c+",");
if(Character.isDigit(c) || c=='+' || c=='-' || c=='*' || c=='/' || c=='(' || c==')' || c=='.'){
//System.out.print(c);
expression+=c;
}else{
System.out.print(" "+c+"不是合法的算术表达式字符.");
System.exit(0);
}
}
// 送去解析
Lexer p=new Lexer(expression);
//p.print();
// 转为后序表达式
Trans t=new Trans(p.getList());
//t.print();
// 计算结果
Calculator c=new Calculator(t.getPostfixList());
System.out.print(expression+"="+c.getResult());
}
}
Lexer类,主要起一个词法分析器的作用,注意这里采用正则表达式简化了代码,比https://www.cnblogs.com/xiandedanteng/p/11445994.html 中Parser类的处理方式简洁些:
package com.hy;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// 此类用于将算术表达式解析成包含操作数和操作符的链表,扮演分词器的角色
public class Lexer {
private List<String> list;// 用于存储表达式的链表
public List<String> getList() {
return list;
}
public Lexer(String expression){
list=new ArrayList<String>();
// 使用正则表达式后,代码简洁多了
String regExp = "(\\d+(\\.*)\\d*)|(\\+)|(\\-)|(\\*)|(\\/)|(\\()|(\\))";
Pattern pattern=Pattern.compile(regExp);
Matcher matcher=pattern.matcher(expression);
while(matcher.find()){
list.add(matcher.group(0));
}
}
public void print(){
for(String str:list){
System.out.println(str);
}
}
}
Trans类 将中序表达式转后序表达式的转换类,他接收来自Parser的包含操作符和操作数的列表,然后根据规则将算术表达式转化成后序表达式,利用的数据结构是栈java.util.Statck,转化的规则如下:
见到操作数->直接送到postfixList中
见到操作符->将栈顶输出,直到栈顶优先级小于该操作符,最后把该操作符压入栈
见到左括号 ->入栈
见到右括号 ->将栈中在左括号之后的操作符全部输出
(以上'栈'在代码中指的是Trans类的成员变量Stack):
package com.hy;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
// 此类用于将中序表达式转译成后序表达式
public class Trans {
private Stack<String> stack;// 用于存储操作符的栈
private List<String> postfixList;// 用于存储后序表达式的链表
public List<String> getPostfixList() {
return postfixList;
}
public Trans(List<String> list){
stack=new Stack<String>();
postfixList=new ArrayList<String>();
for(String str:list){
// 这个分支是当前项是操作符号的情况
if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/") || str.equals("(") || str.equals(")") ){
String opThis=str;
if(stack.size()==0){
// 如果栈为空,直接把操作符推入栈
stack.push(opThis);
}else if(str.equals("(")){
// 如果操作符是左括号,直接推入栈
stack.push(opThis);
}else if(str.equals(")")){
// 如果操作符是右括号,则往前找左括号,将左括号之后的操作符放到后续表达式列表中
while(stack.peek().equals("(")==false){ // stack.peek()是取栈顶元素而不弹出
postfixList.add(stack.pop());
}
stack.pop();// 左括号丢弃,由此完成了去括号的过程
}else{
// 看栈顶元素,如果它优先级大于等于当前操作符的优先级,则弹出放到后续表达式列表中
while( stack.size()>0 && (getOpLevel(stack.peek())>=getOpLevel(opThis)) ){
postfixList.add(stack.pop());
}
stack.push(opThis);// 当前操作符入栈
}
}else{
// 这个分支是当前项是操作数的情况
postfixList.add(str);// 操作数直接入栈
}
}
// 将栈中余下的操作符弹出放到后续表达式列表中
while(stack.size()>0){
String opTop=stack.pop();
postfixList.add(opTop);
}
}
// 取得操作符的等级
private int getOpLevel(String op){
if(op.equals("+") || op.equals("-") ){
return 0;
}else if(op.equals("*") || op.equals("/") ){
return 1;
}
return -1;
}
public void print(){
for(String str:postfixList){
System.out.print(str);
}
}
}
Calculator类 计算后续表达式运算结果类,它接受经过Trans类处理的postfixList,又采用了栈进行辅助,计算结果方式是见到操作数先入栈,见到操作符则从栈中弹出两个操作数进行运算,得到结果后再入栈,执行完毕后弹出栈的顶项(必是最后一项)即是算术表达式的最终结果::
package com.hy;
import java.util.List;
import java.util.Stack;
// 此类用于计算后续表达式的值
public class Calculator {
private Stack<String> stack;
public Calculator(List<String> list){
stack=new Stack<String>();
for(String str:list){
// 这个分支是当前项是操作符号的情况
if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/") || str.equals("(") || str.equals(")") ){
float op2=Float.parseFloat(stack.pop());
float op1=Float.parseFloat(stack.pop());
float result=0;
if(str.equals("+")){
result=op1+op2;
}else if(str.equals("-")){
result=op1-op2;
}else if(str.equals("*")){
result=op1*op2;
}else if(str.equals("/")){
result=op1/op2;
}
stack.push(String.valueOf(result));
}else{
// 如果是操作数直接入栈
stack.push(str);
}
}
}
// 取得结果
public String getResult(){
return stack.peek();
}
}
输出示例:
请输入算术表达式:1.2+3.4-5*(1+3.1)
1.2+3.4-5*(1+3.1)=-15.9
请输入算术表达式:23-4-5*7
23-4-5*7=-16.0
请输入算术表达式:(2+3)*4-(5-6)*7.0
(2+3)*4-(5-6)*7.0=27.0
到这里,基本上算是实现了算术表达式的计算,当然还有需要完善的地方,比如用正则表达式对输入的算术表达式进行预验证,用二叉树形成语法结构等,这些留待日后完成。可以想象如果没有利用波兰数学家卢卡希维茨(Jan Lukasiewicz)发明的后续表达式助力,代码不知会写得多么复杂难懂。由此可知除了分解问题外,合适的数学工具也是改善代码的重要手段。
喝水不忘挖井人,我的参考资料如下:
1.Java数据结构与算法(第二版) [美]Robert Lafore著
2.栈的应用--中序表达式转后序表达式 https://www.cnblogs.com/bgmind/p/3989808.html
3.波兰式,逆波兰式与表达式求值 https://blog.csdn.net/linraise/article/details/20459751
另外使用二叉树计算算术表达式的方案请见 https://www.cnblogs.com/xiandedanteng/p/11457783.html
--END--2019年9月3日10点08分
来源:oschina
链接:https://my.oschina.net/u/4264283/blog/3407096