วิธีการประเมินการแสดงออกทางคณิตศาสตร์ที่ได้รับในรูปแบบสตริง?


318

ฉันพยายามเขียนรูทีน Java เพื่อประเมินนิพจน์ทางคณิตศาสตร์อย่างง่ายจากStringค่าเช่น:

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

ฉันต้องการหลีกเลี่ยงคำสั่ง if-then-else จำนวนมาก ฉันจะทำสิ่งนี้ได้อย่างไร


7
ฉันเพิ่งเขียนตัวแยกวิเคราะห์นิพจน์ทางคณิตศาสตร์ชื่อ exp4j ที่เผยแพร่ภายใต้สิทธิ์ใช้งาน apache ที่คุณสามารถตรวจสอบได้ที่นี่: objecthunter.net/exp4j
fasseg

2
คุณอนุญาตให้มีการแสดงออกประเภทใด เฉพาะผู้ประกอบการนิพจน์เท่านั้น วงเล็บอนุญาตหรือไม่
Raedwald


1
มีความซ้ำซ้อนของฟังก์ชัน eval () ใน Java หรือไม่?
Andrew Li

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! หากคุณต้องการการแสดงออก parser ที่ยังตรวจสอบไวยากรณ์ที่ฉันเพิ่งเสร็จแล้วหนึ่ง (เพราะผมพบว่าไม่มีอะไรที่เหมาะสมกับความต้องการของฉัน): Javaluator
Jean-Marc Astesana

4
เป็นหมายเหตุด้านข้างถ้าคุณต้องการใช้ผลลัพธ์ของนิพจน์นี้ที่อื่นในรหัสของคุณคุณสามารถพิมพ์ผลลัพธ์ไปที่ Double เช่น: return (Double) engine.eval(foo);
Ben Visness

38
หมายเหตุด้านความปลอดภัย: คุณไม่ควรใช้สิ่งนี้ในบริบทเซิร์ฟเวอร์ด้วยอินพุตของผู้ใช้ JavaScript ที่ดำเนินการสามารถเข้าถึงคลาส Java ทั้งหมดและทำให้แอปพลิเคชันของคุณถูกขโมยโดยไม่ จำกัด
Boann

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 ไปยังไดเรกทอรีปัจจุบันของโปรแกรม (โดยค่าเริ่มต้น)
Boann

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 (ซึ่งถูกต้อง)


parser เป็นตัวแยกวิเคราะห์สืบเชื้อสายซ้ำดังนั้นภายในใช้วิธีการแยกวิเคราะห์แยกต่างหากสำหรับแต่ละระดับของความสำคัญของผู้ประกอบการในไวยากรณ์ของมัน ฉันทำให้มันสั้นดังนั้นจึงง่ายต่อการปรับเปลี่ยน แต่นี่คือแนวคิดบางอย่างที่คุณอาจต้องการขยายด้วย:

  • ตัวแปร:

    บิตของ parser ที่อ่านชื่อสำหรับฟังก์ชั่นสามารถจะเปลี่ยนไปเป็นตัวแปรที่กำหนดเองจับเกินไปโดยการมองขึ้นชื่อในตารางตัวแปรส่งผ่านไปยังวิธีการเช่นevalMap<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
อัลกอริทึมที่ดีเริ่มต้นจากนั้นฉันจัดการเพื่อ impliment และตัวดำเนินการเชิงตรรกะ เราสร้างคลาสแยกต่างหากสำหรับฟังก์ชั่นเพื่อประเมินฟังก์ชั่นดังนั้นเช่นความคิดของตัวแปรฉันสร้างแผนที่พร้อมฟังก์ชั่นและดูแลชื่อฟังก์ชั่น ทุกฟังก์ชั่นใช้ส่วนต่อประสานที่มีเมธอด eval (T rightOperator, T leftOperator) ดังนั้นทุกครั้งที่เราสามารถเพิ่มคุณสมบัติได้โดยไม่ต้องเปลี่ยนรหัสอัลกอริทึม และเป็นความคิดที่ดีที่จะทำให้มันใช้งานได้กับประเภททั่วไป ขอบคุณ!
Vasile Bors

1
คุณช่วยอธิบายตรรกะเบื้องหลังอัลกอริทึมนี้ได้ไหม?
iYonatan

