[java] 문자열 형식으로 주어진 수학 표현식을 평가하는 방법은 무엇입니까?

다음 String과 같은 값 에서 간단한 수학 표현식을 평가하기 위해 Java 루틴을 작성하려고합니다 .

  1. "5+3"
  2. "10-40"
  3. "10*3"

나는 많은 if-then-else 진술을 피하고 싶습니다. 어떻게해야합니까?



답변

JDK1.6에서는 내장 Javascript 엔진을 사용할 수 있습니다.

import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

public class Test {
  public static void main(String[] args) throws ScriptException {
    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine engine = mgr.getEngineByName("JavaScript");
    String foo = "40+2";
    System.out.println(engine.eval(foo));
    }
}


답변

나는 eval이 질문에 답하기 위해 산술 표현식을 위해이 방법을 작성했습니다 . 더하기, 빼기, 곱하기, 나누기, 지수 ( ^기호 사용) 및과 같은 몇 가지 기본 기능을 수행 sqrt합니다. (…를 사용한 그룹화를 지원 )하며 연산자 우선 순위연관성 규칙을 올바르게 가져옵니다 .

public static double eval(final String str) {
    return new Object() {
        int pos = -1, ch;

        void nextChar() {
            ch = (++pos < str.length()) ? str.charAt(pos) : -1;
        }

        boolean eat(int charToEat) {
            while (ch == ' ') nextChar();
            if (ch == charToEat) {
                nextChar();
                return true;
            }
            return false;
        }

        double parse() {
            nextChar();
            double x = parseExpression();
            if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
            return x;
        }

        // Grammar:
        // expression = term | expression `+` term | expression `-` term
        // term = factor | term `*` factor | term `/` factor
        // factor = `+` factor | `-` factor | `(` expression `)`
        //        | number | functionName factor | factor `^` factor

        double parseExpression() {
            double x = parseTerm();
            for (;;) {
                if      (eat('+')) x += parseTerm(); // addition
                else if (eat('-')) x -= parseTerm(); // subtraction
                else return x;
            }
        }

        double parseTerm() {
            double x = parseFactor();
            for (;;) {
                if      (eat('*')) x *= parseFactor(); // multiplication
                else if (eat('/')) x /= parseFactor(); // division
                else return x;
            }
        }

        double parseFactor() {
            if (eat('+')) return parseFactor(); // unary plus
            if (eat('-')) return -parseFactor(); // unary minus

            double x;
            int startPos = this.pos;
            if (eat('(')) { // parentheses
                x = parseExpression();
                eat(')');
            } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
                while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
                x = Double.parseDouble(str.substring(startPos, this.pos));
            } else if (ch >= 'a' && ch <= 'z') { // functions
                while (ch >= 'a' && ch <= 'z') nextChar();
                String func = str.substring(startPos, this.pos);
                x = parseFactor();
                if (func.equals("sqrt")) x = Math.sqrt(x);
                else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
                else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
                else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
                else throw new RuntimeException("Unknown function: " + func);
            } else {
                throw new RuntimeException("Unexpected: " + (char)ch);
            }

            if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation

            return x;
        }
    }.parse();
}

예:

System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2"));

출력 : 7.5 (정확한)


파서는 재귀 하강 파서 이므로 내부적으로 문법의 연산자 우선 순위 수준마다 별도의 구문 분석 방법을 사용합니다. 수정하기 쉽도록 짧게 유지 했지만 다음과 같이 확장하려는 아이디어가 있습니다.

  • 변수:

    함수의 이름을 읽는 파서의 비트는 a eval와 같은 메소드에 전달 된 변수 테이블에서 이름을 찾아서 사용자 정의 변수를 처리하도록 쉽게 변경할 수 있습니다 Map<String,Double> variables.

  • 별도의 편집 및 평가 :

    변수에 대한 지원을 추가 한 후 매번 구문 분석하지 않고 변경된 변수로 동일한 표현식을 수백만 번 평가하려면 어떻게해야합니까? 있을 수있다. 먼저 사전 컴파일 된 표현식을 평가하는 데 사용할 인터페이스를 정의하십시오.

    @FunctionalInterface
    interface Expression {
        double eval();
    }

    이제 doubles 를 반환하는 모든 메서드를 변경 하여 대신 해당 인터페이스의 인스턴스를 반환합니다. Java 8의 람다 구문은이 작업에 효과적입니다. 변경된 방법 중 하나의 예 :

    Expression parseExpression() {
        Expression x = parseTerm();
        for (;;) {
            if (eat('+')) { // addition
                Expression a = x, b = parseTerm();
                x = (() -> a.eval() + b.eval());
            } else if (eat('-')) { // subtraction
                Expression a = x, b = parseTerm();
                x = (() -> a.eval() - b.eval());
            } else {
                return x;
            }
        }
    }

    Expression컴파일 된 표현식 ( 추상 구문 트리 )을 나타내는 객체 의 재귀 트리를 만듭니다 . 그런 다음 한 번 컴파일하고 다른 값으로 반복해서 평가할 수 있습니다.

    public static void main(String[] args) {
        Map<String,Double> variables = new HashMap<>();
        Expression exp = parse("x^2 - x + 2", variables);
        for (double x = -20; x <= +20; x++) {
            variables.put("x", x);
            System.out.println(x + " => " + exp.eval());
        }
    }
  • 다른 데이터 유형 :

    대신 double,보다 강력한 것을 사용 BigDecimal하거나 복소수 또는 유리수 (분수)를 구현하는 클래스 를 사용하도록 평가자를 변경할 수 있습니다 . 을 사용 Object하여 실제 프로그래밍 언어와 마찬가지로 식에 여러 데이터 유형을 혼합하여 사용할 수 있습니다. 🙂


