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


318

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

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

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


7
나는 최근에 당신이 그것을 확인할 수있는 아파치 라이센스하에 발표 된 exp4j라는 수학 표현식 파서를 작성했습니다 : objecthunter.net/exp4j
fasseg

2
어떤 종류의 표현을 허용합니까? 단일 연산자 표현식 만? 괄호가 허용됩니까?
Raedwald



3
이것이 어떻게 가능한가? Dijkstra의 평가는 확실한 해결책입니다. en.wikipedia.org/wiki/Shunting-yard_algorithm
Martin Spamer

답변:


376

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));
    } 
}

52
큰 문제가있는 것 같습니다. 식을 평가하지 않고 스크립트를 실행합니다. 명확하게하기 위해 engine.eval ( "8; 40 + 2"), 출력 42! 구문을 확인하는 표현식 파서를 원한다면 방금 하나를 완성했습니다 (필요에 맞는 것을 찾지 못했기 때문에) : Javaluator .
Jean-Marc Astesana

4
참고로, 코드의 다른 곳에서이 표현식의 결과를 사용해야하는 경우 결과를 Double로 타입 캐스트 할 수 있습니다. return (Double) engine.eval(foo);
Ben Visness

38
보안 참고 사항 : 사용자 입력이있는 서버 컨텍스트에서이를 사용해서는 안됩니다. 실행 된 JavaScript는 모든 Java 클래스에 액세스 할 수 있으므로 애플리케이션을 제한없이 가로 챌 수 있습니다.
Boann September

3
@Boann, 나는 당신이 저에게 당신이 한 말에 대한 참조를 제공하기 위해 요청 (반드시 100 %로).
partho

17
@partho- new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");기본적으로 프로그램의 현재 디렉토리에 JavaScript를 통해 파일을
씁니다

236

나는 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하여 실제 프로그래밍 언어와 마찬가지로 식에 여러 데이터 유형을 혼합하여 사용할 수 있습니다. :)


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


1
멋진 알고리즘, 그것부터 시작하여 묵시적 논리 연산자를 관리했습니다. 함수를 평가하기 위해 함수에 대해 별도의 클래스를 만들었으므로 변수에 대한 아이디어와 같이 함수가 포함 된 맵을 만들고 함수 이름을 확인합니다. 모든 함수는 메소드 평가 (T rightOperator, T leftOperator)로 인터페이스를 구현하므로 언제든지 알고리즘 코드를 변경하지 않고 기능을 추가 할 수 있습니다. 그리고 제네릭 형식에서 작동하도록하는 것이 좋습니다. 감사합니다!
Vasile Bors

1
이 알고리즘의 논리를 설명 할 수 있습니까?
iYonatan

1
Boann이 작성한 코드에서 이해하는 내용과 wiki에 설명 된 예제를 설명하려고합니다.이 알고리즘의 논리는 작업 순서 규칙에서 시작합니다. 1. 운영자 서명 | 변수 평가 | 함수 호출 | 괄호 (부 표현식); 2. 지수화; 3. 곱셈, 나눗셈; 4. 더하기, 빼기;
Vasile Bors

1
알고리즘 방법은 다음과 같이 각 작업 순서 레벨에 따라 나뉩니다. parseFactor = 1. operator sign | 변수 평가 | 함수 호출 | 괄호 (부 표현식); 2. 지수화; parseTerms = 3. 곱셈, 나눗셈; parseExpression = 4. 더하기, 빼기. 알고리즘은 역순으로 메소드를 호출 (parseExpression-> parseTerms-> parseFactor-> parseExpression (하위 표현식))하지만 첫 번째 행의 모든 ​​메소드는 다음 레벨로 메소드를 호출하므로 전체 실행 순서 메소드는 다음과 같습니다. 실제로 정상적인 작업 순서입니다.
Vasile Bors

1
예를 들어 parseExpression 메소드는 실제 오더 레벨 (더하기, 빼기)의 성공적인 오퍼레이션을 평가 double x = parseTerm(); 한 후 왼쪽 연산자를 평가합니다 for (;;) {...}. 동일한 논리가 parseTerm 메소드에 있습니다. parseFactor에는 다음 레벨이 없으므로 메소드 / 변수의 평가 만 있거나 마비의 경우 하위 표현식을 평가합니다. 이 boolean eat(int charToEat)메소드는 charToEat 문자와 현재 커서 문자의 동등성을 확인하고, true를 리턴하고 커서를 다음 문자로 이동하면 이름에 'accept'를 사용합니다.
Vasile Bors

