文字列形式で与えられた数式を評価する方法は?


318

次のStringような値から単純な数式を評価するJavaルーチンを記述しようとしています。

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

多くのif-then-elseステートメントを避けたいです。これどうやってするの?


7
私は最近、apacheライセンスの下でリリースされたexp4jと呼ばれる数式パーサーを作成しました。ここで確認できます。objecthunter.net
fasseg

2
どのような表現を許可しますか?単一の演算子式のみ?括弧は許可されていますか?
Raedwald、2014



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という1つが完成しました(自分のニーズに合ったものが見つからなかったため)。
Jean-Marc Astesana、2012

4
:あなたが他の場所で、あなたのコードでこの式の結果を使用する必要がある場合は注意点として、あなたはそうのような二重に結果を型キャストすることができます return (Double) engine.eval(foo);
ベンVisness

38
セキュリティ上の注意:ユーザー入力のあるサーバーコンテキストではこれを使用しないでください。実行されたJavaScriptはすべてのJavaクラスにアクセスできるため、アプリケーションを無制限に乗っ取ることができます。
Boann、2015

3
@Boann、私はあなたが言ったことについての参考資料を私に与えてくれることを要求します。(100%確実にするために)
パルトー

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メソッドなどに渡された変数テーブルで名前を検索することにより、カスタム変数を処理するように簡単に変更できますMap<String,Double> variables

  • 個別のコンパイルと評価:

    変数のサポートを追加した場合、毎回解析することなく、変数を変更して同じ式を数百万回評価したい場合はどうでしょうか。それが可能だ。最初に、プリコンパイルされた式を評価するために使用するインターフェースを定義します。

    @FunctionalInterface
    interface Expression {
        double eval();
    }
    

    doubles を返すすべてのメソッドを変更して、代わりにそれらがそのインターフェースのインスタンスを返すようにします。Java 8のラムダ構文はこれに最適です。変更されたメソッドの1つの例:

    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)を使用してインターフェイスを実装しているため、アルゴリズムコードを変更せずにいつでも機能を追加できます。また、ジェネリック型で機能させることをお勧めします。ありがとうございます!
Vasile Bors

1
このアルゴリズムの背後にある論理を説明できますか?
iYonatan 2016

1
私は、Boannによって書かれたコードから理解できることを説明し、例としてwikiを説明しようとしています。このアルゴリズムのロジックは、操作順序のルールから始まります。1.オペレーター記号| 変数の評価| 関数呼び出し| 括弧(部分式); 2.べき乗。3.乗算、除算。4.加算、減算;
Vasile Bors

1
アルゴリズムのメソッドは、次のように操作順序のレベルごとに分けられます。parseFactor = 1. operator sign | 変数の評価| 関数呼び出し| 括弧(部分式); 2.べき乗。parseTerms = 3.乗算、除算。parseExpression =4。加算、減算。アルゴリズムでは、メソッドを逆の順序で呼び出します(parseExpression-> parseTerms-> parseFactor-> parseExpression(サブ式の場合))。ただし、最初の行のすべてのメソッドが次のレベルのメソッドを呼び出すため、実行順序のメソッド全体が実際の操作の順序。
Vasile Bors

1
たとえば、parseExpressionメソッドは double x = parseTerm(); 左の演算子をfor (;;) {...}評価します。この後、実際の順序レベルの連続演算(加算、減算)を評価します。同じロジックがparseTermメソッドにもあります。parseFactorには次のレベルがないため、メソッド/変数の評価のみ、または括弧の場合のみ-サブ式を評価します。boolean eat(int charToEat)次の文字に等しく復帰真と移動カーソルは、私が名前を使用する場合charToEat文字で現在のカーソルの文字の方法のチェックの平等は、それを「受け入れます」。
Vasile Bors 2016


26

私の大学のプロジェクトでは、基本的な式とより複雑な方程式(特に反復演算子)の両方をサポートするパーサー/エバリュエーターを探していました。mXparserと呼ばれる、JAVAおよび.NET用の非常に優れたオープンソースライブラリを見つけました。構文を理解するためにいくつかの例を示します。詳細については、プロジェクトのWebサイト(特にチュートリアルのセクション)にアクセスしてください。

https://mathparser.org/

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

https://mathparser.org/api/

そしていくつかの例

1-単純なfurmula

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

2-ユーザー定義の引数と定数

