一.简述
使用正则匹配加递归实现四则混合运算的(包括符号)的解析以及计算–实现语言Java
第一次写博客,看客们多多见谅。
也是一天晚上,和同学百无聊奈之时谈到这个问题,我是个实在的人的。
说写就写,总共从思考到编码到完全调试成功历时两个多小时,所以拿出来分享一下。
总体思想:因为所有表达式括号的作用无非就是要提高优先级而已,所以我们的目标就在于两个
1.去括号
2.计算括号里面的表达式【式子里面不带括号】
3.从括号的最内层开始计算,计算出结果去括号并替换计算值。直到计算完毕
4.处理正负数【附加】
二、关于以上三个问题,我们决定先解决掉第二个问题**
如上式子: 1+2-3*4/5
解决方案步骤如下:
1.分离开操作数和操作符
申请两个集合List(一个为String类型–存符号 一个为Double【此处选择用double类型主要是为了偷懒,计算各种小数的问题】–存数字)。所以拆分的结果为:
- String类型的–{+,-,*,/},存的是操作符,用a,b,c,d分别代替+,-,*,/
- Double类型的–{1,2,3,4,5},存的是操作数。
2.解决负数问题
因为表达式如果想成立,负数必须在第一个。如 -4+5-6*7/8 毕竟不可能为 4+-5-6*7/8那么,有人会说可能写成这样不就行了 4+(-5)-6*7/8 但是不行,因为我们解决的 第二个问题式子里面是不带括号的,所以不存在该情况。因此,我会在遇到这种表达式的时候使用替换为 0-4+5-6*7/8,即在式子最前面添上 “0”【因为0加任何数都不变,-4即为0-4】。
- 代码片段
if (calStr.charAt(0) == 'a' || calStr.charAt(0) == 'b') {
//如果 遇到减号(-) 就替换成 (@) 因为计算机不能知道他是减号还是负号
calStr = "0"+calStr.substring(1);
}
3.计算
此时已经有了一个存符号【操作符】的集合和一个数字【操作数】的集合,且解决掉了负号的问题。
- 先算乘法或者除法。计算过称为:
遍历两个集合,有乘除就算乘除,找到符号集合中乘除号的位置,假设为i,则可以知道两个数字【操作数】的位置为i和i+1。 - 代码片段:
if ("a".equals(calStrArr.get(index))) {
result = numberArr.get(index)+numberArr.get(index+1);
}else if ("b".equals(calStrArr.get(index))) {
result = numberArr.get(index)-numberArr.get(index+1);
}else if ("c".equals(calStrArr.get(index))) {
result = numberArr.get(index)*numberArr.get(index+1);
}else if("d".equals(calStrArr.get(index))){
result = numberArr.get(index)/numberArr.get(index+1);
}
//a,b,c,d分别为+-*/
- 对计算后的结果在两个集合中替换相关,递归继续重复,直到算出最终结果【标志为–符合集合没有符号】
三、去括号
1.去括号的思想也就是,从最里层的括号开始依次计算出结果替换掉括号。思想也是递归–具体不做详述,见代码,代码有详尽的注释。
2.重要的点在于去括号,如下例子表达式1*(3-5),我们算出3-5=(-2),所以式子被替换为 1*(-2) ,那么此处不可直接去括号因为1*-2不合法。所以我的做法是将-2替换为一个特殊的字符如1*@2,将-号替换为@。然后在计算时候对这种情况做处理
- 代码片段:
String result_ = result>0?(result+""):("@"+(result__.substring(1)));
到此为止,已经计算解析出相关的表达式。期间肯定有不合理之处,希望大家提出来。初次写博客,不当之处,请多原谅下面贴出代码:
- 主要计算过程类:
package com.ylink.util;
import java.util.ArrayList;
import java.util.List;
public class NewOpr {
//计算不带括号的加减乘除
public double calBase(String calStr){
return DiffcultCal(calStr);
}
/**
* 替换 +-*chu 符号 分别为a,b,c,d
* @param calStr
* @return
*/
public String replace(String calStr){
calStr = calStr.replace("+", "a");
calStr = calStr.replace("-", "b");
calStr = calStr.replace("*", "c");
calStr = calStr.replace("/", "d");
return calStr;
}
/**
* 假若表达式: -1+2*3
*
* 主要是拆分,将一个简单的表达式(不含括号) 差分成 一个专门存符号的集合{+,*}和一个专门存 操作数的集合{-1,2,3}
* 其中一个细节就是对复数的处理,如-1
* @param calStr
* @return
*/
public ModelArr splitCalStr(String calStr){
ModelArr modelArr = new ModelArr();
List<Double> numberArr = new ArrayList<Double>();
List<String> calStrArr = new ArrayList<String>();
//对复数的处理
//如果 有 负号的 情况有三种
//1. -4
//2. -4+5
//3. -4+5 他其实就是(-4加5 但是如果写成 -4+5则不能解析。因此加0变为 0-4+5)
//负号一定为 表达式的第一个
if (calStr.charAt(0) == 'a' || calStr.charAt(0) == 'b') {
//如果 遇到减号(-) 就替换成 (@) 因为计算机不能知道他是减号还是负号
calStr = "0"+calStr.substring(1);
}
String regex = "[a-d]";
String[] numberArr_ = calStr.split(regex);
for (int i = 0; i < numberArr_.length; i++) {
/*
* 在拆分的时候,如果遇到
*/
if (numberArr_[i].startsWith("@")) {
numberArr.add(0-Double.parseDouble(numberArr_[i].substring(1)));
continue;
}
numberArr.add(Double.parseDouble(numberArr_[i]));
}
for (int i = 0; i < calStr.length(); i++) {
if (calStr.charAt(i) == 'a' || calStr.charAt(i) == 'b' || calStr.charAt(i) == 'c' || calStr.charAt(i) == 'd') {
calStrArr.add(""+calStr.charAt(i));
}
}
modelArr.setNumberArr(numberArr);
modelArr.setCalStrArr(calStrArr);
// modelArr
return modelArr;
}
/**
* 对 不带括号的加减乘除 运算表达式(已经被拆分成 一个符号集合和一个值集合)进行计算
* @param modelArr
* @return
*/
public double baseCal(ModelArr modelArr){
List<String> calStrArr = modelArr.getCalStrArr();
List<Double> numberArr = modelArr.getNumberArr();
//判断符号集合是不是空了且值集合也只有一个操作数。如果是,则说明已经计算出结果。否则计算该表达式
if ((calStrArr == null || calStrArr.size() == 0) && numberArr.size() == 1) {
return numberArr.get(0);
}else{
double result = Double.MIN_VALUE;
int index = isHave_cd(calStrArr);
if (index == -1) {
index = 0;
}
if ("a".equals(calStrArr.get(index))) {
result = numberArr.get(index)+numberArr.get(index+1);
}else if ("b".equals(calStrArr.get(index))) {
result = numberArr.get(index)-numberArr.get(index+1);
}else if ("c".equals(calStrArr.get(index))) {
result = numberArr.get(index)*numberArr.get(index+1);
}else if("d".equals(calStrArr.get(index))){
result = numberArr.get(index)/numberArr.get(index+1);
}
modelArr.setCalStrArr(doCalCompleteForCalStrArr(calStrArr,index));
modelArr.setNumberArr(doCalCompleteForNumberArr(numberArr,index,result));
return baseCal(modelArr);
}
}
/**
* 带括号 的加减乘除
*/
public double DiffcultCal(String calStr){
int start = findLastLeftKH(calStr);
int end = findNearLeftKH(calStr,start);
//如果不存在括号,则直接计算。否则递归调用
if (start == -1 || end == -1) {
//替换符号
calStr = replace(calStr);
//拆分表达式
ModelArr modelArr = splitCalStr(calStr);
//计算结果
return baseCal(modelArr);
}else{
//拿到括号 "包起来"的表达式。
String calStr_ = calStr.substring(start+1, end);
//替换符号
calStr_ = replace(calStr_);
//拆分表达式
ModelArr modelArr = splitCalStr(calStr_);
//计算
double result = baseCal(modelArr);
//处理计算的负数,然后去括号重新生成新的表达式
//如果 遇到减号(-) 就替换成 (@) 因为计算机不能知道他是减号还是负号
//切记此处,如果 得到的 值是负值。应加上括号。否则如:1+(3-5) 计算之后3-5=-2 就变成1+-2就错了。所以 我么可以写成 1+@2 以后开到@2就转换成-2
String result__ = result+""; //此处得到 -2
//("@"+(result__.substring(1))) 得到@2, 若是直接@+result则得到 @-2
String result_ = result>0?(result+""):("@"+(result__.substring(1)));
//计算完结果,将结果替换到 相应的表达式中。形成相应的新的表达式。--主要作用是去括号
//那么此处为什么,不用以前的法子用 0-2 替换成-2。因为假设表达式为10*(3-4) 按照以前替换就会变为 10*0-1 而正确
//则应该是10*(0-1),那么肯定会有人说加上括号不就完了,但是我们的目的是为了去括号,你去掉了括号又加了括号。是无用功
calStr = calStr.substring(0,start)+result_+calStr.substring(end+1);
//递归调用
return DiffcultCal(calStr);
}
}
/**
* 是否还存在乘法和除法
* @return -1代表不存在,否则返回相关的下标
*/
public int isHave_cd(List<String> calStrArr){
for (int i = 0; i < calStrArr.size(); i++) {
if ("c".equals(calStrArr.get(i)) || "d".equals(calStrArr.get(i))) {
return i;
}
}
return -1;
}
/**
* 算完数据之后,将 符号数组 的相应数据删除
* @param calStrArr
* @param index
* @return
*/
public List<String> doCalCompleteForCalStrArr(List<String> calStrArr,int index){
calStrArr.remove(index);
return calStrArr;
}
/**
* 算完数据之后,将 数组数组 的相应数据删除
* @param numberArr
* @param index
* @return
*/
public List<Double> doCalCompleteForNumberArr(List<Double> numberArr,int index,Double result){
numberArr.set(index, result);
numberArr.remove(index+1);
return numberArr;
}
/**
* 从左往右 最后一个左括号的位置
*
* -1代表没有
*
* @return
*/
public int findLastLeftKH(String calStr){
return calStr.lastIndexOf("(");
}
/**
* 找对应的左括号,的最近的一个右括号
*
* -1代表没有
*
* @return
*/
public int findNearLeftKH(String calStr,int leftIndex){
return calStr.indexOf(")", leftIndex);
}
}
- 一个封装的Bean,主要是为了参数好传递
package com.ylink.util;
import java.util.List;
/**
* 用于存放 拆分 的表达式
* @author Administrator
*
*/
public class ModelArr {
//存值
private List<Double> numberArr;
//存符号
private List<String> calStrArr;
public List<Double> getNumberArr() {
return numberArr;
}
public void setNumberArr(List<Double> numberArr) {
this.numberArr = numberArr;
}
public List<String> getCalStrArr() {
return calStrArr;
}
public void setCalStrArr(List<String> calStrArr) {
this.calStrArr = calStrArr;
}
}
- 测试类:
package com.ylink.util;
public class Test {
public static void main(String[] args) {
try {
String calStr = "((-1)*3/(-3+1)*(-1.0-0.000))";
// String calStr = "-(-1.0)";
System.out.println("表达式为:"+calStr);
double result = new NewOpr().calBase(calStr);
System.out.println("计算结果为:"+result);
} catch (Exception e) {
System.out.println("表达式不正确..."+e);
}
}
}