34

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

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


26

대학 프로젝트에서 기본 수식과 더 복잡한 수식 (특히 반복 연산자)을 모두 지원하는 파서 / 평가자를 찾고있었습니다. 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 을 다운로드 할 수 있습니다 .

친애하는


지금까지 이것은 최고의 수학 라이브러리입니다. 킥 스타트가 간단하고 사용하기 쉽고 확장 가능합니다. 확실히 최고의 답변이어야합니다.
Trynkiewicz Mariusz

여기에서 Maven 버전을 찾으 십시오 .
izogfif

mXparser가 잘못된 수식을 식별 할 수 없다는 것을 발견했습니다. 예를 들어 '0/0'은 '0'으로 결과를 얻습니다. 이 문제를 어떻게 해결할 수 있습니까?
lulijun

그냥 솔루션, expression.setSlientMode () 발견
lulijun

20

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

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


이것은 괜찮지 만 5 또는 10의 배수 값을 곱하려고하면 실패합니다. 예를 들어 65 * 6의 결과는 3.9E + 2입니다.
paarth batra

나누었다 지금 390됩니다 65 * 6 즉 INT 출력을 int로이를 전송하여이 문제를 해결하는 방법 = (INT)가
paarth 트라

1
명확히하기 위해, 이것은 라이브러리의 문제가 아니라 부동 소수점 값으로 숫자를 표시하는 데 문제가 있습니다.
DavidBittner

이 라이브러리는 정말 좋습니다. @paarth batra int로 캐스팅하면 모든 소수점이 제거됩니다. 대신 이것을 사용하십시오 : expression.eval (). toPlainString ();
einUsername

15

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

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

1
Adnroid Studio에서 BeanShell을 사용하는 방법을 알려주십시오.
한니

1
Hanni-이 게시물을 통해
androidstudio

14

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");

5
SQL 인젝션에 인사하십시오!
cyberz

DB를 사용하는 대상에 따라 다릅니다. 확실하게하려면 특히 수학 평가를 위해 빈 sqlite DB를 쉽게 만들 수 있습니다.
DAB

4
@cyberz 위의 예제를 사용하면 Sqlite는 메모리에 임시 DB를 만듭니다. 참조 stackoverflow.com/questions/849679/...
DAB

11

이 기사 에서는 다양한 접근 방식에 대해 설명합니다. 이 기사에서 언급 한 두 가지 주요 접근 방식은 다음과 같습니다.

Apache의 JEXL

Java 객체에 대한 참조를 포함하는 스크립트를 허용합니다.

// Create or retrieve a JexlEngine
JexlEngine jexl = new JexlEngine();
// Create an expression object
String jexlExp = "foo.innerFoo.bar()";
Expression e = jexl.createExpression( jexlExp );

// Create a context and add data
JexlContext jctx = new MapContext();
jctx.set("foo", new Foo() );

// Now evaluate the expression, getting the result
Object o = e.evaluate(jctx);

JDK에 포함 된 자바 스크립트 엔진을 사용하십시오.

private static void jsEvalWithVariable()
{
    List<String> namesList = new ArrayList<String>();
    namesList.add("Jill");
    namesList.add("Bob");
    namesList.add("Laureen");
    namesList.add("Ed");

    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");

    jsEngine.put("namesListKey", namesList);
    System.out.println("Executing in script environment...");
    try
    {
      jsEngine.eval("var x;" +
                    "var names = namesListKey.toArray();" +
                    "for(x in names) {" +
                    "  println(names[x]);" +
                    "}" +
                    "namesListKey.add(\"Dana\");");
    }
    catch (ScriptException ex)
    {
        ex.printStackTrace();
    }
}

4
링크가 끊어진 경우 기사의 정보를 요약하십시오.
DJClayworth

기사의 관련 부분을 포함하도록 답변을 업그레이드했습니다.
Brad Parks

1
실제로 JEXL은 느리고 (빈의 내부 검사 사용) 멀티 스레딩 (글로벌 캐시)과 관련된 성능 문제가 있습니다
Nishi

