用过python或者PHP的同学应该知道,它们都有个eval函数,这个函数真的是好用啊,比如我算数的时候就喜欢用python的控制台来当做计算器。比如:
Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> print('公积金每年存多少?',3500*0.12*2*12)
公积金每年存多少? 10080.0
>>> print('余额宝年利率0.27的话,一万每天利息多少?',1*270/365)
余额宝年利率0.27的话,一万每天利息多少? 0.7397260273972602
>>> 400+10/(120-20)+400
800.1
>>>
但java貌似没有。
以下是我自己的实现。
思路:
思路就是分两步来进行
- 翻译输入的数学表达式,也就是中缀表达式转后缀表达式。例如
a+b*(c-d)
转为后缀表达式就是abcd-*+
- 对后缀表达式计算结果。这里用到了栈存储计算结果,每次都是对两个数计算,例如
abcd-*+
,计算方法是先从头遍历,数字直接入栈,当遇到计算符,则从栈顶取出来两个数计算然后再把结果压栈,最终全部计算完之后栈里面只剩下一个元素就是结果。
1.中缀表达式转后缀表达式
实现转换的基本步骤如下:
- 初始化一个运算符栈。
- 从算数表达式输入的字符串中依次从左向右每次读取一个字符。
- 如果当前字符是操作数,则直接填写到后缀表达式。
- 如果当前字符是(左括号,将其压入运算符栈(第一步定义)。
- 如果当前字符为运算符,则
- 当运算符栈为空,则将其压入运算符栈。
- 当此运算符的优先级高于栈顶元素的时候,则将此运算符压入运算符栈;否则,弹出栈顶运算符到后缀表达式,并且将当前运算符压栈。回到步骤2.
- 如果当前字符是)右括号,反复将栈顶元素弹出到后缀表达式,直到栈顶元素是左括号(为止,并将左括号从栈中弹出丢弃。
- 如果读取还未完成,回到步骤2.
- 如果读取完成,则将栈中剩余的运算符依次弹出到后缀表达式。
private static String transfer(String mathStr) {
// 标记输出结果
StringBuilder result = new StringBuilder();
// 1.初始化一个运算符栈。
Stack<Character> stack = new Stack();
if (mathStr == null || mathStr.length() == 0) {
return null;
}
System.out.println("--------------");
System.out.println("中缀表达式:" + mathStr);
char[] arr = mathStr.toCharArray();
for (int i = 0; i < arr.length; i++) {
// 2.从算数表达式输入的字符串中依次从左向右每次读取一个字符。
char s = arr[i];
// 3.如果当前字符是操作数,则直接填写到后缀表达式。
if (Character.isDigit(s)) {
result.append(s);
}
// 4.如果当前字符是(左括号,将其压入运算符栈(第一步定义)。
else if ('(' == s) {
stack.push(s);
}
// 5.如果当前字符为运算符,则
else if ('+' == s || '-' == s || '*' == s || '/' == s) {
if (!stack.isEmpty()) {
char stackTop = stack.pop();
// 当此运算符的优先级高于栈顶元素的时候,则将此运算符压入运算符栈
if (compare(s, stackTop)) {
stack.push(stackTop);
stack.push(s);
}
// 否则,弹出栈顶运算符到后缀表达式,并且将当前运算符压栈。回到步骤2.
else {
result.append(stackTop);
stack.push(s);
}
}
// 5.1.当运算符栈为空,则将其压入运算符栈。
else {
stack.push(s);
}
}
// 6.如果当前字符是)右括号,反复将栈顶元素弹出到后缀表达式,直到栈顶元素是左括号(为止,并将左括号从栈中弹出丢弃。
else if (s == ')') {
while (!stack.isEmpty()) {
char item = stack.pop();
if (item != '(') {
result.append(item);
} else {
break;
}
}
}
}
while (!stack.isEmpty()) {
result.append(stack.pop());
}
System.out.println("后缀表达式:" + result.toString());
return result.toString();
}
//比较优先级
private static boolean compare(char s, char item) {
if (item == '(') {
return true;
}
if (s == '*' || s == '/') {
if (item == '+' || item == '-') {
return true;
}
}
return false;
}
2.计算结果
每次都是对两个数计算,例如 abcd-*+
,计算方法是先从头遍历,数字直接入栈,当遇到计算符,则从栈顶取出来两个数计算然后再把结果压栈,最终全部计算完之后栈里面只剩下一个元素就是结果。
private static int calculate(String transferToPostfix) {
Stack<Integer> stack = new Stack();
char[] c = transferToPostfix.toCharArray();
int a, b;
for (int i = 0; i < c.length; i++) {
switch (c[i]) {
case '+':
a = Integer.valueOf(stack.pop().toString());
b = Integer.valueOf(stack.pop().toString());
stack.push(b + a);
break;
case '-':
a = Integer.valueOf(stack.pop().toString());
b = Integer.valueOf(stack.pop().toString());
stack.push(b - a);
break;
case '*':
a = Integer.valueOf(stack.pop().toString());
b = Integer.valueOf(stack.pop().toString());
stack.push(b * a);
break;
case '/':
a = Integer.valueOf(stack.pop().toString());
b = Integer.valueOf(stack.pop().toString());
stack.push(b / a);
break;
default:
Character d = c[i];
stack.push(Integer.valueOf(d.toString()));
break;
}
}
return stack.pop();
}
3.测试
public static void main(String[] args) {
System.out.println("计算结果:" + calculate(transferToPostfix("1+2*(3-4)")));
System.out.println("计算结果:" + calculate(transferToPostfix("3-2/3")));
System.out.println("计算结果:" + calculate(transferToPostfix("2*3-9/(4-1)")));
}
--------------
中缀表达式:1+2*(3-4)
后缀表达式:1234-*+
计算结果:-1
--------------
中缀表达式:3-2/3
后缀表达式:323/-
计算结果:3
--------------
中缀表达式:2*3-9/(4-1)
后缀表达式:23*941-/-
计算结果:3
这里我使用了char来存储运算单位,因此只能运算个位数,如果要运算多位数或者是带小数的,把char类型换成自己封装的就可以了,思路都是一样的。