Як оцінити математичний вираз, поданий у рядковій формі?


318

Я намагаюся написати процедуру Java для оцінки простих математичних виразів із Stringтаких значень, як:

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

Я хочу уникати багатьох тверджень як тоді. Як я можу це зробити?


7
Нещодавно я написав математичний аналізатор виразів під назвою exp4j, який був випущений за ліцензією apache, ви можете перевірити його тут: objecthunter.net/exp4j
fasseg

2
Які вислови ви дозволяєте? Лише окремі вирази оператора? Чи дозволені дужки?
Raedwald

3
Також зверніть увагу на алгоритм два стека Дейкстри
Ritesh

1
Можливий дублікат Чи є в Java функція eval ()?
Ендрю Лі

3
Як це можна вважати занадто широким? Оцінка Дійкстри - це очевидне рішення тут 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 .
Жан-Марк Астесана

4
Як зауваження, якщо вам потрібно використовувати результат цього виразу в іншому місці коду, ви можете набрати результат у подвійний, як так: return (Double) engine.eval(foo);
Бен Віснес

38
Примітка безпеки: Ви ніколи не повинні використовувати це в контексті сервера з введенням користувача. Виконаний JavaScript може отримати доступ до всіх класів Java і таким чином викрасти вашу програму без обмежень.
Боан

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 (що правильно)


Аналізатор є рекурсивним аналізатором зниження , тому внутрішньо використовує окремі методи розбору для кожного рівня пріоритетності оператора у своїй граматиці. Я це зберігав коротко, тому його легко змінити, але ось декілька ідей, з якими ви можете розширити:

  • Змінні:

    Біт аналізатора, який читає імена для функцій, легко змінюється і для обробки спеціальних змінних, шукаючи імена в таблиці змінних, переданій в eval методу, наприклад a 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
Хороший алгоритм, виходячи з нього мені вдалося втілити і логічних операторів. Ми створили окремі класи для функцій для оцінки функції, так що, як і ваше уявлення про змінні, я створюю карту з функціями та доглядаю за назвою функції. Кожна функція реалізує інтерфейс із методом eval (T rightOperator, T leftOperator), тому в будь-який час ми можемо додавати функції, не змінюючи код алгоритму. І це гарна ідея змусити його працювати з загальними типами. Дякую!
Василь Борс

1
Чи можете ви пояснити логіку цього алгоритму?
iYonatan

1
Я намагаюся дати характеристику того, що я розумію з коду, написаного Боаном, та прикладів, описаних у wiki. 1. знак оператора | змінна оцінка | виклик функції | дужки (під вирази); 2. експоненція; 3. множення, ділення; 4. додавання, віднімання;
Василь Борс

1
Методи алгоритму діляться для кожного рівня порядку операцій так: parseFactor = 1. знак оператора | змінна оцінка | виклик функції | дужки (під вирази); 2. експоненція; parseTerms = 3. множення, ділення; parseExpression = 4. додавання, віднімання. Алгоритм, виклику методів у зворотному порядку (parseExpression -> parseTerms -> parseFactor -> parseExpression (для підвиразів)), але кожен метод до першого рядка викликає метод на наступний рівень, тому всі методи порядку виконання будуть фактично нормальний порядок операцій.
Василь Борс

1
Наприклад, метод parseExpression double x = parseTerm(); оцінює лівий оператор, після цього for (;;) {...}оцінюють послідовні операції фактичного рівня порядку (додавання, віднімання). Така ж логіка є і в методі parseTerm. Аналіз parseFactor не має наступного рівня, тому є лише оцінки методів / змінних або в разі парантезу - оцінюйте суб-вираження. boolean eat(int charToEat)Перевірка методу рівність поточного символу курсора з характером charToEat, якщо одно повернення істинний і переміщує курсор до наступного символу, я використовую ім'я «прийняти» для нього.
Василь Борс

34

Правильний спосіб вирішити це за допомогою лексеру та аналізатора . Ви можете самостійно писати прості версії, або на цих сторінках також є посилання на лексеми та парсери Java.

Створення рекурсивного аналізатора спуску - це дійсно гарна вправа.


26

Для свого університетського проекту я шукав аналізатор / оцінювач, що підтримує як основні формули, так і складніші рівняння (особливо ітераційні оператори). Я знайшов дуже гарну бібліотеку з відкритим кодом для JAVA та .NET під назвою mXparser. Я наведу кілька прикладів, щоб відчути синтаксис, для подальших інструкцій відвідайте веб-сайт проекту (особливо підручник).

https://mathparser.org/

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

https://mathparser.org/api/

І кілька прикладів

1 - Проста фурмула

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.

З повагою


Поки що це найкраща математична бібліотека там; простий у швидкому старті, простий у використанні та розширюваний. Однозначно має бути головна відповідь.
Trynkiewicz Mariusz