@Nishi를 알게되어 반갑습니다! -내 유스 케이스는 실제 환경에서 디버깅하는 것이었지만 일반적인 배포 앱의 일부는 아닙니다.
Brad Parks

10

또 다른 방법은 수학 표현 평가와 함께 훨씬 더 많은 기능을 수행하는 Spring Expression Language 또는 SpEL을 사용하는 것이므로 약간 과잉 일 수 있습니다. 이 표현식 라이브러리를 독립형으로 사용하기 위해 Spring 프레임 워크를 사용할 필요는 없습니다. SpEL 문서에서 예제 복사 :

ExpressionParser parser = new SpelExpressionParser();
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); //24.0

더 간결한 SpEL 예제를 여기에서 읽고 전체 문서를 여기 에서 읽으 십시오


8

구현하려는 경우 아래 알고리즘을 사용할 수 있습니다.

  1. 읽을 토큰이 여전히 있지만,

    1.1 다음 토큰을 얻으십시오. 1.2 토큰이 다음과 같은 경우 :

    1.2.1 숫자 : 값 스택으로 밀어 넣습니다.

    1.2.2 변수 : 값을 가져 와서 값 스택으로 푸시합니다.

    1.2.3 왼쪽 괄호 : 연산자 스택으로 밀어 넣습니다.

    1.2.4 오른쪽 괄호 :

     1 While the thing on top of the operator stack is not a 
       left parenthesis,
         1 Pop the operator from the operator stack.
         2 Pop the value stack twice, getting two operands.
         3 Apply the operator to the operands, in the correct order.
         4 Push the result onto the value stack.
     2 Pop the left parenthesis from the operator stack, and discard it.

    1.2.5 연산자 (이것이라고 부르십시오) :

     1 While the operator stack is not empty, and the top thing on the
       operator stack has the same or greater precedence as thisOp,
       1 Pop the operator from the operator stack.
       2 Pop the value stack twice, getting two operands.
       3 Apply the operator to the operands, in the correct order.
       4 Push the result onto the value stack.
     2 Push thisOp onto the operator stack.
  2. 운영자 스택이 비어 있지 않은 경우 1 운영자 스택에서 운영자를 팝합니다. 2 값 스택을 두 번 팝하여 두 피연산자를 얻습니다. 3 피연산자에 올바른 순서로 연산자를 적용하십시오. 4 결과를 값 스택으로 밉니다.

  3. 이 시점에서 연산자 스택은 비어 있어야하며 값 스택에는 값이 하나만 있어야합니다. 이것이 최종 결과입니다.


3
이것은 Dijkstra Shunting-yard 알고리즘의 무자비한 표현 입니다. 크레딧이 필요한 신용.
Lorne의 후작



4

나는 당신이 이것을하는 방법이 많은 조건문을 포함 할 것이라고 생각합니다. 그러나 예제에서와 같은 단일 작업의 경우 다음과 같은 경우가있는 if 문을 4 개로 제한 할 수 있습니다

String math = "1+4";

if (math.split("+").length == 2) {
    //do calculation
} else if (math.split("-").length == 2) {
    //do calculation
} ...

"4 + 5 * 6"과 같은 여러 작업을 처리하려는 경우 훨씬 더 복잡해집니다.

계산기를 만들려고하면 계산의 각 섹션을 단일 문자열이 아닌 개별적으로 (각 숫자 또는 연산자) 전달하는 것이 가장 좋습니다.


2
여러 연산, 연산자 우선 순위, 괄호 등을 처리 해야하는 즉시 실제로 산술 표현을 특징 짓는 모든 것이 훨씬 복잡해집니다. 이 기술부터 시작할 수 없습니다.
Lorne의 후작

4

대답하기에는 너무 늦었지만 Java에서 표현식을 평가하기 위해 동일한 상황을 겪었습니다.

MVEL표현식의 런타임 평가를 수행하면 Java 코드를 작성하여 String평가할 수 있습니다.

    String expressionStr = "x+y";
    Map<String, Object> vars = new HashMap<String, Object>();
    vars.put("x", 10);
    vars.put("y", 20);
    ExecutableStatement statement = (ExecutableStatement) MVEL.compileExpression(expressionStr);
    Object result = MVEL.executeExpression(statement, vars);


