Làm thế nào để đánh giá một biểu thức toán học được đưa ra dưới dạng chuỗi?


318

Tôi đang cố gắng viết một thói quen Java để đánh giá các biểu thức toán học đơn giản từ Stringcác giá trị như:

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

Tôi muốn tránh rất nhiều câu lệnh if-then-other. Tôi có thể làm cái này như thế nào?


7
Gần đây tôi đã viết một trình phân tích cú pháp biểu thức toán học gọi là exp4j được phát hành theo giấy phép apache, bạn có thể kiểm tra nó ở đây: objecthunter.net/exp4j
fasseg

2
Những loại biểu thức nào bạn cho phép? Chỉ có biểu thức toán tử đơn? Được phép ngoặc đơn?
Raedwald


1
Bản sao có thể có của hàm Có một hàm eval () trong Java không?
Andrew Li

3
Làm thế nào điều này có thể được coi là quá rộng? Đánh giá của Dijkstra là giải pháp rõ ràng ở đây en.wikipedia.org/wiki/Shunting-yard_alacticm
Martin Spamer

Câu trả lời:


376

Với JDK1.6, bạn có thể sử dụng công cụ Javascript tích hợp.

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
Dường như có một vấn đề lớn ở đó; Nó thực thi một tập lệnh, không đánh giá một biểu thức. Để rõ ràng, engine.eval ("8; 40 + 2"), xuất ra 42! Nếu bạn muốn một trình phân tích cú pháp biểu thức cũng kiểm tra cú pháp, tôi vừa hoàn thành một (vì tôi không tìm thấy gì phù hợp với nhu cầu của mình): Javaluator .
Jean-Marc Astesana

4
Lưu ý phụ, nếu bạn cần sử dụng kết quả của biểu thức này ở nơi khác trong mã của mình, bạn có thể đánh máy kết quả thành Double like như vậy: return (Double) engine.eval(foo);
Ben Visness

38
Lưu ý bảo mật: Bạn không bao giờ nên sử dụng điều này trong ngữ cảnh máy chủ với đầu vào của người dùng. JavaScript được thực thi có thể truy cập tất cả các lớp Java và do đó chiếm quyền điều khiển ứng dụng của bạn mà không giới hạn.
Boann

3
@Boann, tôi yêu cầu bạn cho tôi một tài liệu tham khảo về những gì bạn nói. (Để chắc chắn 100%)
partho 27/2/2016

17
@partho new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");- sẽ ghi một tệp qua JavaScript vào (theo mặc định) thư mục hiện tại của chương trình
Boann

236

Tôi đã viết evalphương pháp này cho các biểu thức số học để trả lời câu hỏi này. Nó không cộng, trừ, nhân, chia, lũy thừa (sử dụng ^ký hiệu) và một vài chức năng cơ bản như sqrt. Nó hỗ trợ nhóm bằng cách sử dụng (... ), và nó được các nhà điều hành ưu tiên và quy tắc kết hợp chính xác.

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

Thí dụ:

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

Đầu ra: 7.5 (đúng)


Trình phân tích cú pháp là một trình phân tích cú pháp gốc đệ quy , do đó, bên trong sử dụng các phương thức phân tích cú pháp riêng biệt cho từng cấp độ ưu tiên của toán tử trong ngữ pháp của nó. Tôi giữ nó ngắn gọn để dễ sửa đổi, nhưng đây là một số ý tưởng bạn có thể muốn mở rộng nó với:

  • Biến:

    Có thể dễ dàng thay đổi bit của trình phân tích cú pháp đọc tên cho các hàm để xử lý các biến tùy chỉnh, bằng cách tìm kiếm các tên trong bảng biến được truyền cho evalphương thức, chẳng hạn như a Map<String,Double> variables.

  • Biên soạn và đánh giá riêng biệt:

    Điều gì sẽ xảy ra nếu, khi thêm hỗ trợ cho các biến, bạn muốn đánh giá cùng một biểu thức hàng triệu lần với các biến đã thay đổi mà không cần phân tích cú pháp mỗi lần? Điều đó là có thể. Trước tiên, xác định một giao diện sẽ sử dụng để đánh giá biểu thức được biên dịch trước:

    @FunctionalInterface
    interface Expression {
        double eval();
    }

    Bây giờ thay đổi tất cả các phương thức trả về doubles, vì vậy thay vào đó chúng trả về một thể hiện của giao diện đó. Cú pháp lambda của Java 8 hoạt động rất tốt cho việc này. Ví dụ về một trong những phương thức đã thay đổi:

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

    Điều đó xây dựng một cây đệ quy của Expressioncác đối tượng đại diện cho biểu thức được biên dịch (một cây cú pháp trừu tượng ). Sau đó, bạn có thể biên dịch nó một lần và đánh giá nó nhiều lần với các giá trị khác nhau:

    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());
        }
    }
  • Các kiểu dữ liệu khác nhau:

    Thay vì double, bạn có thể thay đổi người đánh giá để sử dụng một cái gì đó mạnh hơn như BigDecimal, hoặc một lớp thực hiện các số phức hoặc số hữu tỷ (phân số). Bạn thậm chí có thể sử dụng Object, cho phép kết hợp một số kiểu dữ liệu trong các biểu thức, giống như một ngôn ngữ lập trình thực sự. :)