Знайдіть версію Maven тут .
izogfif

Я виявив, що mXparser не може визначити незаконну формулу, наприклад, "0/0" отримає результат як "0". Як я можу вирішити цю проблему
lu

Щойно знайшли рішення,
express.setSlientMode

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 видалить усі десяткові крапки. Використовуйте це замість: express.eval (). ToPlainString ();
einUsername

15

Ви також можете спробувати перекладача BeanShell :

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

1
Скажіть, будь ласка, як використовувати BeanShell в студії adnroid.
Ганні

1
Ханни - цей пост може допомогти вам при додаванні BeanShell до вашого androidstudio проекту: stackoverflow.com/questions/18520875 / ...
marciowerner

14

Ви можете легко оцінити вирази, якщо ваша програма Java вже має доступ до бази даних, не використовуючи жодних інших JAR.

У деяких базах даних потрібно використовувати макетну таблицю (наприклад, "подвійну" таблицю Oracle), а інші дозволять оцінити вирази, не "вибираючи" жодної таблиці.

Наприклад, на сервері Sql або Sqlite

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

і в Oracle

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

Перевага використання БД полягає в тому, що ви можете одночасно оцінювати багато виразів. Також більшість БД дозволять використовувати дуже складні вирази, а також матимуть ряд додаткових функцій, які можна викликати за необхідності.

Однак продуктивність може постраждати, якщо багато окремих виразів потрібно оцінювати індивідуально, особливо коли БД розташована на мережевому сервері.

Далі у певній мірі розглядається проблема продуктивності, використовуючи базу даних у пам'яті 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

У цій статті розглядаються різні підходи. Ось два ключові підходи, згадані у статті:

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

Використовуйте двигун javascript, вбудований у 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


1
на практиці JEXL повільний (використовує інтроспекцію квасолі), має проблеми з багатопотоковою трансляцією (глобальний кеш)
Nishi

Приємно знати @Nishi! - Мій випадок використання був для налагодження речей у живих умовах, але не є частиною звичайного розгорнутого додатка.
Бред Паркс

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 Оператор (назвіть цеOp):

     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
Це незареєстрована експозиція алгоритму « Динькстра шунтування» . Кредит, де належить кредит.
Маркіз Лорн



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
Це стає набагато складніше, як тільки вам доведеться мати справу з декількома операціями, пріоритетом оператора, дужками, ... насправді будь-чим, що характеризує реальний арифметичний вираз. Ви не можете потрапити туди, починаючи з цієї техніки.
Маркіз Лорн

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

Я переглянув і знайшов деякі додаткові арифметичні функції, які також обробляються тут github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/…
thecodefather

Чудовий! Це врятувало мені день. Спасибі
Саріка.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 (), і у вас все буде добре. Подивіться нижче, що відповідь @ Боана трохи змінена. Модифікація є в коментарях.

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). Спробуйте, наприклад, на Десмосі. Ваш код фактично вводить кілька помилок. Перший полягає в тому, що ^більше не групуються справа наліво. Іншими словами, 2^3^2слід вважати групу подобається 2^(3^2)тому, що ^є правильною асоціацією, але ваші модифікації роблять її подібною до групи (2^3)^2. Друга полягає в тому, що ^вона має вищу перевагу перед *та /, але ваші модифікації трактують її так само. Див. Ideone.com/iN2mMa .
Radiodef

Отже, що ви припускаєте, що експоненцію краще зберігати там, де вона не була?
Ромео Сьєрра

Так, це я пропоную.
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
Не обробляє пріоритет оператора або декілька операторів чи круглі дужки. Не використовувати.
Маркіз Лорн

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), не працюватимуть з вашою ідеєю, або код буде надмірно брудним і неефективним.
Даніель Нурієв

2

Можна перетворити будь-який рядок вираження в нотації інфіксації в нотацію постфікса, використовуючи алгоритм маневрового двору Джикстра . Результат алгоритму може служити вхідним кодом до алгоритму постфікса з поверненням результату вираження.

Я написав статтю про це тут, з реалізацією в java


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
Не правильно керує пріоритетом оператора. Існують стандартні способи зробити це, і це не один із них.
Маркіз Лорн

EJP, чи можете ви вказати, де є проблема з перевагою оператора? Я повністю згоден з тим, що це не стандартний спосіб зробити це. стандартні способи вже згадувалися в попередніх дописах, ідея полягала в тому, щоб показати інший спосіб зробити це.
Efi G

2

Зовнішня бібліотека, наприклад RHINO або NASHORN, може використовуватися для запуску javascript. І javascript може оцінити просту формулу без розбиття рядка. Не впливає на продуктивність також, якщо код написано добре. Нижче наводиться приклад із 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; }
   //.........
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.