이 답변의 모든 코드 는 공개 도메인에 릴리스되었습니다 . 즐기세요!


답변

이것을 해결하는 올바른 방법은 렉서파서를 사용하는 것 입니다. 간단한 버전을 직접 작성하거나 해당 페이지에도 Java 렉서 및 구문 분석기에 대한 링크가 있습니다.

재귀 강하 파서를 만드는 것은 정말 좋은 학습 연습입니다.


답변

대학 프로젝트에서 기본 수식과 더 복잡한 수식 (특히 반복 연산자)을 모두 지원하는 파서 / 평가자를 찾고있었습니다. mXparser라는 JAVA 및 .NET에 대한 매우 훌륭한 오픈 소스 라이브러리를 찾았습니다. 구문에 대한 느낌을주는 몇 가지 예를 제공 할 것입니다. 자세한 지침은 프로젝트 웹 사이트 (특히 자습서 섹션)를 방문하십시오.

https://mathparser.org/

https://mathparser.org/mxparser-tutorial/

https://mathparser.org/api/

그리고 몇 가지 예

1-단순 furmula

Expression e = new Expression("( 2 + 3/4 + sin(pi) )/2");
double v = e.calculate()

2-사용자 정의 인수 및 상수

Argument x = new Argument("x = 10");
Constant a = new Constant("a = pi^2");
Expression e = new Expression("cos(a*x)", x, a);
double v = e.calculate()

3-사용자 정의 함수

Function f = new Function("f(x, y, z) = sin(x) + cos(y*z)");
Expression e = new Expression("f(3,2,5)", f);
double v = e.calculate()

4-반복

Expression e = new Expression("sum( i, 1, 100, sin(i) )");
double v = e.calculate()

최근에 발견 – 구문을 사용하려는 경우 (고급 사용 사례 참조) mXparser가 제공하는 Scalar Calculator 을 다운로드 할 수 있습니다 .

친애하는


답변

여기 EitEx라는 GitHub의 다른 오픈 소스 라이브러리입니다.

JavaScript 엔진과 달리이 라이브러리는 수학 표현식 만 평가하는 데 중점을 둡니다. 또한 라이브러리는 확장 가능하며 부울 연산자와 괄호 사용을 지원합니다.


답변

BeanShell 인터프리터를 사용해 볼 수도 있습니다 .

Interpreter interpreter = new Interpreter();
interpreter.eval("result = (7+21*6)/(32-27)");
System.out.println(interpreter.get("result"));


답변

Java 응용 프로그램이 다른 JAR을 사용하지 않고 이미 데이터베이스에 액세스하는 경우 표현식을 쉽게 평가할 수 있습니다.

일부 데이터베이스는 더미 테이블 (예 : Oracle의 “이중”테이블)을 사용해야하며 다른 데이터베이스에서는 테이블에서 “선택”하지 않고 식을 평가할 수 있습니다.

예를 들어 Sql Server 또는 Sqlite에서

select (((12.10 +12.0))/ 233.0) amount

오라클에서

select (((12.10 +12.0))/ 233.0) amount from dual;

DB를 사용하면 여러 식을 동시에 평가할 수 있다는 장점이 있습니다. 또한 대부분의 DB를 사용하면 매우 복잡한 표현식을 사용할 수 있으며 필요에 따라 호출 할 수있는 여러 가지 추가 함수가 있습니다.

그러나 많은 단일 표현식을 개별적으로 평가해야하는 경우, 특히 DB가 네트워크 서버에있는 경우 성능이 저하 될 수 있습니다.

다음은 Sqlite 인 메모리 데이터베이스를 사용하여 성능 문제를 어느 정도 해결합니다.

다음은 Java의 전체 작동 예입니다.

Class. forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount");
rs.next();
System.out.println(rs.getBigDecimal(1));
stat.close();
conn.close();

물론 여러 계산을 동시에 처리하기 위해 위의 코드를 확장 할 수 있습니다.

ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount, (1+100)/20.0 amount2");