Java 如何解析计算器输入字符串

Java 如何解析计算器输入字符串,java,string,parsing,math,Java,String,Parsing,Math,我正在编写一个计算器,当用户点击enter键时,我需要它找到任何有括号的地方,例如 然后我首先需要计算器来解决里面的数学问题 获取括号内函数并将第二个字符串的值设置为括号内的值的最佳方法是什么 你有一些不同的方法来解决这个问题 定义全局变量,当用户按下其中一个操作数(+、-、*/)时,将其保存在定义变量中 拆分字符串,如果将整个输入视为一个字符串,可以使用定义的操作数(+、-、*/)之一拆分该字符串,那么就有了一个运算符和操作数数组 使用正则表达式,您可以使用正则表达式找出哪些运算符和操作数 您

我正在编写一个计算器,当用户点击enter键时,我需要它找到任何有括号的地方,例如

然后我首先需要计算器来解决里面的数学问题


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

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

  • 定义全局变量,当用户按下其中一个操作数(+、-、*/)时,将其保存在定义变量中
  • 拆分字符串,如果将整个输入视为一个字符串,可以使用定义的操作数(+、-、*/)之一拆分该字符串,那么就有了一个运算符和操作数数组
  • 使用正则表达式,您可以使用正则表达式找出哪些运算符和操作数
  • 您可以使用堆栈:

  • 将字符推到堆栈上直到
  • 然后从堆栈中弹出子方程,直到出现
  • 将步骤2中的方程式结果推送到堆栈上
  • 重复上述步骤,直到找不到括号,然后求解最终方程

  • 我喜欢前两张海报的创意,因此我的方法如下:

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

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


    3) 然后,当您处于最高级别(例如指数)时,您可以解决所有这些问题。然后转到较低级别的运算符。

    作为替代,您可以让Java自己编译。它仅限于简单的操作,如
    +-/*

    完整工作示例:

    import java.lang.*;
    导入java.io.*;
    公开课{
    公共静态void main(字符串[]args){
    试试{System.out.println(calculate(“5*(1+1)”);}
    catch(异常e){System.out.println(“我们尝试了”+e);}
    }
    私有静态字符串子程序(字符串命令)引发异常{
    Process proc=Runtime.getRuntime().exec(命令);//启动子进程
    BufferedReader标准输出=新的BufferedReader(新的
    InputStreamReader(proc.getInputStream());//从标准输出读取
    StringBuilder sb=新建StringBuilder();//生成输出
    String ln=stdout.readLine();//读取行,直到结束
    而(ln!=null){sb.append(ln);ln=stdout.readLine();}
    proc.waitFor();//等待进程退出
    int exitCode=proc.exitValue();//获取退出代码
    if(exitCode!=0)//如果它不是0,则表示发生了错误。抛出错误
    抛出新异常(“数学无效!已退出,代码为:+exitCode”);
    return sb.toString();//return stdout
    }
    私有静态字符串计算(字符串数学)引发异常{
    //**编译一个新的Java类,它将抛出计算结果
    File File=File.createTempFile(“tmp”,“.java”);//创建新的临时文件
    字符串classpath=file.getParent(),//获取类路径和名称
    classname=file.getName().substring(0,file.getName().length()-5);
    PrintWriter=新的PrintWriter(文件);//将Java写入临时文件
    writer.println(
    “公共类”+类名+“{”+
    “公共静态void main(字符串[]args){”+
    System.out.println(“+math+”;}}”);writer.close();
    子程序(“javac”+file.getAbsolutePath());//编译它
    file.delete();//删除源文件
    返回子程序(“java-cp”+classpath+“”+classname);//运行它
    }
    }
    
    像往常一样编译和运行:

    javac SO.java; java SO
    
    在这个特定的示例中,它将打印出
    10
    ,因为调用是
    calculate(“5*(1+1)”)


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

    这里是一个递归下降解析器,用于上的语法子集,它似乎与您的案例相关

    我没有包括标记器,但是编写一个适合接口的标记器应该相当简单。该代码与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();
      }
    

    听起来你可能会对抽象语法树感兴趣。也许这会有帮助……但是,你离开了最后一步,对吗?处理完所有括号后,方程的顺序将相反。而且,也不要在意操作顺序:我投票给你的答案是因为我喜欢堆栈方法,但它并不完整。@user919860这只取决于实现。如何将推送和弹出操作的结果追加到创建表达式字符串中,并不是我真正关心的问题。运算的顺序取决于OPs的实现来求解方程。OP只是问如何处理括号为什么变量必须是全局的?我喜欢这样,否则它只提供简单的操作
    +-/*
    Wow。谈论用大锤杀死跳蚤。