1
ฉันพยายามอธิบายสิ่งที่ฉันเข้าใจจากโค้ดที่เขียนโดย Boann และตัวอย่างที่อธิบายถึงวิกิตรรกะของ algoritm นี้เริ่มต้นจากกฎของคำสั่งการดำเนินการ 1. เครื่องหมายผู้ประกอบการ | การประเมินตัวแปร ฟังก์ชั่นการโทร | วงเล็บ (นิพจน์ย่อย); 2. การยกกำลัง 3. การคูณการหาร 4. นอกจากนี้การลบ;
Vasile Bors

1
วิธีอัลกอริทึมถูกแบ่งออกสำหรับแต่ละระดับของการดำเนินการตามลำดับดังนี้: parseFactor = 1 เครื่องหมายผู้ประกอบการ | การประเมินตัวแปร ฟังก์ชั่นการโทร | วงเล็บ (นิพจน์ย่อย); 2. การยกกำลัง parseTerms = 3. การคูณการหาร; parseExpression = 4. นอกจากนี้การลบ อัลกอริทึมวิธีการโทรในลำดับย้อนกลับ (parseExpression -> parseTerms -> parseFactor -> parseExpression (สำหรับการแสดงออกย่อย)) แต่ทุกวิธีในบรรทัดแรกเรียกวิธีการในระดับถัดไปดังนั้นวิธีการดำเนินการทั้งหมดจะเป็น คำสั่งปกติของการดำเนินงานจริง
Vasile Bors

1
ตัวอย่างเช่นวิธีการ parseExpression double x = parseTerm(); ประเมินผู้ประกอบการด้านซ้ายหลังจากนี้for (;;) {...}ประเมินการดำเนินงานที่ประสบความสำเร็จของระดับการสั่งซื้อที่เกิดขึ้นจริง (นอกจากนี้การลบ) ตรรกะเดียวกันและในวิธี parseTerm parseFactor ไม่มีระดับถัดไปดังนั้นจึงมีเพียงการประเมินเมธอด / ตัวแปรหรือในกรณีของ paranthesis - ประเมินการแสดงออกของย่อย boolean eat(int charToEat)วิธีการตรวจสอบความเท่าเทียมกันของตัวละครเคอร์เซอร์ปัจจุบันที่มีตัวละคร charToEat ถ้ากลับมาเท่ากันจริงและย้ายเคอร์เซอร์ไปยังตัวอักษรถัดไปผมใช้ชื่อ 'ยอมรับ' สำหรับมัน
Vasile Bors

34

วิธีที่ถูกต้องในการแก้ปัญหานี้อยู่กับlexerและparser คุณสามารถเขียนเวอร์ชันแบบง่าย ๆ เหล่านี้เองหรือหน้าเหล่านั้นยังมีลิงค์ไปยัง Java lexers และ parsers

การสร้าง parser ที่สืบเชื้อสายแบบเรียกซ้ำเป็นแบบฝึกหัดการเรียนรู้ที่ดีจริงๆ


26

สำหรับโครงการในมหาวิทยาลัยของฉันฉันกำลังมองหา parser / ผู้ประเมินที่สนับสนุนทั้งสูตรพื้นฐานและสมการที่ซับซ้อนมากขึ้น ฉันพบไลบรารี่โอเพนซอร์สที่ดีมากสำหรับ JAVA และ. NET ที่เรียกว่า mXparser ฉันจะให้ตัวอย่างบางอย่างเพื่อให้ความรู้สึกเกี่ยวกับไวยากรณ์สำหรับคำแนะนำเพิ่มเติมโปรดเยี่ยมชมเว็บไซต์โครงการ (โดยเฉพาะอย่างยิ่งส่วนการสอน)

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

พบเมื่อเร็ว ๆ นี้ - ในกรณีที่คุณต้องการลองใช้ไวยากรณ์ (และดูกรณีการใช้งานขั้นสูง) คุณสามารถดาวน์โหลดแอปScalar Calculator ที่ขับเคลื่อนโดย mXparser

ขอแสดงความนับถืออย่างสูง


จนถึงตอนนี้นี่เป็นห้องสมุดคณิตศาสตร์ที่ดีที่สุด ง่ายต่อการเริ่มต้นใช้งานง่ายและขยายได้ แน่นอนควรเป็นคำตอบด้านบน
Trynkiewicz Mariusz

ค้นหารุ่น Maven ที่นี่
izogfif

ฉันพบว่า mXparser ไม่สามารถระบุสูตรที่ผิดกฎหมายตัวอย่างเช่น '0/0' จะได้ผลลัพธ์เป็น '0' ฉันจะแก้ปัญหานี้ได้อย่างไร
lulijun

เพิ่งพบโซลูชัน, expression.setSlientMode ()
lulijun

