【问题标题】:How to parse a calculator input string如何解析计算器输入字符串
【发布时间】:2013-10-03 01:38:43
【问题描述】:

我正在写一个计算器,当用户按下回车键时,我需要它来查找任何有括号的地方。

然后我需要计算器先解决其中的数学问题。

获取括号内的函数并将第二个String 的值设置为括号内的值的最佳方法是什么?

【问题讨论】:

标签: java string parsing math


【解决方案1】:

您可以使用堆栈:

  1. 将字符推入堆栈直到)
  2. 然后让你的子方程从堆栈中弹出直到(
  3. 将方程的结果从步骤 2 推入堆栈
  4. 重复直到找不到括号,然后求解最终方程

【讨论】:

  • 但是,你要离开最后一步了,对吧?处理完所有括号后,等式的顺序将颠倒。而且,也不要介意操作的顺序。 :S。我投票给你的答案是因为我喜欢堆栈方法,但它并不完整。
  • @user919860 这仅取决于实现。如何将 push 和 pop 操作的结果附加到创建方程字符串并不是我真正关心的问题。操作顺序取决于 OPs 的实现来求解方程。 OP 只是询问如何处理括号
【解决方案2】:

作为替代方案,您可以让 Java 自行编译。仅限于+ - / *等简单操作

完整的工作示例:

import java.lang.*;
import java.io.*;

public class SO {
  public static void main(String[] args) {
    try { System.out.println(calculate("5 * (1 + 1)")); }
    catch (Exception e) { System.out.println("we tried " + e); }
  }

  private static String subProc(String command) throws Exception {
    Process proc = Runtime.getRuntime().exec(command); // kick off sub process
    BufferedReader stdout = new BufferedReader(new
      InputStreamReader(proc.getInputStream())); // read from stdout
    StringBuilder sb = new StringBuilder(); // build output
    String ln = stdout.readLine(); // read lines, until we are at the end
    while (ln != null) { sb.append(ln); ln = stdout.readLine(); }
    proc.waitFor(); // wait for process to exit
    int exitCode = proc.exitValue(); // get exit code
    if (exitCode != 0) // if it isn't 0, something went wrong. Throw error
      throw new Exception("invalid math! exited with code: " + exitCode);
    return sb.toString(); // return stdout
  }

  private static String calculate(String math) throws Exception {
    //** Compile a new Java class that will spit out the calculation
    File file = File.createTempFile("tmp", ".java"); // create new temp file
    String classpath = file.getParent(), // get class path, and name
    classname = file.getName().substring(0, file.getName().length() - 5);
    PrintWriter writer = new PrintWriter(file); // write Java to temp file
    writer.println(
      "public class " + classname + "{" +
        "public static void main(String[] args) { " +
          "System.out.println(" + math + "); }}"); writer.close();
    subProc("javac " + file.getAbsolutePath()); // compile it
    file.delete(); // remove our source file
    return subProc("java -cp " + classpath + " " + classname); // run it
  }
}

照常编译运行:

javac SO.java; java SO

在这个特定的例子中,它会打印出10,因为调用是calculate("5 * (1 + 1)"));

从速度上看,这不是解决问题的实用方法,但它是一个纯 Java 解决方案,我对此很感兴趣。

【讨论】:

  • 我喜欢这个,否则只提供简单的操作+ - / *
  • 哇。谈论使用大锤杀死跳蚤。
【解决方案3】:

你有一些不同的方法来解决这个问题,

  1. 定义一个全局变量,当用户按下其中一个操作数(+、-、*/)时,您将其保存在定义变量中。
  2. 拆分字符串,如果您将整个输入视为一个字符串,您可以将其与定义的操作数之一(+、-、*/)拆分,然后您将拥有一组运算符和操作数。
  3. 使用正则表达式,您可以使用正则表达式找出哪些运算符和操作数。

【讨论】:

  • 为什么变量必须是全局的?
【解决方案4】:

我喜欢前两张海报的想法,所以这是我的方法:

1) 将字符放入堆栈。当您看到) 时,您会将成员从堆栈中弹出,直到您看到(。完成后,弹出所有字符并将它们以相反的顺序放入另一个 String 或 StringBuffer 或 StringBuilder 中。

2) 按相反的优先级拆分等式的其余部分。然后对于每个更高级别的运算符优先级,您按该运算符拆分子方程。

3) 然后,当您处于最高级别(例如指数)时,您就可以解决所有这些问题。然后进入较低级别的运营商。

【讨论】:

    【解决方案5】:

    这是一个递归下降解析器,用于 Wikipedia page on recursive descent parsers 上似乎与您的情况相关的语法子集。

    我没有包含分词器,但编写一个适合界面的分词器应该相当简单。该代码与 Wikipedia 页面上的代码没有显着差异,只是只实现了语法的一个子集,并且它实际上执行了计算。

    /**
     * From http://en.wikipedia.org/wiki/Recursive_descent_parser
     *
     * expression =
     *     [ "+" | "-" ] term { ("+" | "-") term } .
     *
     * term =
     *     factor { ( "*" | "/" ) factor } .
     *
     * factor =
     *     number
     *     | "(" expression ")" .
     */
    public class Arithmetic
    {
      private final TokenStream tokenStream;
      private TokenStream.Token currentToken;
      private double currentValue;
    
      public Arithmetic(TokenStream tokenStream) {
        this.tokenStream = tokenStream;
      }
    
      public double parse() {
        nextToken();
        return expression();
      }
    
      private double expression() {
        double lhs = 0.0;
        if (accept(TokenStream.Token.MINUS)) {
          lhs = -term();
        } else {
          // Optional unary plus swallowed
          accept(TokenStream.Token.PLUS);
          lhs = term();
        }
        for (boolean moreTerms = true; moreTerms; ) {
          if (accept(TokenStream.Token.PLUS)) {
            lhs += term();
          } else if (accept(TokenStream.Token.MINUS)) {
            lhs -= term();
          } else {
            moreTerms = false;
          }
        }
        return lhs;
      }
    
      private double term() {
        double lhs = factor();
        for (boolean moreFactors = true; moreFactors; ) {
          if (accept(TokenStream.Token.TIMES)) {
            lhs *= factor();
          } else if (accept(TokenStream.Token.DIVIDED_BY)) {
            lhs /= factor();
          } else {
            moreFactors = false;
          }
        }
        return lhs;
      }
    
      private double factor() {
        if (peek(TokenStream.Token.NUMBER)) {
          // Save currentValue before calling nextToken()
          double value = currentValue;
          nextToken();
          return value;
        }
        require(TokenStream.Token.OPEN);
        double value = expression();
        require(TokenStream.Token.CLOSE);
        return value;
      }
    
      private void nextToken() {
        currentToken = tokenStream.nextToken();
        if (currentToken == TokenStream.Token.NUMBER) {
          currentValue = tokenStream.getValue();
        }
      }
    
      private boolean peek(TokenStream.Token token) {
        return (currentToken == token);
      }
    
      private boolean accept(TokenStream.Token token) {
        if (peek(token)) {
          nextToken();
          return true;
        }
        return false;
      }
    
      private void require(TokenStream.Token token) {
        if (currentToken == token) {
          nextToken();
        } else {
          throw new IllegalStateException("Unexpected token " + currentToken);
        }
      }
    
    }
    

    TokenStream 的界面非常简单:

    public interface TokenStream {
      public enum Token {
        // Terminals
        PLUS,
        MINUS,
        TIMES,
        DIVIDED_BY,
        OPEN,
        CLOSE,
        NUMBER,
        EOF
      }
    
      Token nextToken();
    
      // Retrieve value when nextToken() returns NUMBER
      double getValue();
    }
    

    另一种可能性是使用脚本工具来评估一些 JavaScript,如下所示:

      try
      {
        ScriptEngineManager factory = new ScriptEngineManager();
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        Object result = engine.eval("1 + 2 / 2");
        System.out.println(result.getClass().getCanonicalName());
        System.out.println(result);
      }
      catch (ScriptException e)
      {
        e.printStackTrace();
      }
    

    【讨论】:

      猜你喜欢
      • 2016-11-03
      • 1970-01-01
      • 2012-11-30
      • 2013-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多