"计算器算法"(含括号匹配,小数点)-之高效,通俗易懂详细解析.
涉及到的技术点(分两部分):1.第一阶段,没有括号时的计算1.用ArrayList容器即可解决2.简单的正则表达式3.for和while循环2.带括号,重点:1.匹配一个完整的括号就算一个,并再加入结果2.新的集合中长度的获取***关键3.真正的长度是:右括号减左
·
1.介绍
今天写了一个关于计算器的算法,主要注重的是简单高效和通俗易懂,并且是层层解释,下面我就谈谈一下这个算法,该算法可以对数字式子进行最终值的计算,给一段字符串式子(可以包含括号,正负整数,小数的加减乘除),那么就可以计算出结果.
2.涉及到的技术点(分两部分):
1.第一阶段,没有括号时的计算:
1.用ArrayList容器即可解决 2.简单的正则表达式 3.for和while循环
2.第二阶段,计算有括号时的匹配和计算问题:
1.把字符串存入集合,一个一个字符的存,为了后续的"读取和替换" 2.采用"堆栈"来获取"括号"的内容 3.然后计算得到的第一个"完整括号内容",再到第二个,大化小,因为是左右匹配,所以每次都能计算一个"小括号"里的式子. 4.前期加上了"( )",最后计算出来的就是结果.
用这些基础的技术点就能轻松完成计算器算法
3.逻辑过程讲解:
第一阶段:常规计算
给定一个字符串,比如: String str = “-3+5.0*-4-9/-3”;
1.把"数字"放入一个容器: -3,5.0,-4,9,-3;
2.把"运算符号"放入一个容器:+,*,-,/
3.先计算 *和/
4.再计算 +和-
5.先遍历符号集合,读到*或者/的时候,获取并移除该符号(后面元素自动往前移),然后同样从数字集合中获取并移除相同角标的数字和连着的数字(后面自动前移);
6.求出计算结果,然后在放入同样的位置
7.计算完*和/后集合只剩+,-,所以都是按顺序,就可以无限循环获取并移除0位置的符号,并获取和移除0位置的数字,当符号集合没有符号即停止,得出结果.
以上所讲的就是第一步常规计算的核心思想,大家要注意的还有一点,就是怎么获取数字,而且是包括”负号”的数字.
所以优先的就是处理负号,要处理的符号就是首位负号,以及*或者/后面的负号,其它不好当作运算符处理,处理的办法就是把两个位置的负号变成@代替:-3+5.0*-4-9/-3变成:@3+5.0*@4-9/@3;
1.所以就可以通过正则获取数字字符数组:str.split("[0123456789\\.@]");0-9加小数点
2.获取符号,放集合:str.split("[\\+\\-\\*/]"),加减乘都是正则表达式正规字符
3.先遍历*和/
1.get(i) =='*'||'/',就移除符号
2.从数字容器取两数字运算
ch = ops.remove(i);
d1 = numbers.remove(i);//自动往前;
d2 = numbers.remove(i);
if op ==* d1*d2 =='/' d1/d2
numbers.add(i,d1)//又放入
4.再变量+-;
while(ops不为空) 一直取,因为+-都是取第0个
ops.remove(0);
d1 = remove(0);
d2 = remove(0);
如果是加号: d1+d2;
如果是减号: d1-d2;
第二阶段:括号的问题计算
得出的结论:
1.式子就是一个集合包含着的"单个"字符
2.每次计算后,集合都会改变(涉及到第三步)
注意:
1.这里关键是判断集合的移除长度的问题(移除(2-8) 是5个长度)
2.但是计算到后面,前面原本的括号变成了"33.0000",他只占集合的1位,所以移除的时候,应该按角标来计算才是准确的,而不是"(2-8)".length,"真正的lenght"是最括号到右括号,即:右括号-左括号;然后移动左括号角标位置共"lenght次"
3.把结果放入
放入问题(即使放入88.00000或更长,那么还是占1个长度而已)
4.追踪括号里面就剩唯一的式子就得到结果了
以上的第2和第3步是解决问题的重点,同样是最容易出问题的地方
4.详细代码过程
第一部分:基础计算,括号在第二部分
/**
* 计算器算法
*
* 1.用ArrayList集合
*
* 2.正则表达获取数字集合注意有小数Double
*
* 3.获取符号集合,注意符号先转成@,再转成-
*
* 4.获取* / 注意符号的0,就获取数字的01 ,1就获取12,再放入
*
* 5.获取+- 获取完* / +-只用while寻0即可
*
*/
public class Demo11_Calc {
public static void main(String[] args) {
String str = "-3+5.0*-4-9/-3";
Result(str);
}
private static void Result(String str) {
ArrayList<String> ops = getOps(str);
ArrayList<Double> num = getNum(str);
// 先乘除再加减
for (int i = 0; i < ops.size(); i++) {
if (ops.get(i).contains("*") || ops.get(i).contains("/")) {
String op = ops.remove(i);
if (op.equals("*")) {
// 从数字集合取对应和后面一位数字
double d1 = num.remove(i);
double d2 = num.remove(i);
double number = d1*d2;
//再加上
num.add(i,number);
}
if (op.equals("/")) {
double d1 = num.remove(i);
double d2 = num.remove(i);
double number = d1/d2;
num.add(i, number);
}
i--; //刚刚移掉两个,却又刚加上一个新数,所以i要--,因为i++,所以才能取到,如果不加那么虽然貌似正常,但是如果如8*3/3,*/连在一起就报错了;因为连着的两个if;
}
}
//到+-,按顺序的所以就用while()了
while (ops.size() != 0) {
String op = ops.remove(0);
double d1 = num.remove(0);
double d2 = num.remove(0);
if (op.equals("+")) {
double number = d1+d2;
//再加入
num.add(0, number);
}
if (op.equals("-")) {
double number = d1-d2;
num.add(0, number);
}
}
System.out.println(num);
}
/**
* 获取符号 1.首位 和 * /后面 的-变成@,其他的-不用
*/
private static ArrayList getNum(String str) {
// -变成@
str = change(str);
ArrayList<Double> list = new ArrayList();
String[] split = str.split("[\\+\\-\\*/]");
for (int i = 0; i < split.length; i++) { // @3,5,@4,9,@3
String s = split[i];
// 再把@变成-
if (s.contains("@")) {
s = '-' + s.substring(1);
}
list.add(Double.parseDouble(s));
}
return list;
}
private static String change(String str) {
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
// @3+5*-4-9/-3
if (i == 0 && chars[i] == '-') {
str = '@' + str.substring(i + 1);
}
// @3+5*@4-9/@3
if (chars[i] == '*' && chars[i + 1] == '-' || chars[i] == '/' && chars[i + 1] == '-') {
str = str.substring(0, i + 1) + '@' + str.substring(i + 2);
}
}
return str;
}
// 获取符号
private static ArrayList getOps(String str) {
ArrayList<String> list = new ArrayList();
// @变-
str = change(str);
// @3+5*@4-9/@3
String[] split = str.split("[0-9\\.@]");// 表示0-9包括小数和@
for (int i = 0; i < split.length; i++) {
if (split[i].contains("+") || split[i].contains("-") || split[i].contains("*") || split[i].contains("/")) {
list.add(split[i]);
}
}
return list;
}
}
以上就是第一步,解决了没有括号时的计算的问题,下面是升级到能加括号计算
第二步骤:加括号算法:
public class Demo10_test {
private static Object calc;
public static void main(String[] args) {
calcDemo();
}
private static void calcDemo() {
String str = "3+12/(2-8)+7*((55+1)/2+0.2*(9-1))/2+10";
// 括弧:先判断左括号和右括号是否相等,再判断括号是否左右是否匹配
if (!isPiPei(str)) {
return;
}
if (str.contains("()")) {
System.out.println("包含了空的括号,不符合,请检查重新输入");
return;
}
/*--------------集合存单字符,用于随时移除和添加--------------*/
// 加上括号,这样就能当作最终的表达式并判断,最终求出结果
str = "(" + str + ")";
ArrayList str_list = new ArrayList();
for (int i = 0; i < str.length(); i++) {
str_list.add(str.charAt(i));
}
/*--------------获取所有的单位括号内容--------------*/
//关键是获取每一次“对称的括号”,并逐个计算,小到大,所以用栈是最好的方法。
Stack<Integer> stack = new Stack();
for (int i = 0; i < str_list.size(); i++) {
if (str_list.get(i) == '(') {
stack.add(i);
}
if (str_list.get(i) == ')') {
// 移除栈记录的内容角标
int s = stack.pop();
// 获取式子内容
StringBuilder sb = new StringBuilder();
for (int j = s; j <= i; j++) {
sb.append(str_list.get(j));
}
int sbLength = i - s + 1;// 重点(这部把是最容易出错的了,(sb.Length()按照的是字符串,看似满足其实不满足list,因为8123488或者再长的小树,在list只占一位,这里用i-s+1才满足所有))
for (int k = 0; k < sbLength; k++) {
str_list.remove(s);// 移除这个位置长度次
}
// 获取括号中的式子,计算成结果,在放入集合变成新的式子
String strCalc = sb.substring(1, sb.length() - 1);
double num = Demo11_Calc.Result(strCalc);
str_list.add(s, num);
System.out.println(str_list);
i = i - sbLength + 1; // 移掉后,占一位,++后要能获取"本来i位置"的下一位
if (i < -1) {// 防越界
break;
}
}
}
}
/**
* 判断字符串里的"左和右"括号是否"相等"
*/
private static boolean isCountEqual(String str) {
char[] chars = str.toCharArray();
int left = 0;
int right = 0;
for (int i = 0; i < chars.length; i++) {
if (chars[i] == '(') {
left++;
}
}
for (int i = 0; i < chars.length; i++) {
if (chars[i] == ')') {
right++;
}
}
if (left == right) {
} else {
System.out.println("表达式括号不匹配");
}
return left == right;
}
/**
* 判断字符串里的"左和右"括号是否"匹配"
*/
private static boolean isPiPei(String str) {
boolean isPiPei = false;
//先判断是否相等
if (!isCountEqual(str)) {
return false;
}
//定义栈记录左
Stack<Character> stack = new Stack<>();
char pop;
char[] chs = str.toCharArray();
fo:
for (int i = 0; i < chs.length; i++) {
switch (chs[i]) {
case '(':
stack.add(chs[i]);// 放在前面
break;
case ')':
pop = stack.pop();// 获取并移除
if (pop == '(') {
isPiPei = true;
} else {
isPiPei = false;
// 并停止所有
break fo;
}
break;
}
}
return isPiPei;
}
}
5.总结:
注意重点的步骤即可:
第一部分:
第一个和*/后面的负号现变成@,截取后再变回来
1.数字集合
2.符号集合
3.获取并移除符号集合元素和数字元素,再添加在原位
4.追踪符号集合为空即可求出结果;
第二部分:
括号的概念就是:获取一个就计算一个,并把计算后的值放入到原来的位置替换
1.移除的特点:移除左括号位置:(右括号-左括号+1次)
2.添加结果在左括号位置
3.为了方便,初始也加上(),让方法自动算出最后的结果。
4.额外的注意点就是前期的判断括号是否匹配问题。
更多推荐
已为社区贡献1条内容
所有评论(0)