대박! 그것은 나의 하루를 구했다. 감사합니다
Sarika.S

4

Symja 프레임 워크를 살펴볼 수 있습니다 .

ExprEvaluator util = new ExprEvaluator(); 
IExpr result = util.evaluate("10-40");
System.out.println(result.toString()); // -> "-30" 

확실히 더 복잡한 표현을 평가할 수 있습니다.

// D(...) gives the derivative of the function Sin(x)*Cos(x)
IAST function = D(Times(Sin(x), Cos(x)), x);
IExpr result = util.evaluate(function);
// print: Cos(x)^2-Sin(x)^2

4

코드 삽입 처리와 함께 JDK1.6의 Javascript 엔진을 사용하여 다음 샘플 코드를 사용해보십시오.

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

public class EvalUtil {
private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
public static void main(String[] args) {
    try {
        System.out.println((new EvalUtil()).eval("(((5+5)/2) > 5) || 5 >3 "));
        System.out.println((new EvalUtil()).eval("(((5+5)/2) > 5) || true"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public Object eval(String input) throws Exception{
    try {
        if(input.matches(".*[a-zA-Z;~`#$_{}\\[\\]:\\\\;\"',\\.\\?]+.*")) {
            throw new Exception("Invalid expression : " + input );
        }
        return engine.eval(input);
    } catch (Exception e) {
        e.printStackTrace();
        throw e;
    }
 }
}

4

이것은 실제로 @Boann의 답변을 보완합니다. 약간의 버그가있어서 "-2 ^ 2"가 -4.0의 잘못된 결과를 낳습니다. 그 문제는 지수가 지수에서 평가되는 시점입니다. 지수를 parseTerm () 블록으로 옮기면 괜찮을 것입니다. @Boann의 답변이 약간 수정 된 아래를 살펴보십시오 . 수정 사항은 의견에 있습니다.

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 if (eat('^')) x = Math.pow(x, parseFactor()); //exponentiation -> Moved in to here. So the problem is fixed
                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 -> This is causing a bit of problem

            return x;
        }
    }.parse();
}

-2^2 = -4실제로는 정상이며 버그가 아닙니다. 처럼 그룹화됩니다 -(2^2). 예를 들어 Desmos에서 사용해보십시오. 실제로 코드에는 몇 가지 버그가 있습니다. 첫 번째는 ^더 이상 오른쪽에서 왼쪽으로 그룹화하지 않는 것입니다. 다시 말해서, 오른쪽 연관 이기 때문에 2^3^2같은 것으로 그룹화되어야 하지만 수정하면 그룹처럼 됩니다. 두 번째는 and 보다 우선 순위가 높지만 수정 내용이 동일하게 취급된다는 것입니다. ideone.com/iN2mMa를 참조하십시오 . 2^(3^2)^(2^3)^2^*/
Radiodef

그래서, 당신이 제안하는 것은 지수가 아닌 곳에서 지수가 더 잘 유지된다는 것입니다.
Romeo Sierra

예, 그것이 제가 제안하는 것입니다.
Radiodef

4
package ExpressionCalculator.expressioncalculator;

import java.text.DecimalFormat;
import java.util.Scanner;

public class ExpressionCalculator {

private static String addSpaces(String exp){

    //Add space padding to operands.
    //https://regex101.com/r/sJ9gM7/73
    exp = exp.replaceAll("(?<=[0-9()])[\\/]", " / ");
    exp = exp.replaceAll("(?<=[0-9()])[\\^]", " ^ ");
    exp = exp.replaceAll("(?<=[0-9()])[\\*]", " * ");
    exp = exp.replaceAll("(?<=[0-9()])[+]", " + "); 
    exp = exp.replaceAll("(?<=[0-9()])[-]", " - ");

    //Keep replacing double spaces with single spaces until your string is properly formatted
    /*while(exp.indexOf("  ") != -1){
        exp = exp.replace("  ", " ");
     }*/
    exp = exp.replaceAll(" {2,}", " ");

       return exp;
}

public static Double evaluate(String expr){

    DecimalFormat df = new DecimalFormat("#.####");

    //Format the expression properly before performing operations
    String expression = addSpaces(expr);

    try {
        //We will evaluate using rule BDMAS, i.e. brackets, division, power, multiplication, addition and
        //subtraction will be processed in following order
        int indexClose = expression.indexOf(")");
        int indexOpen = -1;
        if (indexClose != -1) {
            String substring = expression.substring(0, indexClose);
            indexOpen = substring.lastIndexOf("(");
            substring = substring.substring(indexOpen + 1).trim();
            if(indexOpen != -1 && indexClose != -1) {
                Double result = evaluate(substring);
                expression = expression.substring(0, indexOpen).trim() + " " + result + " " + expression.substring(indexClose + 1).trim();
                return evaluate(expression.trim());
            }
        }

        String operation = "";
        if(expression.indexOf(" / ") != -1){
            operation = "/";
        }else if(expression.indexOf(" ^ ") != -1){
            operation = "^";
        } else if(expression.indexOf(" * ") != -1){
            operation = "*";
        } else if(expression.indexOf(" + ") != -1){
            operation = "+";
        } else if(expression.indexOf(" - ") != -1){ //Avoid negative numbers
            operation = "-";
        } else{
            return Double.parseDouble(expression);
        }

        int index = expression.indexOf(operation);
        if(index != -1){
            indexOpen = expression.lastIndexOf(" ", index - 2);
            indexOpen = (indexOpen == -1)?0:indexOpen;
            indexClose = expression.indexOf(" ", index + 2);
            indexClose = (indexClose == -1)?expression.length():indexClose;
            if(indexOpen != -1 && indexClose != -1) {
                Double lhs = Double.parseDouble(expression.substring(indexOpen, index));
                Double rhs = Double.parseDouble(expression.substring(index + 2, indexClose));
                Double result = null;
                switch (operation){
                    case "/":
                        //Prevent divide by 0 exception.
                        if(rhs == 0){
                            return null;
                        }
                        result = lhs / rhs;
                        break;
                    case "^":
                        result = Math.pow(lhs, rhs);
                        break;
                    case "*":
                        result = lhs * rhs;
                        break;
                    case "-":
                        result = lhs - rhs;
                        break;
                    case "+":
                        result = lhs + rhs;
                        break;
                    default:
                        break;
                }
                if(indexClose == expression.length()){
                    expression = expression.substring(0, indexOpen) + " " + result + " " + expression.substring(indexClose);
                }else{
                    expression = expression.substring(0, indexOpen) + " " + result + " " + expression.substring(indexClose + 1);
                }
                return Double.valueOf(df.format(evaluate(expression.trim())));
            }
        }
    }catch(Exception exp){
        exp.printStackTrace();
    }
    return 0.0;
}

public static void main(String args[]){

    Scanner scanner = new Scanner(System.in);
    System.out.print("Enter an Mathematical Expression to Evaluate: ");
    String input = scanner.nextLine();
    System.out.println(evaluate(input));
}

}


1
연산자 우선 순위 나 여러 연산자 또는 괄호를 처리하지 않습니다. 사용하지 마세요.
Lorne의 후작

2

이런 식으로 어떻습니까 :

String st = "10+3";
int result;
for(int i=0;i<st.length();i++)
{
  if(st.charAt(i)=='+')
  {
    result=Integer.parseInt(st.substring(0, i))+Integer.parseInt(st.substring(i+1, st.length()));
    System.out.print(result);
  }         
}

그에 따라 다른 모든 수학적 연산자에 대해서도 유사한 작업을 수행합니다.


9
효율적인 수학 식 파서를 작성하는 방법에 대해 읽어야합니다. 컴퓨터 과학 방법론이 있습니다. 예를 들어 ANTLR을 살펴보십시오. 당신이 작성한 것에 대해 잘 생각하면 (a + b / -c) * (e / f)와 같은 것들이 당신의 아이디어와 작동하지 않거나 코드가 더럽고 비효율적이라는 것을 알 수 있습니다.
Daniel Nuriyev


2

또 다른 옵션 : https://github.com/stefanhaustein/expressionparser

간단하지만 유연한 옵션으로 두 가지를 모두 허용하도록 이것을 구현했습니다.

위에 링크 된 TreeBuilder 는 기호 파생을 수행 하는 CAS 데모 패키지의 일부입니다 . BASIC 인터프리터 예제 도 있으며 이를 사용하여 TypeScript 인터프리터 를 빌드하기 시작 했습니다.


2

수학 표현식을 평가할 수있는 Java 클래스 :

package test;

public class Calculator {

