本次测试内容为结对开发,我和蒲煜凡组队进行开发,对于结对开发我们都没什么经验,初步分工为我编写计算类,由他编写页面,然后我进行测试,蒲煜凡进行指导。
通过这次结对开发,锻炼了我们合作的能力,美中不足是两人异地,配合起来有些僵硬
Text.java
package size;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Text {
//字符合法检验正则表达式
private static final Pattern EXPRESSION_PATTERN = Pattern.compile("[0-9\\.+-/*()= ]+");
//运算符优先级map
private static final Map<String, Integer> OPT_PRIORITY_MAP = new HashMap<String,Integer>(){
private static final long serialVersionUID = 4633502127458385800L;
{
put("(",0);
put("+",2);
put("-",2);
put("*",3);
put("/",3);
put(")",7);
put("=",20);
}
};
/**
* 运算执行类
* @param expression 算数表达式
* @return
*/
public static String Calc(String expression) {
//非空校验
if (expression == null || "".equals(expression.trim())) {
throw new IllegalArgumentException("表达式不能为空!");
}
//表达式字符合法校验
Matcher matcher = EXPRESSION_PATTERN.matcher(expression);
if (!matcher.matches()) {
throw new IllegalArgumentException("表达式含有非法字符!");
}
Stack<String> optStack = new Stack<>();//运算符栈
Stack<BigDecimal> numStack = new Stack<>();//数值栈
StringBuilder curNumBuilder = new StringBuilder();//正在读取的可变字符串
//逐个读取字符,执行计算操作
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c != ' ') {//丢弃空白字符
if ((c >= '0' && c <='9') || c == '.') {
curNumBuilder.append(c);
}
else {
if (curNumBuilder.length() > 0)
{
numStack.push(new BigDecimal(curNumBuilder.toString()));
curNumBuilder.delete(0, curNumBuilder.length());
}
String curOpt = String.valueOf(c);//当前运算符
if (optStack.empty())//运算符栈顶位空直接入栈
{
optStack.push(curOpt);
}
else {
if (curOpt.equals("(")) { //左括号直接入栈
optStack.push(curOpt);
}
else if (curOpt.equals(")")) //右括号执行括号内的运算
{
insideCalc(optStack, numStack, true);
}
else if (curOpt.equals("=")) {//等号进行剩余运算,运算结束
insideCalc(optStack, numStack, false);
return String.valueOf(numStack.pop().doubleValue());
}
else {
compareAndCalc(optStack, numStack, curOpt);//加减乘除判断后进行运算
}
}
}
}
}
//若字符串不以等号结尾
if (curNumBuilder.length() > 0) {
numStack.push(new BigDecimal(curNumBuilder.toString()));
}
insideCalc(optStack, numStack, false);
return String.valueOf(numStack.pop().doubleValue());
}
/**
* 遇到括号或等号时的运算
* @param optStack 运算符栈
* @param numStack 数值栈
* @param b true为括号,false为等号
*/
private static void insideCalc(Stack<String> optStack, Stack<BigDecimal> numStack,
boolean b) {
String opt = optStack.pop();
BigDecimal num2 = numStack.pop();
BigDecimal num1 = numStack.pop();
BigDecimal bigDecimal = floatingPointCalc(opt, num1, num2);
//结果入栈
numStack.push(bigDecimal);
if (b) {//右括号情况
if ("(".equals(optStack.peek())) {//遇到左括号停止运算并将其弹出
optStack.pop();
} else {
insideCalc(optStack, numStack, b);
}
} else {//等号情况,递归运算直到栈空
if (!optStack.empty()) {
insideCalc(optStack, numStack, b);
}
}
}
/**
* 二元运算,支持高精度
* @param opt
* @param num1
* @param num2
* @return
*/
private static BigDecimal floatingPointCalc(String opt, BigDecimal num1,
BigDecimal num2) {
BigDecimal result = new BigDecimal(0);
switch (opt) {
case "+":
result = num1.add(num2);
break;
case "-":
result = num1.subtract(num2);
break;
case "*":
result = num1.multiply(num2);
break;
case "/":
//BigDecimal的divide方法第一个参数表示除数,第二个表示小数点后位数,第三个表示舍入模式
result = num1.divide(num2, 10, BigDecimal.ROUND_HALF_DOWN);
break;
}
return result;
}
/**
* 判断当前运算符与栈顶运算符优先级并进行计算
* @param optStack 运算符栈
* @param numStack 数值栈
* @param curOpt 当前运算符
*/
private static void compareAndCalc(Stack<String> optStack, Stack<BigDecimal> numStack,
String curOpt) {
//比较当前运算符与栈顶运算符的优先级
String peekOpt = optStack.peek();
int priority = getPriority(peekOpt, curOpt);
if (priority == -1 || priority == 0) {
//当前运算符比栈顶运算符低或同级,取栈顶运算符执行运算
String opt = optStack.pop();
BigDecimal num2 = numStack.pop();
BigDecimal num1 = numStack.pop();
BigDecimal bigDecimal = floatingPointCalc(opt, num1, num2);//计算
numStack.push(bigDecimal);//结果入栈
//判断栈空与否,非空还要再进行递归判断
if (optStack.empty()) {
optStack.push(curOpt);
} else {
compareAndCalc(optStack, numStack, curOpt);
}
} else {
//当前优先级高,入栈
optStack.push(curOpt);
}
}
/**
* 判断运算符优先级
* @param peekOpt
* @param curOpt
* @return 1表示同级,1表示第二个运算符级别高,-1表示第一个运算符级别高
*/
private static int getPriority(String peekOpt, String curOpt) {
int result = OPT_PRIORITY_MAP.get(curOpt) - OPT_PRIORITY_MAP.get(peekOpt);
return result;
}
/**
* 解决负数问题
* @param expression
* @return
*/
private static String updateExpression(String expression) {
//解决首位负数
if (expression.trim().charAt(0) == '-') {
expression = "0" + expression;
}
//解决括号中的负数
expression = expression.replace("(-", "(0-");
return expression;
}
public static void main(String[] args) {
String expression = "";
Scanner in = new Scanner(System.in);
System.out.println("请输入要计算的表达式:");
expression = in.nextLine();
expression = updateExpression(expression);
System.out.println(Calc(expression));
}
}