Tất cả các mã trong câu trả lời này được phát hành cho miền công cộng . Chúc vui vẻ!


1
Thuật toán đẹp, bắt đầu từ nó tôi quản lý để triển khai và vận hành logic. Chúng tôi đã tạo các lớp riêng biệt cho các hàm để đánh giá một hàm, vì vậy giống như ý tưởng của bạn về các biến, tôi tạo một bản đồ với các hàm và chăm sóc tên hàm. Mỗi hàm thực hiện một giao diện với một eval phương thức (T rightOperator, T leftOperator), vì vậy bất cứ lúc nào chúng ta có thể thêm các tính năng mà không thay đổi mã thuật toán. Và đó là một ý tưởng tốt để làm cho nó hoạt động với các loại chung chung. Cảm ơn bạn!
Vasile Bors

1
Bạn có thể giải thích logic đằng sau thuật toán này?
iYonatan

1
Tôi cố gắng đưa ra một mô tả về những gì tôi hiểu từ mã được viết bởi Boann và các ví dụ mô tả wiki. Logic của algoritm này bắt đầu từ các quy tắc của các đơn đặt hàng hoạt động. 1. ký hiệu nhà điều hành | đánh giá biến | chức năng gọi | dấu ngoặc đơn (biểu thức phụ); 2. lũy thừa; 3. nhân, chia; 4. phép cộng, phép trừ;
Vasile Bors

1
Các phương thức thuật toán được chia cho từng cấp bậc của lệnh hoạt động như sau: parseFactor = 1. ký hiệu toán tử | đánh giá biến | chức năng gọi | dấu ngoặc đơn (biểu thức phụ); 2. lũy thừa; parseTerms = 3. nhân, chia; parseExpression = 4. cộng, trừ. Thuật toán, gọi các phương thức theo thứ tự ngược lại (parseExpression -> parseTerms -> parseFactor -> parseExpression (đối với các biểu thức con)), nhưng mọi phương thức cho dòng đầu tiên đều gọi phương thức đến cấp độ tiếp theo, vì vậy toàn bộ các phương thức thực hiện sẽ là thứ tự thực sự bình thường của hoạt động.
Vasile Bors

1
Ví dụ, phương pháp parseExpression double x = parseTerm(); đánh giá toán tử bên trái, sau khi for (;;) {...}đánh giá các hoạt động thành công của mức đặt hàng thực tế (cộng, trừ). Logic tương tự là và trong phương pháp parseTerm. ParseFactor không có cấp độ tiếp theo, do đó, chỉ có các đánh giá về các phương thức / biến hoặc trong trường hợp parantulation - đánh giá biểu thức con. Các boolean eat(int charToEat)bình đẳng phương pháp kiểm tra của nhân vật con trỏ hiện tại với nhân vật charToEat, nếu trở lại bằng đúng và di chuyển con trỏ đến ký tự tiếp theo, tôi sử dụng tên 'chấp nhận' cho điều đó.
Vasile Bors

34

Cách chính xác để giải quyết vấn đề này là với một lexer và một trình phân tích cú pháp . Bạn có thể tự viết các phiên bản đơn giản của chúng hoặc các trang đó cũng có liên kết đến các trình phân tích và trình phân tích cú pháp Java.

Tạo một trình phân tích cú pháp gốc đệ quy là một bài tập học tập thực sự tốt.


26

Đối với dự án đại học của tôi, tôi đã tìm kiếm một trình phân tích cú pháp / đánh giá hỗ trợ cả các công thức cơ bản và các phương trình phức tạp hơn (đặc biệt là các toán tử lặp). Tôi tìm thấy thư viện mã nguồn mở rất đẹp cho JAVA và .NET được gọi là mXparser. Tôi sẽ đưa ra một vài ví dụ để tạo cảm giác về cú pháp, để được hướng dẫn thêm, vui lòng truy cập trang web của dự án (đặc biệt là phần hướng dẫn).

