다음 String
과 같은 값 에서 간단한 수학 표현식을 평가하기 위해 Java 루틴을 작성하려고합니다 .
"5+3"
"10-40"
"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(); }
이제
double
s 를 반환하는 모든 메서드를 변경 하여 대신 해당 인터페이스의 인스턴스를 반환합니다. 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/mxparser-tutorial/
그리고 몇 가지 예
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");