    public static Double calculate(String expression){
        if (expression == null || expression.length() == 0) {
            return null;
        }
        return calc(expression.replace(" ", ""));
    }
    public static Double calc(String expression) {

        if (expression.startsWith("(") && expression.endsWith(")")) {
            return calc(expression.substring(1, expression.length() - 1));
        }
        String[] containerArr = new String[]{expression};
        double leftVal = getNextOperand(containerArr);
        expression = containerArr[0];
        if (expression.length() == 0) {
            return leftVal;
        }
        char operator = expression.charAt(0);
        expression = expression.substring(1);

        while (operator == '*' || operator == '/') {
            containerArr[0] = expression;
            double rightVal = getNextOperand(containerArr);
            expression = containerArr[0];
            if (operator == '*') {
                leftVal = leftVal * rightVal;
            } else {
                leftVal = leftVal / rightVal;
            }
            if (expression.length() > 0) {
                operator = expression.charAt(0);
                expression = expression.substring(1);
            } else {
                return leftVal;
            }
        }
        if (operator == '+') {
            return leftVal + calc(expression);
        } else {
            return leftVal - calc(expression);
        }

    }

    private static double getNextOperand(String[] exp){
        double res;
        if (exp[0].startsWith("(")) {
            int open = 1;
            int i = 1;
            while (open != 0) {
                if (exp[0].charAt(i) == '(') {
                    open++;
                } else if (exp[0].charAt(i) == ')') {
                    open--;
                }
                i++;
            }
            res = calc(exp[0].substring(1, i - 1));
            exp[0] = exp[0].substring(i);
        } else {
            int i = 1;
            if (exp[0].charAt(0) == '-') {
                i++;
            }
            while (exp[0].length() > i && isNumber((int) exp[0].charAt(i))) {
                i++;
            }
            res = Double.parseDouble(exp[0].substring(0, i));
            exp[0] = exp[0].substring(i);
        }
        return res;
    }