https://mathparser.org/

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

https://mathparser.org/api/

Và một vài ví dụ

1 - Furmula đơn giản

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

2 - Các đối số và hằng số do người dùng định nghĩa

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 - Hàm do người dùng xác định

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 - Lặp lại

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

Được tìm thấy gần đây - trong trường hợp bạn muốn thử cú pháp (và xem trường hợp sử dụng nâng cao), bạn có thể tải xuống ứng dụng Máy tính vô hướng được cung cấp bởi mXparser.

Trân trọng


Cho đến nay đây là thư viện toán học tốt nhất hiện có; đơn giản để khởi động, dễ sử dụng và có thể mở rộng. Chắc chắn nên được trả lời hàng đầu.
Trynkiewicz Mariusz

Tìm phiên bản Maven tại đây .
izogfif

Tôi thấy mXparser không thể xác định công thức bất hợp pháp, ví dụ: '0/0' sẽ nhận được kết quả là '0'. Làm thế nào tôi có thể giải quyết vấn đề này
lulijun

Chỉ cần tìm giải pháp, biểu
thức.setSlientMode

20

ĐÂY là một thư viện mã nguồn mở khác trên GitHub có tên EvalEx.

Không giống như công cụ JavaScript, thư viện này chỉ tập trung vào đánh giá các biểu thức toán học. Hơn nữa, thư viện có thể mở rộng và hỗ trợ sử dụng các toán tử boolean cũng như dấu ngoặc đơn.


Điều này không sao, nhưng thất bại khi chúng tôi cố gắng nhân các giá trị của bội số 5 hoặc 10, ví dụ 65 * 6 kết quả trong 3.9E + 2 ...
paarth batra

.Nhưng có một cách để khắc phục điều này bằng cách chuyển nó thành int tức là int output = (int) 65 * 6, kết quả bây giờ là 390
paarth batra

1
Để làm rõ, đó không phải là vấn đề của thư viện mà là vấn đề với việc biểu diễn các số dưới dạng giá trị dấu phẩy động.
DavidBittner

Thư viện này thực sự tốt @paarth batra Casting to int sẽ xóa tất cả các dấu thập phân. Sử dụng cái này thay thế: biểu hiện.eval (). ToPlainString ();
Tên người dùng

15

Bạn cũng có thể thử trình thông dịch BeanShell :

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

1
Bạn có thể vui lòng cho tôi biết cách sử dụng BeanShell trong adnroid Studio không.
Hanni

1
Hanni - bài đăng này có thể giúp bạn thêm BeanShell vào dự án androidstudio của bạn: stackoverflow.com/questions/18520875/ chủ
marciowerner 8/2/2016

14

Bạn có thể dễ dàng đánh giá các biểu thức nếu ứng dụng Java của bạn đã truy cập cơ sở dữ liệu mà không cần sử dụng bất kỳ JAR nào khác.

Một số cơ sở dữ liệu yêu cầu bạn sử dụng bảng giả (ví dụ: bảng "kép" của Oracle) và các cơ sở dữ liệu khác sẽ cho phép bạn đánh giá các biểu thức mà không "chọn" từ bất kỳ bảng nào.

Ví dụ: trong Sql Server hoặc Sqlite

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

và trong Oracle

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

Ưu điểm của việc sử dụng DB là bạn có thể đánh giá nhiều biểu thức cùng một lúc. Ngoài ra, hầu hết các DB sẽ cho phép bạn sử dụng các biểu thức rất phức tạp và cũng sẽ có một số hàm bổ sung có thể được gọi là cần thiết.

Tuy nhiên, hiệu suất có thể bị ảnh hưởng nếu nhiều biểu thức đơn lẻ cần được đánh giá riêng lẻ, đặc biệt khi DB được đặt trên máy chủ mạng.

Phần sau đây giải quyết vấn đề hiệu năng ở một mức độ nào đó, bằng cách sử dụng cơ sở dữ liệu trong bộ nhớ Sqlite.

Đây là một ví dụ hoạt động đầy đủ trong 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();

Tất nhiên bạn có thể mở rộng mã trên để xử lý nhiều phép tính cùng một lúc.

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

5
Nói xin chào với SQL tiêm!
cyberz

