首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >JavaRPN计算器

JavaRPN计算器
EN

Code Review用户
提问于 2019-08-25 16:03:27
回答 2查看 458关注 0票数 8

我发现我明显缺乏RPN计算器,所以我决定自己制作。我仍然在向它添加新的函数,但从现在开始,我想重构我的代码。

Main of JavaRPN

因为这是一个小问题,而且我还没有为它制作GUI,所以我只使用一个类。

代码语言:javascript
复制
package owl;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import java.util.Stack;

public class Main {

    public static void main(String[] args) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            Stack<Double> stack = new Stack<Double>();
            System.out.println("JavaRPN: Input numbers and operands separated by newline or space");
            DecimalFormat df = new DecimalFormat("#,###.#########");
            while (true) {
                String input = reader.readLine();
                String[] inputs = input.split(" ");
                for (int i = 0; i < inputs.length; i++) {
                    if (isNumber(inputs[i])) {
                        stack.push(Double.parseDouble(inputs[i]));
                        continue;
                    }
                    if (inputs[i].equals("e") || inputs[i].equals("p") || inputs[i].equals("c")) {
                        commands(inputs[i], stack, df);
                    } else if (inputs[i].equals("sq") || inputs[i].equals("sin") || inputs[i].equals("cos")
                            || inputs[i].equals("tan") || inputs[i].equals("asin") || inputs[i].equals("acos")
                            || inputs[i].equals("atan")) {
                        function(inputs[i], stack);
                    } else if (inputs[i].equals("+") || inputs[i].equals("-") || inputs[i].equals("*")
                            || inputs[i].equals("/") || inputs[i].equals("^")) {
                        operator(stack, inputs[i]);
                    } else {
                        System.out.println("ERROR: Invalid input");
                    }
                }
            }
        } catch (

        Exception e) {
            e.printStackTrace();
        }
    }

    private static void commands(String input, Stack<Double> stack, DecimalFormat df) {
        switch (input) {
        case "e":
            System.exit(0);
            ;
            break;
        case "p":
            if (stack.size() > 0) {
                System.out.println(df.format(stack.peek()));
                break;
            } else {
                System.out.println("ERROR: All Stacks Empty");
                break;
            }
        case "c":
            stack.clear();
            break;
        }

    }

    private static void function(String string, Stack<Double> stack) {
        if (stack.size() > 0) {
            double num = stack.pop();
            switch (string) {
            case "sq":
                stack.push(num * num);
                break;
            case "sin":
                stack.push(Math.sin(Math.toRadians(num)));
                break;
            case "cos":
                stack.push(Math.cos(Math.toRadians(num)));
                break;
            case "tan":
                stack.push(Math.tan(Math.toRadians(num)));
                break;
            case "asin":
                stack.push(Math.asin(Math.toRadians(num)));
                break;
            case "acos":
                stack.push(Math.acos(Math.toRadians(num)));
                break;
            case "atan":
                stack.push(Math.atan(Math.toRadians(num)));
                break;
            }
        }
    }

    private static void operator(Stack<Double> stack, String input) {
        if (stack.size() > 1) {
            double num2 = stack.pop();
            double num1 = stack.pop();
            switch (input) {
            case "+":
                stack.push(num1 + num2);
                break;
            case "-":
                stack.push(num1 - num2);
                break;
            case "*":
                stack.push(num1 * num2);
                break;
            case "/":
                stack.push(num1 / num2);
                break;
            case "^":
                stack.push(Math.pow(num1, num2));
                break;
            }
        } else {
            System.out.println("ERROR: Can't operate on an empty stack");
        }
    }

    private static boolean isNumber(String input) {
        try {
            Double.parseDouble(input);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

}

任何熟悉RPN计算器和UNIX系统的人都可能熟悉内置的dc计算器。计算器解析输入非常类似于旧程序,但支持小数与旧程序不同(或者至少我找不到在其中使用小数的方法)。

我知道,在代码执行之前检查输入的行中,我显然是多余的,该方法检查相同的精确方法。我以前曾尝试过重构,但我想不出如何在不破坏当前代码的情况下更有效地完成这一任务。

我经常在我的github上更新这个项目:https://github.com/ViceroyFaust/JavaRPN/tree/Refactoring ^我做了更新,请告诉我我是改进了还是使问题变得更糟了^_^

EN

回答 2

Code Review用户

发布于 2019-08-26 09:43:10

将所有代码放入一个类中会使程序变得非常复杂。将代码重构为每个类执行一个任务的类。这叫做单一责任原则

RpnEgine的核心是以下功能。我使用了Deque,因为它比同步堆栈性能更好。

代码语言:javascript
复制
public void process(String ... input) {
    for (String s: input) {
        final Consumer<Deque<Double>> func = FUNCTIONS.get(s);
        if (func != null) {
            func.accept(stack);
        } else {
            stack.push(Double.valueOf(s));
        }
    }
}

然后,数学操作和命令可以定义为lambdas或独立类。它们有点难看,因为从堆栈读取时,操作符顺序被颠倒了。您会注意到这段代码经常重复推送和弹出,所以最好将它们重构到一个检查堆栈大小、弹出操作数、将它们委托给BiFunction并推送结果的公共类。

它还引入了很大的灵活性,因为实现计算堆栈中任何内容之和的函数变得非常简单。

代码语言:javascript
复制
static {
    FUNCTIONS.put("+", (d) -> d.push(d.pop() + d.pop()));
    FUNCTIONS.put("-", (d) -> d.push((- d.pop()) + d.pop()));
    FUNCTIONS.put("/", (d) -> d.push(1.0 / (d.pop() / d.pop())));
    FUNCTIONS.put("sum", new Sum());
}

不要将输入解析放到同一个类中。创建一个名为RpnCli的类,该类读取输入并将其传递给RpnEngine

票数 4
EN

Code Review用户

发布于 2021-12-02 20:01:20

代码语言:javascript
复制
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.Stack;
import java.util.function.DoubleBinaryOperator;
import java.util.function.DoubleUnaryOperator;

import static java.util.Map.entry;

public class Rpn {
  private static final Map<String, DoubleUnaryOperator> FUNCTIONS = Map.ofEntries(
     entry("sin", Math::sin),
     entry("cos", Math::cos),
     entry("tan", Math::tan)
  );

  private static final Map<String, DoubleBinaryOperator> OPERATORS = Map.ofEntries(
    entry("+", (n1, n2) -> n1 + n2),
    entry("-", (n1, n2) -> n1 - n2),
    entry("*", (n1, n2) -> n1 * n2),
    entry("/", (n1, n2) -> n1 / n2),
    entry("^", Math::pow)
  );

  public static void main(String[] args) throws Exception {
    System.out.println("Rpn: Reverse Polish Notation Calculator");

    final var reader = new BufferedReader(new InputStreamReader(System.in));
    final var stack = new Stack<Double>();
    var valid = true;
    String line;

    while (valid && ((line = reader.readLine()) != null)) {
      for (final var token : line.split("\\s+")) {
        valid =
          applyNumber(token, stack) ||
          applyFunction(token, stack) ||
          applyOperator(token, stack);
      }
    }

    reader.close();
  }

  private static boolean applyFunction(final String token, final Stack<Double> stack) {
    if (!stack.isEmpty()) {
      final var f = FUNCTIONS.get(token);

      if (f != null) {
        stack.push(f.applyAsDouble(Math.toRadians(stack.pop())));

        return true;
      }
    }

    return false;
  }

  private static boolean applyOperator(final String token, final Stack<Double> stack) {
    if (stack.size() > 1) {
      final var op = OPERATORS.get(token);

      if (op != null) {
        final var num2 = stack.pop();
        final var num1 = stack.pop();
        stack.push(op.applyAsDouble(num1, num2));

        return true;
      }
    }

    return false;
  }

  private static boolean applyNumber(final String token, final Stack<Double> stack) {
    try {
      stack.push(Double.parseDouble(token));
      return true;
    }
    catch (final Exception e) {
      return false;
    }
  }
}

为了简洁起见,删除了“命令”,但以下概念通常适用:

  • 将令牌映射到函数、运算符和命令(未显示)。
  • 将逻辑代码片段组合在一起(即,不要将变量声明与println分开)。
  • 更改方法签名,使它们尽可能相似:相同的参数、相同的参数顺序和相同的返回类型。
  • 通过直接声明终止条件来避免while(true)
  • 请确保关闭任何打开的流,最好使用“尝试-使用-资源”(未显示)。
  • 考虑修改正则表达式,使其更宽松一些(例如,\\s+允许在多个空格之间拆分)?
  • 通过添加null检查API调用可以返回null的位置来验证数据。
  • 尽可能将变量声明为final
  • 在可行的情况下使用不变的数据结构(例如FUNCTIONSOPERATORS)。
  • 考虑在变量类型明显时使用var
  • 尝试使用标准术语命名变量(例如,解析文本通常被理解为涉及“记号”/“词汇”,而不是“字符串”或“名称”或“输入”)。

最后,使用现代IDE进行开发,这将产生显示代码改进的警告。例如,IntelliJ将显示stack.size() != 0等同于!stack.isEmpty(),以及可以是switchif语句,以及更多的简化。

票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/226782

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档