四则运算的优先级
()为第一优先级 */为第二优先级 +-最低优先级
根据优先级将中缀表达式转为后缀表达式
如 2 * 3 改为 2 3 * 这样可以基于栈进行运算
代码实现部分
计算主类:
package com.littlehow.math;
import java.util.*;
import java.util.regex.Pattern;
import static com.littlehow.math.CalculateVerify.*;
/**
* 整形数据的四则运算
*/
public class Calculate {
private static final Set<Character> operators = new HashSet<>();
private static final Set<String> operatorString = new HashSet<>();
static {
char[] ops = "+-*/()".toCharArray();
for (char op : ops) {
operators.add(op);
}
operatorString.add("+");
operatorString.add("-");
operatorString.add("*");
operatorString.add("/");
}
public static int calculate(String target) {
verify(target);
//切分成单体成分
List<String> result = transferCalc(target);
//中缀表达式转后缀表达式
result = transferCalcSuffix(result);
Stack<Integer> stack = new Stack<>();
for (String s : result) {
checkParam(s, stack, target);
if ("+".equals(s)) {
stack.push(stack.pop() + stack.pop());
} else if ("-".equals(s)) {
stack.push(- stack.pop() + stack.pop());
} else if ("*".equals(s)) {
stack.push(stack.pop() * stack.pop());
} else if ("/".equals(s)) {
Integer i1 = stack.pop();
Integer i2 = stack.pop();
stack.push(i2 / i1);
} else {
stack.push(Integer.parseInt(s));
}
}
if (stack.size() != 1) {
throwArgumentException(target);
}
return stack.pop();
}
/**
* 是否为数字
* @param i -- 字符
* @return true表示为数字
*/
private static boolean isDigital(char i) {
return i >= '0' && i <= '9';
}
private static boolean isDigital(String s) {
return Pattern.matches("\\d+", s);
}
/**
* 是否为运算符
* @param c -- 字符
* @return true表示为运算符
*/
private static boolean isOperator(char c) {
return operators.contains(c);
}
/**
* 转换运算 中缀表达式转换为后缀表达式
* @param target -- 目标
* @return 转换后的结构
*/
private static List<String> transferCalc(String target) {
String tmp = target.replace(" ", "");
List<String> splitList = new ArrayList<>();
StringBuilder sb = new StringBuilder();
char[] cs = tmp.toCharArray();
for (char c : cs) {
if (isDigital(c)) {
sb.append(c);
} else if (isOperator(c)) {
//如果有数字,则将数字保存
if (sb.length() > 0) {
splitList.add(sb.toString());
//重置sb
sb.delete(0, sb.length());
}
splitList.add(c+"");
} else {
throwArgumentException(target);
}
}
if (sb.length() > 0) {
splitList.add(sb.toString());
}
return splitList;
}
/**
* 将中缀表达式变成后缀表达式
* @param orig -- 中缀表达式集合
* @return 后缀表达式集合
*/
private static List<String> transferCalcSuffix(List<String> orig) {
List<String> result = new ArrayList<>();
//栈信息
Stack<String> stack = new Stack<>();
for (String s : orig) {
if (isDigital(s)) {
result.add(s);
} else if ("*".equals(s) || "/".equals(s) || "(".equals(s)) {
if (stack.size() > 0 && !"(".equals(stack.peek())) {
//如果上一个符号是括号,则元算符不可以取出,因为优先级此刻还在括号内
String last = null;
if ("(".equals(s)) { //括号的前一个符号优先级低于括号
last = stack.pop();
}
while (!stack.empty() && !"(".equals(stack.peek())
&& !"+".equals(stack.peek())
&& !"-".equals(stack.peek())) {
result.add(stack.pop());
}
if (last != null) {
stack.push(last);
}
}
stack.push(s);
} else if ("+".equals(s) || "-".equals(s)) {
//将之前的全部出栈,括号优先级最高,所以遇(则停止
while (!stack.empty() && !"(".equals(stack.peek())) {
result.add(stack.pop());
}
stack.push(s);
} else {//将闭合括号中的操作符全部出栈
while (!"(".equals(stack.peek())) {
result.add(stack.pop());
}
//基于先前的简单检测,肯定会处于(处跳出循环
stack.pop();
}
}
while (!stack.empty()) {
result.add(stack.pop());
}
return result;
}
/**
* 判断值,基本校验不完整,所以需要计算时在进行表达式正常的校验
* 如果遇到加减乘除运算符,但是栈内数据不足两条,则抛出异常
* @param s -- 运算符
* @param stack -- 数据栈
* @param target -- 原始表达式
*/
private static void checkParam(String s, Stack<Integer> stack, String target) {
if (operatorString.contains(s) && stack.size() < 2) {
throwArgumentException(target);
}
}
}
校验表达式类:
package com.littlehow.math;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 检测类
*/
class CalculateVerify {
//如果是+-*/开头或结尾,如果+-*/()之间没有任何数字,则表示非法的表达式
private static final Pattern invalidExpression = Pattern.compile("([\\+\\-\\*/][\\)\\(]?\\s*[[\\+\\-\\*/]])|\\(\\s*\\)|[\\+\\-\\*/]\\s*$|^\\s*[\\+\\-\\*/]");
/**
* 判断是否为非法的四则运算符
* @param target -- 运算字符串
*/
private static void verifyExpression(String target) {
Matcher matcher = invalidExpression.matcher(target);
boolean flag = matcher.find();
if (flag) {
throw new IllegalArgumentException("非法表达式:" + target);
}
}
/**
* 验证括号
* @param target -- 运算字符串
*/
private static void verifyBracket(String target) {
//括号之前只有数字或者什么都没有则为非法
if (Pattern.matches("(.*\\(\\d*\\s*\\).*)|", target)) {
throwArgumentException(target);
}
char bbk = '(';
char abk = ')';
int bc = 0;//前后括号数量必须一致,且前括号必须先于后括号出现
for (int i = 0, len = target.length(); i < len; i++) {
char c = target.charAt(i);
if (c == bbk) {
bc++;
} else if (c == abk) {
bc--;
if (bc < 0) {
throwArgumentException(target);
}
}
}
if (bc > 0) {
throwArgumentException(target);
}
}
/**
* 简单检查参数
* @param target -- 参数
*/
static void verify(String target) {
if (target == null) {
throwArgumentException("null");
}
target = target.trim();
if (target.length() == 0) {
throwArgumentException(target);
}
if (!target.contains("+") &&
!target.contains("-") &&
!target.contains("*") &&
!target.contains("/")) {
throwArgumentException(target);
}
//如果包含其他字符
if (target.replaceAll("[\\d\\s\\+\\-\\*/\\(\\)]", "").length() > 0) {
throwArgumentException(target);
}
verifyExpression(target);
verifyBracket(target);
}
/**
* 不正常的表达式进行抛出
* @param target -- 表达式
*/
static void throwArgumentException(String target) {
throw new IllegalArgumentException("非法表达式:" + target);
}
}
下面就是测试了:
package com.littlehow.math;
import java.util.Scanner;
public class TestCalculate {
private static void test() {
Scanner scanner = new Scanner(System.in);
while (true) {
//input 3 + 2 output 3 + 2 = 5
//input 5 * 4 / 7 * (3 + 4) output 5 * 4 / 7 * (3 + 4) = 14
//input 3 * (4 * (2 + 6) / 5) * 7 * 6 / (5 + 3) output 3 * (4 * (2 + 6) / 5) * 7 * 6 / (5 + 3) = 94
//input 3 output 非法表达式
//input 3 + 4 * output 非法表达式
System.out.print("请输入表达式:");
String text = scanner.nextLine();
if ("quit".equalsIgnoreCase(text) || "exit".equalsIgnoreCase(text)) {
break;
}
try {
int result = Calculate.calculate(text);
System.out.println(text + " = " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
test();
}
}
代码中都有注释,所以就不再过多解释了...
littlehow写于2018-08-29