Nó phụ thuộc vào những gì bạn sử dụng DB cho. Nếu bạn muốn chắc chắn, bạn có thể dễ dàng tạo một DB sqlite trống, đặc biệt cho việc đánh giá toán học.
DAB

4
@cyberz Nếu bạn sử dụng ví dụ của tôi ở trên, Sqlite sẽ tạo DB tạm thời trong bộ nhớ. Xem stackoverflow.com/questions/849679/ trên
DAB

11

Bài viết này thảo luận về các phương pháp khác nhau. Dưới đây là 2 cách tiếp cận chính được đề cập trong bài viết:

JEXL từ Apache

Cho phép các tập lệnh bao gồm các tham chiếu đến các đối tượng 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);

Sử dụng công cụ javascript được nhúng trong 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
Hãy tóm tắt thông tin từ bài viết, trong trường hợp liên kết đến nó bị hỏng.
DJClayworth

Tôi đã nâng cấp câu trả lời để bao gồm các bit có liên quan từ bài viết
Brad

1
Trong thực tế, JEXL hoạt động chậm (sử dụng nội tâm của các loại đậu), có vấn đề về hiệu suất với đa luồng (bộ đệm chung)
Nishi

Thật tốt khi biết @Nishi! - Trường hợp sử dụng của tôi là để gỡ lỗi mọi thứ trong môi trường trực tiếp, nhưng không phải là một phần của ứng dụng được triển khai bình thường.
Brad park

10

Một cách khác là sử dụng Spring Expression Language hoặc SpEL, điều này thực hiện nhiều hơn cùng với việc đánh giá các biểu thức toán học do đó có thể hơi quá mức cần thiết. Bạn không phải sử dụng Spring framework để sử dụng thư viện biểu thức này vì nó là độc lập. Sao chép các ví dụ từ tài liệu của 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

Đọc thêm các ví dụ SpEL súc tích ở đây và các tài liệu đầy đủ ở đây


8

nếu chúng ta sẽ thực hiện nó thì chúng ta có thể sử dụng thuật toán dưới đây: -

  1. Trong khi vẫn còn các mã thông báo để đọc,

    1.1 Nhận mã thông báo tiếp theo. 1.2 Nếu mã thông báo là:

    1.2.1 Một số: đẩy nó lên ngăn xếp giá trị.

    1.2.2 Một biến: lấy giá trị của nó và đẩy lên ngăn xếp giá trị.

    1.2.3 Dấu ngoặc đơn bên trái: đẩy nó vào ngăn xếp toán tử.

    1.2.4 Một dấu ngoặc đơn đúng:

     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 Một toán tử (gọi nó là thisOp):

     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. Trong khi ngăn xếp toán tử không trống, 1 Pop toán tử từ ngăn xếp toán tử. 2 Pop ngăn xếp giá trị hai lần, nhận hai toán hạng. 3 Áp dụng toán tử cho toán hạng, theo đúng thứ tự. 4 Đẩy kết quả lên ngăn xếp giá trị.

  3. Tại thời điểm này, ngăn xếp toán tử sẽ trống và ngăn xếp giá trị chỉ có một giá trị trong đó, đó là kết quả cuối cùng.


3
Đây là một giải trình chưa được công nhận của thuật toán Dijkstra Shunting-yard . Tín dụng có tín dụng đến hạn.
Hầu tước Lorne



4

Tôi nghĩ rằng bất cứ cách nào bạn làm điều này sẽ liên quan đến rất nhiều tuyên bố có điều kiện. Nhưng đối với các hoạt động đơn lẻ như trong ví dụ của bạn, bạn có thể giới hạn ở mức 4 nếu các câu lệnh có nội dung như

String math = "1+4";

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

Nó phức tạp hơn nhiều khi bạn muốn xử lý nhiều thao tác như "4 + 5 * 6".

Nếu bạn đang cố gắng xây dựng một máy tính thì tôi sẽ vượt qua từng phần của phép tính một cách riêng biệt (mỗi số hoặc toán tử) thay vì dưới dạng một chuỗi.


2
Nó phức tạp hơn nhiều ngay khi bạn phải xử lý nhiều thao tác, quyền ưu tiên của toán tử, dấu ngoặc đơn, ... trong thực tế, bất cứ điều gì đặc trưng cho biểu thức số học thực sự. Bạn không thể đến đó bắt đầu từ kỹ thuật này.
Hầu tước Lorne

4

Quá muộn để trả lời nhưng tôi đã gặp tình huống tương tự để đánh giá biểu thức trong java, nó có thể giúp ai đó