20

ที่นี่เป็นอีกหนึ่งไลบรารีโอเพนซอร์ซบน GitHub ชื่อ EvalEx

ไลบรารีนี้มีจุดเน้นในการประเมินนิพจน์ทางคณิตศาสตร์ต่างจากเครื่องมือ JavaScript เท่านั้น นอกจากนี้ไลบรารียังขยายได้และสนับสนุนการใช้ตัวดำเนินการบูลีนและวงเล็บ


ไม่เป็นไร แต่ล้มเหลวเมื่อเราพยายามคูณค่าทวีคูณของ 5 หรือ 10 เช่น 65 * 6 ให้ผลลัพธ์เป็น 3.9E + 2 ...
paarth batra

. แต่มีวิธีแก้ไขปัญหานี้โดยการชี้ไปที่ int เช่น int output = (int) 65 * 6 ซึ่งจะส่งผลให้ตอนนี้ 390
paarth batra

1
เพื่อชี้แจงว่าไม่ใช่ปัญหาของห้องสมุด แต่เป็นปัญหาเกี่ยวกับการแสดงตัวเลขเป็นค่าทศนิยม
DavidBittner

ห้องสมุดนี้ดีจริงๆ @ paarth batra แคสติ้งที่ int จะลบจุดทศนิยมทั้งหมด ใช้สิ่งนี้แทน: expression.eval (). toPlainString ();
einUsername

15

คุณสามารถลองใช้BeanShell interpreter:

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

1
คุณกรุณาบอกวิธีการใช้ BeanShell ใน adnroid Studio ได้ไหม
Hanni

1
Hanni - โพสต์นี้อาจช่วยให้คุณเพิ่ม BeanShell ในโครงการ androidstudio ของคุณ: stackoverflow.com/questions/18520875/…
marciowerner

14

คุณสามารถประเมินนิพจน์ได้อย่างง่ายดายหากแอปพลิเคชัน Java ของคุณเข้าถึงฐานข้อมูลอยู่แล้วโดยไม่ต้องใช้ JAR อื่น ๆ

ฐานข้อมูลบางอย่างต้องการให้คุณใช้ตารางจำลอง (เช่นตาราง "คู่" ของ Oracle) และฐานข้อมูลอื่นจะอนุญาตให้คุณประเมินนิพจน์โดยไม่ต้อง "เลือก" จากตารางใด ๆ

ตัวอย่างเช่นใน SQL Server หรือ Sqlite

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

และใน Oracle

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

ข้อดีของการใช้ฐานข้อมูลคือคุณสามารถประเมินหลาย ๆ นิพจน์ได้ในเวลาเดียวกัน นอกจากนี้ฐานข้อมูลส่วนใหญ่ยังอนุญาตให้คุณใช้นิพจน์ที่มีความซับซ้อนสูงและยังมีฟังก์ชั่นพิเศษจำนวนมากที่สามารถเรียกใช้ได้ตามความจำเป็น

อย่างไรก็ตามประสิทธิภาพการทำงานอาจประสบถ้านิพจน์เดียวจำนวนมากจำเป็นต้องประเมินเป็นรายบุคคลโดยเฉพาะอย่างยิ่งเมื่อ 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

ขึ้นอยู่กับสิ่งที่คุณใช้ฐานข้อมูล หากคุณต้องการแน่ใจว่าคุณสามารถสร้างฐานข้อมูล sqlite ที่ว่างเปล่าได้อย่างง่ายดาย
DAB

4
@cyberz หากคุณใช้ตัวอย่างของฉันด้านบน Sqlite จะสร้างฐานข้อมูลชั่วคราวในหน่วยความจำ ดูstackoverflow.com/questions/849679/…
DAB

11

บทความนี้กล่าวถึงวิธีการต่าง ๆ นี่คือวิธีการ 2 วิธีที่กล่าวถึงในบทความ:

JEXL จาก Apache

อนุญาตสำหรับสคริปต์ที่มีการอ้างอิงถึงวัตถุ 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! - กรณีการใช้งานของฉันมีไว้สำหรับการดีบักสิ่งต่าง ๆ ในสภาพแวดล้อมสด แต่ไม่ได้เป็นส่วนหนึ่งของแอปที่ปรับใช้ปกติ
Brad Parks

10