Argument x = new Argument("x = 10");
Constant a = new Constant("a = pi^2");
Expression e = new Expression("cos(a*x)", x, a);
double v = e.calculate()

3-ユーザー定義関数

Function f = new Function("f(x, y, z) = sin(x) + cos(y*z)");
Expression e = new Expression("f(3,2,5)", f);
double v = e.calculate()

4-反復

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

最近見つかりました-構文を試したい場合(および高度な使用例を参照したい場合)、mXparserを搭載したスカラー 計算 アプリをダウンロードできます。

宜しくお願いします


これまでのところ、これは最高の数学ライブラリです。キックスタートが簡単で、使いやすく、拡張可能です。間違いなくトップアンサーでなければなりません。
Trynkiewicz Mariusz

ここで Mavenバージョンを見つけます
izogfif

mXparserが不正な数式を識別できないことがわかりました。たとえば、「0/0」は「0」として結果を取得します。この問題を解決するにはどうすればよいですか?
lulijun

解が見つかりました、expression.setSlientMode()
lulijun

20

ここは、EvalExという名前のGitHub上の別のオープンソースライブラリです。

JavaScriptエンジンとは異なり、このライブラリは数式の評価のみに焦点を当てています。さらに、ライブラリは拡張可能であり、ブール演算子と括弧の使用をサポートしています。


これは... okですが、私たちは5または10の倍数の乗算値にしようとすると、例えば、3.9E + 2で65の* 6件の結果を失敗した
paarth BATRA

.But、それは今390を発生しますint型つまりint型出力=(INT)65 * 6にそれをキャストすることによってこの問題を解決する方法があります
paarth BATRA

1
明確にするために、これはライブラリの問題ではなく、浮動小数点値としての数値の表現に関する問題です。
DavidBittner 2018

このライブラリは本当に良いです。@paarth batra intにキャストすると、すべての小数点が削除されます。代わりにこれを使用してください:expression.eval()。toPlainString();
einUsername

15

BeanShellインタープリターを試すこともできます。

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

1
adnroid StudioでBeanShellを使用する方法を教えてください。
ハンニ

1
: -ハンニこの記事はあなたのandroidstudioプロジェクトへのBeanShellを追加することに役立つかもしれない 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を使用する利点は、同時に多くの式を評価できることです。また、ほとんどのDBでは、非常に複雑な式を使用でき、必要に応じて呼び出すことができる追加の関数がいくつかあります。

ただし、多くの単一の式を個別に評価する必要がある場合、特にDBがネットワークサーバー上にある場合は、パフォーマンスが低下する可能性があります。

以下では、Sqliteインメモリデータベースを使用して、パフォーマンスの問題にある程度対処しています。

これがJavaで完全に機能する例です

Class. forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount");
rs.next();
System.out.println(rs.getBigDecimal(1));
stat.close();
conn.close();

もちろん、上記のコードを拡張して、同時に複数の計算を処理することもできます。

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

5
SQLインジェクションによろしく!
cyberz 2017

それは、DBを何に使用するかによって異なります。確実にしたい場合は、特に数学の評価用に、空のsqlite DBを簡単に作成できます。
DAB

4
@cyberz上記の例を使用すると、Sqliteはメモリに一時DBを作成します。stackoverflow.com/questions/849679/…を
DAB

11

この記事では、さまざまなアプローチについて説明します。記事で言及されている2つの主要なアプローチは次のとおりです。

ApacheのJEXL

Javaオブジェクトへの参照を含むスクリプトを許可します。

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

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

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

JDKに組み込まれたJavaScriptエンジンを使用します。

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 2016年

記事の関連するビットを含めるように回答をアップグレードしました
Brad Parks

1
実際には、JEXLは遅く(Beanのイントロスペクションを使用)、マルチスレッド(グローバルキャッシュ)でパフォーマンスの問題があります
Nishi

@西を知って良かった!-私の使用例は、ライブ環境でのデバッグ用でしたが、通常のデプロイ済みアプリの一部ではありませんでした。
ブラッドパークス

10

もう1つの方法は、Spring Expression LanguageまたはSpELを使用することです。SpELは、数式の評価とともに、はるかに多くのことを行うため、多分少しやりすぎです。この式ライブラリはスタンドアロンであるため、Springフレームワークを使用する必要はありません。SpELのドキュメントから例をコピーする:

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

ここでより簡潔なSpELの例とここで完全なドキュメントを読ん


8

