数据结构与算法之逆波兰计算器

拼搏现实的明天。 2021-12-07 12:55 395阅读 0赞

前言

在上篇文章我们介绍了什么是前缀(波兰表达式)、中缀、后缀表达式(逆波兰表达式)。中缀表达式是通用的算术表达式,也是我们人常用算术表示方法。因为中缀表达式本身不容易被计算机解析,所以通常会转换为前缀或者后缀表达式。前面分别有介绍两种表达式的计算过程,可以看到前缀表达式计算过于复杂,所以通常转换为后缀表达式来进行计算,本编文章我们将结合后缀表达式来实现一个综合计算器。

综合计算器

算术表达式类

  1. public class ArithmeticExpression {
  2. /**
  3. * 匹配+ - * / ( ) 运算符正则表达式
  4. */
  5. private static final String OPERATOR_REGEX = "\\+|-|\\*|/|\\(|\\)";
  6. /**
  7. * 匹配数字正则表达式
  8. */
  9. private static final String NUMBER_REGEX = "^(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*))$";
  10. /**
  11. * 运算符
  12. */
  13. public static final String LEFT_SYMBOL = "(";
  14. public static final String RIGHT_SYMBOL = ")";
  15. public static final String ADD_SYMBOL = "+";
  16. public static final String SUB_SYMBOL = "-";
  17. public static final String MUL_SYMBOL = "*";
  18. public static final String DIV_SYMBOL = "/";
  19. /**
  20. * 运算符优先级
  21. */
  22. private static final int OPERATOR_PRIORITY_LEVEL_00 = 0;
  23. private static final int OPERATOR_PRIORITY_LEVEL_01 = 1;
  24. private static final int OPERATOR_PRIORITY_LEVEL_02 = 2;
  25. /**
  26. * 表达式转list
  27. *
  28. * @param expression 中缀表达式
  29. * @return
  30. */
  31. public static List<String> toExpressionList(String expression) {
  32. List<String> result = new ArrayList<>();
  33. String temp = "";
  34. for (int i = 0; i < expression.length(); i++) {
  35. String str = expression.substring(i, i + 1);
  36. // 使用正则表达示判断是否是数字
  37. if (!isNumber(str)) {
  38. result.add(str);
  39. } else {
  40. // 判断是否是表达式最后一个字符,如果是,直接放入list
  41. if (i == expression.length() - 1) {
  42. result.add(str);
  43. continue;
  44. }
  45. temp += str;
  46. // 处理多位数
  47. String nextStr = expression.substring(i + 1, i + 2);
  48. if (!isNumber(nextStr)) {
  49. result.add(temp);
  50. temp = "";
  51. }
  52. }
  53. }
  54. return result;
  55. }
  56. /**
  57. * 去除空白字符
  58. *
  59. * @param str
  60. * @return
  61. */
  62. public static String replaceAllBlank(String str) {
  63. // \\s+ 匹配任何空白字符,包括空格、制表符、换行符等等,等价于[\f\n\t\v]
  64. return str.replaceAll("\\s+", "");
  65. }
  66. /**
  67. * 判断是不是数字(int float double long)
  68. *
  69. * @param str
  70. * @return
  71. */
  72. public static boolean isNumber(String str) {
  73. Pattern pattern = Pattern.compile(NUMBER_REGEX);
  74. return pattern.matcher(str).matches();
  75. }
  76. /**
  77. * 判断是不是运算符
  78. *
  79. * @param str
  80. * @return
  81. */
  82. public static boolean isOperator(String str) {
  83. return str.matches(OPERATOR_REGEX);
  84. }
  85. /**
  86. * 匹配运算优先级
  87. *
  88. * @param operator
  89. * @return
  90. */
  91. public static int calcLevel(String operator) {
  92. if (ADD_SYMBOL.equals(operator) || SUB_SYMBOL.equals(operator)) {
  93. return OPERATOR_PRIORITY_LEVEL_01;
  94. } else if (MUL_SYMBOL.equals(operator) || DIV_SYMBOL.equals(operator)) {
  95. return OPERATOR_PRIORITY_LEVEL_02;
  96. } else {
  97. return OPERATOR_PRIORITY_LEVEL_00;
  98. }
  99. }
  100. /**
  101. * 中缀表达式转后缀表达式
  102. *
  103. * @param expression 中缀表达式
  104. * @return
  105. */
  106. public static List<String> parseSuffixExpression(String expression){
  107. List<String> result = new ArrayList<>();
  108. // 中缀表达式转list
  109. List<String> toInfixExpressionList = toExpressionList(expression);
  110. Stack<String> stack = new Stack<>();
  111. for (String item : toInfixExpressionList) {
  112. if (isNumber(item)) { // 如果是数字直接放入结果集中
  113. result.add(item);
  114. } else if (LEFT_SYMBOL.equals(item)) { // 如果是左括号直接入栈
  115. stack.push(item);
  116. } else if (RIGHT_SYMBOL.equals(item)) {
  117. // 如果是右括号,则依次弹出栈顶的运算符,并放入result list中,直到遇到左括号为止,此时将这一对括号丢弃
  118. while (!LEFT_SYMBOL.equals(stack.peek())) {
  119. result.add(stack.pop());
  120. }
  121. stack.pop(); // 消除左括号
  122. } else {
  123. // 当item的优先级小于等于栈顶的运算符,将栈顶的运算符弹出并加入到result中,并重复此步骤,直到条件不满足时结束
  124. while (stack.size() != 0 && calcLevel(stack.peek()) >= calcLevel(item)) {
  125. result.add(stack.pop());
  126. }
  127. // 还需要将item入栈
  128. stack.push(item);
  129. }
  130. }
  131. // 将栈中剩余的运算符弹出放入result中
  132. while (stack.size() != 0){
  133. result.add(stack.pop());
  134. }
  135. return result;
  136. }
  137. }