    private static boolean isNumber(int c) {
        int zero = (int) '0';
        int nine = (int) '9';
        return (c >= zero && c <= nine) || c =='.';
    }

    public static void main(String[] args) {
        System.out.println(calculate("(((( -6 )))) * 9 * -1"));
        System.out.println(calc("(-5.2+-5*-5*((5/4+2)))"));

    }

}

2
연산자 우선 순위를 올바르게 처리하지 않습니다. 이 작업을 수행하는 표준 방법이 있으며 이는 그중 하나가 아닙니다.
Lorne의 후작

EJP, 운영자 우선 순위에 문제가있는 부분을 알려 주시겠습니까? 나는 그것이 표준 방법이 아니라는 사실에 전적으로 동의합니다. 이전 게시물에서 이미 언급 된 표준 방법은 다른 방법으로 표시하는 것이 었습니다.
Efi G

2

RHINO 또는 NASHORN과 같은 외부 라이브러리를 사용하여 자바 스크립트를 실행할 수 있습니다. 그리고 자바 스크립트는 문자열을 파싱하지 않고 간단한 수식을 평가할 수 있습니다. 코드를 잘 작성해도 성능에 영향을 미치지 않습니다. 아래는 RHINO의 예입니다-

public class RhinoApp {
    private String simpleAdd = "(12+13+2-2)*2+(12+13+2-2)*2";

public void runJavaScript() {
    Context jsCx = Context.enter();
    Context.getCurrentContext().setOptimizationLevel(-1);
    ScriptableObject scope = jsCx.initStandardObjects();
    Object result = jsCx.evaluateString(scope, simpleAdd , "formula", 0, null);
    Context.exit();
    System.out.println(result);
}

2
import java.util.*;

public class check { 
   int ans;
   String str="7 + 5";
   StringTokenizer st=new StringTokenizer(str);

   int v1=Integer.parseInt(st.nextToken());
   String op=st.nextToken();
   int v2=Integer.parseInt(st.nextToken());

   if(op.equals("+")) { ans= v1 + v2; }
   if(op.equals("-")) { ans= v1 - v2; }
   //.........
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.