อีกวิธีหนึ่งคือการใช้ Spring Expression Language หรือ SpEL ซึ่งทำงานได้มากขึ้นพร้อมกับการประเมินการแสดงออกทางคณิตศาสตร์ดังนั้นอาจจะเกินความจำเป็นเล็กน้อย คุณไม่จำเป็นต้องใช้ Spring Framework เพื่อใช้ไลบรารีนิพจน์นี้เนื่องจากเป็นแบบสแตนด์อะโลน คัดลอกตัวอย่างจากเอกสารของ 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 ผู้ดำเนินการ (เรียกว่า 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. ในขณะที่โอเปอเรเตอร์สแต็กไม่ว่างเปล่า 1 นำโอเปอเรเตอร์จากสแต็กผู้ประกอบการ 2 เปิดสแต็กมูลค่าสองครั้งเพื่อรับสองตัวถูกดำเนินการ 3 ใช้โอเปอเรเตอร์กับตัวถูกดำเนินการตามลำดับที่ถูกต้อง 4 ดันผลลัพธ์ลงบนสแต็กค่า

  3. ณ จุดนี้สแต็กโอเปอเรเตอร์ควรว่างเปล่าและสแต็กค่าควรมีเพียงหนึ่งค่าเท่านั้นซึ่งเป็นผลลัพธ์สุดท้าย


3
นี่คือการแสดงออก uncredited ของอัลกอริทึม Dijkstra แบ่งหลา เครดิตที่เครดิตครบกำหนด
มาร์ควิสแห่ง Lorne



4

ฉันคิดว่าสิ่งที่คุณทำเช่นนี้มันจะเกี่ยวข้องกับข้อความที่มีเงื่อนไขมากมาย แต่สำหรับการดำเนินงานเดี่ยวอย่างในตัวอย่างของคุณคุณสามารถ จำกัด ไว้ที่ 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ทำการประเมินผลนิพจน์ในรันไทม์เราสามารถเขียนโค้ดจาวา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);

ฉัน scoured และพบฟังก์ชั่นทางคณิตศาสตร์เพิ่มเติมบางอย่างก็จัดการที่นี่github.com/mvel/mvel/mem/blob/master/src/test/java/org/mvel2/tests/ …
thecodefather

Awsome! มันช่วยชีวิตฉันไว้ ขอบคุณ
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

ลองโค้ดตัวอย่างต่อไปนี้โดยใช้เครื่องมือ Javascript ของ JDK1.6 พร้อมการจัดการการฉีดโค้ด

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ที่ควรจะเป็นกลุ่มที่ชอบ2^(3^2)เพราะ^เป็นขวาเชื่อมโยง (2^3)^2แต่การปรับเปลี่ยนของคุณทำให้กลุ่มเช่น อย่างที่สองก็^คือควรจะมีลำดับความสำคัญสูงกว่า*และ/แต่การดัดแปลงของคุณจะเป็นแบบเดียวกัน ดูideone.com/iN2mMa
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
คุณควรอ่านเกี่ยวกับการเขียน parsers นิพจน์ทางคณิตศาสตร์ที่มีประสิทธิภาพ มีวิธีวิทยาการคอมพิวเตอร์เป็น ลองดูที่ ANTLR เช่น หากคุณคิดอย่างดีเกี่ยวกับสิ่งที่คุณเขียนคุณจะเห็นว่าสิ่งต่าง ๆ เช่น (a + b / -c) * (e / f) จะไม่ทำงานกับความคิดของคุณมิฉะนั้นโค้ดจะดูสกปรกและไม่มีประสิทธิภาพ
Daniel Nuriyev

2

มันเป็นไปได้ที่จะแปลงสตริงการแสดงออกใด ๆ ในมัดสัญกรณ์ไป postfix สัญกรณ์ใช้ขั้นตอนวิธีการแบ่งหลา Djikstra ของ ผลลัพธ์ของอัลกอริทึมสามารถใช้เป็นอินพุตไปยังอัลกอริทึม postfixโดยส่งคืนผลลัพธ์ของนิพจน์

ฉันเขียนบทความเกี่ยวกับที่นี่ด้วยการใช้งานใน java


2

อีกตัวเลือกหนึ่ง: https://github.com/stefaninatingein/expressionparser

ฉันใช้สิ่งนี้เพื่อให้มีตัวเลือกที่เรียบง่าย แต่มีความยืดหยุ่นในการอนุญาตทั้งสอง:

TreeBuilder ที่ลิงก์ด้านบนเป็นส่วนหนึ่งของแพ็คเกจการสาธิต CASซึ่งได้รับสัญลักษณ์ นอกจากนี้ยังมีตัวอย่างล่ามพื้นฐานและฉันได้เริ่มสร้างล่าม 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.