综合计算器类

  1. public class ReversePolishMultiCalc {
  2. /**
  3. * 根据表达式计算结果
  4. *
  5. * @param expression 表达式
  6. * @return
  7. */
  8. public static double calculation(String expression) {
  9. double result = 0d;
  10. if (null == expression || "".equals(expression.trim())) {
  11. throw new RuntimeException("expression is empty");
  12. }
  13. // 过滤表达式,去除无效字符
  14. expression = ArithmeticExpression.replaceAllBlank(expression);
  15. // 将中缀表达式转换为后缀表达式
  16. List<String> expressionList = ArithmeticExpression.parseSuffixExpression(expression);
  17. result = calculator(expressionList);
  18. return result;
  19. }
  20. /**
  21. * 根据后缀表达式计算结果
  22. *
  23. * 从左至右扫描,如果是数字则压入栈中,如果是运算符则取出栈顶元素和次栈顶元素进行运算,并将运算结果入栈
  24. * 重复上述过程,直到循环结束,最终留到栈中的就是运算结果
  25. *
  26. * @param dataList
  27. * @return
  28. */
  29. public static double calculator(List<String> dataList) {
  30. Stack<String> stack = new Stack<>();
  31. for (String item : dataList) {
  32. // 使用正则表达示判断是否是数字
  33. if (ArithmeticExpression.isNumber(item)) {
  34. // 入栈
  35. stack.push(item);
  36. } else {
  37. String num1 = stack.pop();
  38. String num2 = stack.pop();
  39. double calRsult = cal(num2, num1, item);
  40. // 将计算结果入栈
  41. stack.push(String.valueOf(calRsult));
  42. }
  43. }
  44. return Double.valueOf(stack.pop());
  45. }
  46. /**
  47. * 计算
  48. *
  49. * @param num1 数据1
  50. * @param num2 数据2
  51. * @param operation 运算符
  52. * @return
  53. */
  54. private static double cal(String num1, String num2, String operation) {
  55. double result = 0d;
  56. switch (operation) {
  57. case ArithmeticExpression.ADD_SYMBOL:
  58. result = Double.valueOf(num1) + Double.valueOf(num2);
  59. break;
  60. case ArithmeticExpression.SUB_SYMBOL:
  61. result = Double.valueOf(num1) - Double.valueOf(num2);
  62. break;
  63. case ArithmeticExpression.MUL_SYMBOL:
  64. result = Double.valueOf(num1) * Double.valueOf(num2);
  65. break;
  66. case ArithmeticExpression.DIV_SYMBOL:
  67. result = Double.valueOf(num1) / Double.valueOf(num2);
  68. break;
  69. default:
  70. break;
  71. }
  72. return result;
  73. }
  74. }

测试:

  1. public class ReversePolishMultiCalcTest {
  2. public static void main(String[] args) {
  3. String expression = "1+((2+3)*4)-5";
  4. double calculation = ReversePolishMultiCalc.calculation(expression);
  5. System.out.println("calculation result is " + calculation);
  6. }
  7. }

输出结果:

  1. calculation result is 16.0

发表评论

表情:
评论列表 (有 0 条评论,395人围观)

还没有评论,来说两句吧...

相关阅读

    相关 波兰计算器

    该版本只支持整数类型数进行计算 本文主要是对逆波兰计算器的原理介绍和代码实现,所以对前缀、中缀、后缀表达式不做过多的赘述,下面先简单介绍一下后缀表达式 后缀表达式又称逆波兰

    相关 波兰计算器

    > 逆波兰表达式又叫做后缀表达式,逆波兰表达式把运算量写在前面,把运算符写在后面。 我们平时书写的表达式都是中缀表达式,这个表达式更加方便我们进行计算。但是相对于计算机而言,

    相关 波兰计算器

    一 整体算法 1 输入一个中缀表达式。 2 将中缀表达式转换为后缀表达式(逆波兰表达式)。 3 根据逆波兰表达式求算最后的结果。 二 将中缀表达式转换为后缀表达式