それを実装する場合は、以下のアルゴリズムを使用できます:-

  1. 読み込まれるトークンはまだありますが、

    1.1次のトークンを取得します。1.2トークンが次の場合:

    1.2.1数値:値スタックにプッシュします。

    1.2.2変数:その値を取得し、値スタックにプッシュします。

    1.2.3左括弧:演算子スタックにプッシュします。

    1.2.4右括弧:

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

    1.2.5演算子(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値スタックを2回ポップし、2つのオペランドを取得します。3演算子を正しい順序でオペランドに適用します。4結果を値スタックにプッシュします。

  3. この時点で、演算子スタックは空で、値スタックには値が1つだけ含まれているはずです。これが最終結果です。


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"のような複数の操作を処理する場合は、さらに複雑になります。

電卓を作成しようとしている場合は、計算の各セクションを1つの文字列としてではなく、個別に(各数値または演算子)渡すことをお勧めします。


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

かっこいい!それは私の日を救った。ありがとう
Sarika.S

4

あなたはSymjaフレームワークを見ているかもしれません:

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

より複雑な式を評価できることに注意してください。

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

4

コードインジェクション処理を備えたJDK1.6のJavaScriptエンジンを使用して、次のサンプルコードを試してください。

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

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

4

これは実際に@Boannの回答を補完するものです。"-2 ^ 2"で-4.0という誤った結果が発生するわずかなバグがあります。そのための問題は、累乗が彼で評価されるポイントです。指数をparseTerm()のブロックに移動するだけで大​​丈夫です。@Boannの回答を少し変更した下記をご覧ください。変更はコメントにあります。

public static double eval(final String str) {
    return new Object() {
        int pos = -1, ch;

        void nextChar() {
            ch = (++pos < str.length()) ? str.charAt(pos) : -1;
        }

        boolean eat(int charToEat) {
            while (ch == ' ') nextChar();
            if (ch == charToEat) {
                nextChar();
                return true;
            }
            return false;
        }

        double parse() {
            nextChar();
            double x = parseExpression();
            if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
            return x;
        }

        // Grammar:
        // expression = term | expression `+` term | expression `-` term
        // term = factor | term `*` factor | term `/` factor
        // factor = `+` factor | `-` factor | `(` expression `)`
        //        | number | functionName factor | factor `^` factor

        double parseExpression() {
            double x = parseTerm();
            for (;;) {
                if      (eat('+')) x += parseTerm(); // addition
                else if (eat('-')) x -= parseTerm(); // subtraction
                else return x;
            }
        }

        double parseTerm() {
            double x = parseFactor();
            for (;;) {
                if      (eat('*')) x *= parseFactor(); // multiplication
                else if (eat('/')) x /= parseFactor(); // division
                else if (eat('^')) x = Math.pow(x, parseFactor()); //exponentiation -> Moved in to here. So the problem is fixed
                else return x;
            }
        }

        double parseFactor() {
            if (eat('+')) return parseFactor(); // unary plus
            if (eat('-')) return -parseFactor(); // unary minus

            double x;
            int startPos = this.pos;
            if (eat('(')) { // parentheses
                x = parseExpression();
                eat(')');
            } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
                while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
                x = Double.parseDouble(str.substring(startPos, this.pos));
            } else if (ch >= 'a' && ch <= 'z') { // functions
                while (ch >= 'a' && ch <= 'z') nextChar();
                String func = str.substring(startPos, this.pos);
                x = parseFactor();
                if (func.equals("sqrt")) x = Math.sqrt(x);
                else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
                else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
                else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
                else throw new RuntimeException("Unknown function: " + func);
            } else {
                throw new RuntimeException("Unexpected: " + (char)ch);
            }

            //if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation -> This is causing a bit of problem

            return x;
        }
    }.parse();
}

-2^2 = -4バグではなく、実際には正常です。のようにグループ化されます-(2^2)たとえば、Desmosでお試しください。あなたのコードは実際にいくつかのバグをもたらします。1つ目は、^右から左にグループ化されなくなったことです。言い換えると、2^3^2は右結合である2^(3^2)ため^、のようにグループ化されることになっていますが、変更すると、のようにグループ化されます(2^3)^2。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)のようなものがあなたのアイデアではうまくいかないか、コードが非常に汚くて非効率的であることがわかります。
Daniel Nuriyev 2014


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
演算子の優先順位を正しく処理しません。これを行うには標準的な方法があり、これはそれらの1つではありません。
ローン侯爵

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; }
   //.........
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.