MVELkhông đánh giá thời gian chạy của các biểu thức, chúng ta có thể viết mã java Stringđể đánh giá nó trong này.

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

Tôi đã quét và tìm thấy một số hàm Số học bổ sung cũng được xử lý ở đây github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/
trộm

Tuyệt vời! Nó đã cứu ngày của tôi. Cảm ơn
Sarika.S

4

Bạn có thể có một cái nhìn vào khung Symja :

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

Lưu ý rằng các biểu thức phức tạp hơn chắc chắn có thể được đánh giá:

// 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

Hãy thử mã mẫu sau bằng cách sử dụng công cụ Javascript của JDK1.6 với xử lý tiêm mã.

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

Điều này thực sự bổ sung cho câu trả lời được đưa ra bởi @Boann. Nó có một lỗi nhỏ khiến "-2 ^ 2" đưa ra kết quả sai lệch là -4.0. Vấn đề cho điều đó là điểm tại đó lũy thừa được đánh giá theo giá trị của nó. Chỉ cần di chuyển số mũ sang khối parseTerm (), và bạn sẽ ổn thôi. Hãy xem bên dưới, đây là câu trả lời của @ Boann được sửa đổi đôi chút. Sửa đổi là trong các ý kiến.

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 = -4là thực sự bình thường, và không phải là một lỗi. Nó được nhóm như thế nào -(2^2). Hãy thử nó trên Desmos, ví dụ. Mã của bạn thực sự giới thiệu một số lỗi. Đầu tiên là ^không còn các nhóm từ phải sang trái. Nói cách khác, 2^3^2được cho là nhóm thích 2^(3^2)^là liên kết đúng, nhưng sửa đổi của bạn làm cho nhóm thích (2^3)^2. Thứ hai là được cho ^là có quyền ưu tiên cao hơn */, nhưng các sửa đổi của bạn đối xử với nó như nhau. Xem ideone.com/iN2mMa .
Radiodef

Vì vậy, những gì bạn đang đề nghị là lũy thừa được giữ tốt hơn ở nơi không phải là nó?
Romeo Sierra

Vâng, đó là những gì tôi đề nghị.
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
Không xử lý quyền ưu tiên của toán tử hoặc một số toán tử hoặc dấu ngoặc đơn. Không được dùng.
Hầu tước Lorne

2

Còn những thứ như thế này thì sao:

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

và làm điều tương tự cho mọi toán tử toán học khác cho phù hợp ..


9
Bạn nên đọc về cách viết trình phân tích cú pháp biểu thức toán hiệu quả. Có một phương pháp khoa học máy tính cho nó. Hãy xem ANTLR, ví dụ. Nếu bạn nghĩ tốt về những gì bạn đã viết, bạn sẽ thấy rằng những thứ như (a + b / -c) * (e / f) sẽ không hoạt động với ý tưởng của bạn hoặc mã sẽ siêu bẩn và không hiệu quả.
Daniel Nuriyev


2

Còn một lựa chọn khác: https://github.com/stefanhaustein/expressionparser

Tôi đã thực hiện điều này để có một tùy chọn đơn giản nhưng linh hoạt để cho phép cả hai:

TreeBuilder được liên kết ở trên là một phần của gói demo CAS thực hiện dẫn xuất tượng trưng. Ngoài ra còn có một ví dụ về trình thông dịch BASIC và tôi đã bắt đầu xây dựng trình thông dịch TypeScript bằng cách sử dụng nó.


2

Một lớp Java có thể đánh giá các biểu thức toán học:

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
Không xử lý quyền ưu tiên của nhà điều hành một cách chính xác. Có những cách tiêu chuẩn để làm điều này, và đây không phải là một trong số đó.
Hầu tước Lorne

EJP, bạn có thể vui lòng chỉ ra nơi có vấn đề với quyền ưu tiên của nhà điều hành không? Tôi hoàn toàn đồng ý về thực tế rằng đó không phải là cách tiêu chuẩn để làm điều đó. những cách tiêu chuẩn đã được đề cập trong các bài viết trước, ý tưởng là chỉ ra một cách khác để làm điều đó.
Efi G

2

Thư viện bên ngoài như RHINO hoặc NASHORN có thể được sử dụng để chạy javascript. Và javascript có thể đánh giá công thức đơn giản mà không cần ghép chuỗi. Không có tác động hiệu suất là tốt nếu mã được viết tốt. Dưới đây là một ví dụ với 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; }
   //.........
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.