Dize biçiminde verilen bir matematik ifadesi nasıl değerlendirilir?


318

StringDeğerler gibi basit matematik ifadeleri değerlendirmek için bir Java rutin yazmaya çalışıyorum :

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

Ben if-then-else deyimlerini bir sürü önlemek istiyorum. Bunu nasıl yapabilirim?


7
Son zamanlarda apache lisansı altında yayınlanan exp4j adında bir matematik ifade ayrıştırıcısı yazdım, buradan kontrol edebilirsiniz: objecthunter.net/exp4j
fasseg

2
Ne tür ifadelere izin veriyorsunuz? Yalnızca tek operatör ifadeleri? Parantezlere izin veriliyor mu?
Raedwald



3
Bunun nasıl çok geniş olduğu düşünülebilir? Dijkstra'nın değerlendirme burada bariz bir çözümdür en.wikipedia.org/wiki/Shunting-yard_algorithm
Martin Spamer

Yanıtlar:


376

JDK1.6 ile, yerleşik Javascript motorunu kullanabilirsiniz.

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
Orada büyük bir sorun var gibi görünüyor; Bir komut dosyasını yürütür, bir ifadeyi değerlendirmez. Net olmak gerekirse, motor.eval ("8; 40 + 2"), 42 çıktı! Sözdizimini de kontrol eden bir ifade ayrıştırıcısı istiyorsanız, yeni bir tane bitirdim (çünkü ihtiyaçlarıma uygun hiçbir şey bulamadım): Javaluator .
Jean-Marc Astesana

4
Bir yan not olarak, bu ifadenin sonucunu kodunuzda başka bir yerde kullanmanız gerekiyorsa, sonucu aşağıdaki gibi bir return (Double) engine.eval(foo);
Double'e yazabilirsiniz

38
Güvenlik notu: Bunu hiçbir zaman kullanıcı girişi olan bir sunucu bağlamında kullanmamalısınız. Yürütülen JavaScript tüm Java sınıflarına erişebilir ve böylece sınırsız olarak uygulamanızı ele geçirebilir.
Boann

3
@Boann, söylediğin şey hakkında bana bir referans vermeni istiyorum. (% 100 emin olmak için)
partho

17
@partho new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");- programın şu anki dizinine (varsayılan olarak) JavaScript aracılığıyla bir dosya
yazacaktır

236

evalBu soruyu cevaplamak için aritmetik ifadeler için bu yöntemi yazdım . Toplama, çıkarma, çarpma, bölme, üs alma ( ^sembolü kullanarak ) ve gibi birkaç temel işlevi yapar sqrt. (... kullanarak gruplandırmayı destekler )ve operatörün öncelik ve ilişkilendirilebilirlik kurallarını doğru hale getirir .

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

Misal:

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

Çıktı: 7.5 (doğru olan)


Ayrıştırıcı özyinelemeli bir iniş ayrıştırıcısıdır , bu nedenle dahili olarak dilbilgisinde her bir operatör önceliği düzeyi için ayrı ayrıştırma yöntemleri kullanır. Kısa tuttum, bu yüzden değiştirmesi kolay, ancak burada genişletmek isteyebileceğiniz bazı fikirler var:

  • Değişkenler:

    Ayrıştırıcının işlev adlarını okuyan bit, evala Map<String,Double> variables.

  • Ayrı derleme ve değerlendirme:

    Değişkenler için destek ekledikten sonra, aynı ifadeyi her seferinde ayrıştırmadan değiştirilen değişkenlerle milyonlarca kez değerlendirmek isteseydiniz? Mümkün. İlk olarak, önceden derlenmiş ifadeyi değerlendirmek için kullanılacak bir arabirim tanımlayın:

    @FunctionalInterface
    interface Expression {
        double eval();
    }

    Şimdi doubles döndüren tüm yöntemleri değiştirin , bunun yerine bu arabirimin bir örneğini döndürürler. Java 8'in lambda sözdizimi bunun için harika çalışıyor. Değişen yöntemlerden birine örnek:

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

    Bu Expression, derlenmiş ifadeyi ( soyut bir sözdizimi ağacı ) temsil eden özyinelemeli bir nesne ağacı oluşturur . Ardından bir kez derleyebilir ve farklı değerlerle tekrar tekrar değerlendirebilirsiniz:

    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());
        }
    }
  • Farklı veri türleri:

    Bunun yerine double, değerlendiriciyi daha güçlü bir şey BigDecimalveya karmaşık sayılar veya rasyonel sayılar (kesirler) uygulayan bir sınıf kullanacak şekilde değiştirebilirsiniz . Hatta Objectgerçek bir programlama dili gibi ifadelerde bazı veri türlerinin karışımına izin vererek kullanabilirsiniz . :)


Bu yanıttaki tüm kodlar kamuya açıklandı . İyi eğlenceler!


1
Güzel bir algoritma, ondan başlayarak belirleyici ve mantıksal operatörleri başardım. Bir işlevi değerlendirmek için işlevler için ayrı sınıflar oluşturduk, böylece değişkenler fikriniz gibi, işlevlerle bir harita oluşturuyorum ve işlev adına bakıyorum. Her fonksiyon, bir yöntem eval (T rightOperator, T leftOperator) ile bir arayüz uygular, böylece her zaman algoritma kodunu değiştirmeden özellikler ekleyebiliriz. Ve jenerik tiplerle çalışmasını sağlamak iyi bir fikirdir. Teşekkürler!
Vasile Bors

1
Bu algoritmanın arkasındaki mantığı açıklayabilir misiniz?
iYonatan

1
Boann tarafından yazılan koddan ne anladığımı ve wiki'yi açıklayan örnekleri anlatmaya çalışıyorum. Bu algoritmanın operasyon emir kurallarından başlayarak mantığı. 1. operatör işareti | değişken değerlendirme | işlev çağrısı | parantez (alt ifadeler); 2. üs alma; 3. çarpma, bölme; 4. toplama, çıkarma;
Vasile Bors

1
Algoritma yöntemleri her işlem düzeyi için aşağıdaki gibi bölünür: parseFactor = 1. operatör işareti | değişken değerlendirme | işlev çağrısı | parantez (alt ifadeler); 2. üs alma; parseTerms = 3. çarpma, bölme; parseExpression = 4. toplama, çıkarma. Algoritma, yöntemleri ters sırayla çağırır (parseExpression -> parseTerms -> parseFactor -> parseExpression (alt ifadeler için)), ancak ilk satıra giden her yöntem yöntemi bir sonraki seviyeye çağırır, böylece tüm yürütme sırası yöntemleri aslında normal işlem sırası.
Vasile Bors

1
Örneğin, parseExpression yöntemi double x = parseTerm(); sol operatörü değerlendirir, bundan sonra for (;;) {...}gerçek sipariş seviyesinin başarılı işlemlerini (toplama, çıkarma) değerlendirir. Aynı mantık parseTerm yöntemindedir. ParseFactor bir sonraki seviyeye sahip değildir, bu nedenle sadece yöntemlerin / değişkenlerin değerlendirmeleri veya parantez durumunda - alt ifadeyi değerlendirin. boolean eat(int charToEat)Sonraki karaktere eşit dönüş doğru ve hareket imleç, adını kullanırsanız charToEat karakteri ile geçerli imleç karakterin yöntem onay eşitliği, bunun için 'kabul'.
Vasile Bors

34

Bunu çözmenin doğru yolu bir lexer ve bir ayrıştırıcıdır . Bunların basit sürümlerini kendiniz yazabilirsiniz veya bu sayfalarda ayrıca Java lexers ve ayrıştırıcılara bağlantılar bulunur.

Özyinelemeli bir iniş ayrıştırıcısı oluşturmak gerçekten iyi bir öğrenme alıştırmasıdır.


26

Üniversite projem için hem temel formülleri hem de daha karmaşık denklemleri (özellikle yinelenen operatörler) destekleyen bir ayrıştırıcı / değerlendirici arıyordum. MXparser adlı JAVA ve .NET için çok güzel bir açık kaynak kütüphane buldum. Sözdizimi hakkında bir şeyler yapmak için birkaç örnek vereceğim, daha fazla talimat için lütfen proje web sitesini (özellikle eğitim bölümü) ziyaret edin.

https://mathparser.org/

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

https://mathparser.org/api/

Ve birkaç örnek

1 - Basit furmula

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

2 - Kullanıcı tanımlı argümanlar ve sabitler

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 - Kullanıcı tanımlı işlevler

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 - İterasyon

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

Son zamanlarda bulundu - sözdizimini denemek (ve gelişmiş kullanım durumunu görmek) durumunda mXparser tarafından desteklenen Skaler Hesap Makinesi uygulamasını indirebilirsiniz.

Saygılarımla


Şimdiye kadar buradaki en iyi matematik kütüphanesi; kickstart basit, kullanımı kolay ve genişletilebilir. Kesinlikle en iyi cevap olmalı.
Trynkiewicz Mariusz

Maven sürümünü burada bulabilirsiniz .
izogfif

MXparser'ın yasadışı formülü tanımlayamadığını fark ettim, örneğin '0/0', '0' olarak sonuç alacak. Bu sorunu nasıl çözebilirim?
lulijun

Çözümü buldum, expression.setSlientMode ()
lulijun

20

BURAYA , GitHub'da EvalEx adlı başka bir açık kaynak kütüphanesidir.

JavaScript motorunun aksine, bu kütüphane yalnızca matematiksel ifadeleri değerlendirmeye odaklanmıştır. Dahası, kütüphane genişletilebilir ve mantıksal işleçlerin yanı sıra mantıksal işleçlerin kullanımını destekler.


Tamam, ama 5 veya 10 katlarının değerlerini çarpmaya çalıştığımızda başarısız oluyor, örneğin 65 * 6 sonuçları 3.9E + 2 ...
paarth batra

Ama bunu int yani int output = (int) 65 * 6'ya
çevirerek

1
Açıklığa kavuşturmak için, bu kütüphanenin bir sorunu değil, sayıların kayan nokta değerleri olarak gösterilmesiyle ilgili bir sorundur.
DavidBittner

Bu kütüphane gerçekten çok iyi. @paarth batra int için yayınlama tüm ondalık basamakları silecektir. Bunun yerine şunu kullanın: expression.eval (). ToPlainString ();
einUsername

15

BeanShell yorumlayıcısını da deneyebilirsiniz :

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

1
Adnroid Studio'da BeanShell'in nasıl kullanılacağını söyleyebilir misiniz?
Hanni

1
Hanni - Bu yazı
androidstudio

14

Java uygulamanız başka bir JAR kullanmadan bir veritabanına zaten erişiyorsa ifadeleri kolayca değerlendirebilirsiniz.

Bazı veritabanları sahte bir tablo kullanmanızı gerektirir (ör. Oracle'ın "ikili" tablosu) ve diğerleri ise herhangi bir tablodan "seçmeden" ifadeleri değerlendirmenize olanak tanır.

Örneğin, Sql Server veya Sqlite

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

ve Oracle'da

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

Bir DB kullanmanın avantajı, aynı anda birçok ifadeyi değerlendirebilmenizdir. Ayrıca çoğu DB, oldukça karmaşık ifadeler kullanmanıza izin verir ve ayrıca gerektiğinde çağrılabilecek bir dizi ekstra işleve sahiptir.

Ancak, özellikle DB bir ağ sunucusunda bulunuyorsa, birçok tek ifadenin ayrı ayrı değerlendirilmesi gerekirse performans düşebilir.

Aşağıda, bir Sqlite bellek içi veritabanı kullanılarak performans sorunu bir dereceye kadar ele alınmaktadır.

İşte Java'da çalışan tam bir örnek

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

Tabii ki, aynı anda birden fazla hesaplamayı işlemek için yukarıdaki kodu genişletebilirsiniz.

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

5
SQL enjeksiyonuna merhaba de!
cyberz

DB için ne kullandığınıza bağlıdır. Emin olmak istiyorsanız, özellikle matematik değerlendirmesi için kolayca boş bir sqlite DB oluşturabilirsiniz.
DAB

4
@cyberz Yukarıdaki örneğimi kullanırsanız, Sqlite bellekte geçici bir DB oluşturur. Bkz. Stackoverflow.com/questions/849679/…
DAB

11

Bu makalede çeşitli yaklaşımlar tartışılmaktadır. Makalede bahsedilen 2 temel yaklaşım şunlardır:

Apache'den JEXL

Java nesnelerine başvurular içeren komut dosyalarına izin verir.

// 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'ye gömülü javascript motorunu kullanın:

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
Bağlantının kopması durumunda lütfen makaledeki bilgileri özetleyin.
DJClayworth

Cevabı makaleden ilgili bitleri içerecek şekilde güncelledim
Brad Parks

1
uygulamada, JEXL yavaş (fasulye introspection kullanır), multithreading (global önbellek) ile performans sorunları var
Nishi

@Nishi'yi bilmek güzel! - Benim kullanım durumum, canlı ortamlarda hata ayıklamaktı, ancak normal konuşlandırılmış uygulamanın bir parçası değildi.
Brad Parks

10

Başka bir yol da, matematiksel ifadeleri değerlendirmekle birlikte çok daha fazlasını yapan Bahar İfade Dili veya SpEL'i kullanmaktır, bu nedenle belki biraz fazla olabilir. Bu ifade kitaplığını tek başına olduğu için kullanmak için Spring çerçevesini kullanmanız gerekmez. SpEL belgelerinden örnekler kopyalanıyor:

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

Burada kısa özlü SpEL örneklerini ve tüm belgeleri burada okuyun


8

Eğer bunu uygulayacaksak aşağıdaki algoritmayı kullanabiliriz:

  1. Hâlâ okunacak jetonlar olsa da,

    1.1 Bir sonraki kodu alın. 1.2 Belirteci:

    1.2.1 Bir sayı: değer yığınına itin.

    1.2.2 Bir değişken: değerini alın ve değer yığınına itin.

    1.2.3 Sol parantez: operatör yığınına itin.

    1.2.4 Doğru parantez:

     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 Bir operatör (buna OperaP deyin):

     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. Operatör yığını boş değilken, 1 Operatörü operatör yığınından açın. 2 İki işlenen elde ederek değer yığınını iki kez açın. 3 Operatörü işlenenlere doğru sırayla uygulayın. 4 Sonucu değer yığınına itin.

  3. Bu noktada operatör yığını boş olmalı ve değer yığını içinde sadece bir değer olmalıdır, bu da nihai sonuçtur.


3
Bu, Dijkstra Manevra avlu algoritmasının açıklanmamış bir gösterimidir . Kredinin vadesi gelen kredi.
Lorne Marquis



4

Sanırım bunu her ne şekilde yaparsanız yapın, birçok koşullu ifade içerecektir. Ancak, örneklerinizdeki gibi tekli işlemler için ifadeler aşağıdaki gibi ise 4 ile sınırlayabilirsiniz.

String math = "1+4";

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

"4 + 5 * 6" gibi çoklu işlemlerle uğraşmak istediğinizde çok daha karmaşık bir hal alıyor.

Bir hesap makinesi oluşturmaya çalışıyorsanız, o zaman hesaplamanın her bölümünü tek bir dize yerine ayrı ayrı (her sayı veya operatör) geçirerek cerrah olurum.


2
Birden fazla işlem, operatör önceliği, parantez, aslında gerçek bir aritmetik ifadeyi karakterize eden herhangi bir şeyle uğraşmanız gerektiğinde çok daha karmaşık hale gelir. Bu teknikten başlayarak oraya varamazsınız.
Lorne Marquis

4

Cevaplamak için çok geç ama java'daki ifadeyi değerlendirmek için aynı duruma rastladım, birisine yardımcı olabilir

MVELifadelerin çalışma zamanı değerlendirmesi yaparsa, Stringbunu değerlendirmek için içine bir java kodu yazabiliriz .

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

Ayrıca burada ele alınan bazı ek Aritmetik işlevler buldum ve github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/…
thecodefather

Awsome! Günümü kurtardı. Teşekkürler
Sarika.S

4

Symja çerçevesine bir göz atabilirsiniz :

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

Kesinlikle daha karmaşık ifadelerin değerlendirilebileceğini unutmayın:

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

Kod enjeksiyon işlemeli JDK1.6'nın Javascript motorunu kullanarak aşağıdaki örnek kodu deneyin.

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

Bu aslında @Boann tarafından verilen yanıtı tamamlıyor. "-2 ^ 2" nin -4.0 hatalı sonucunu vermesine neden olan küçük bir hata vardır. Bunun sorunu, üssün kendi içinde değerlendirildiği noktadır. Üstellemeyi parseTerm () bloğuna taşımanız yeterlidir. @ Boann'ın cevabı biraz değiştirilmiş olan aşağıdakilere bir göz atın . Değişiklik yorumlarda.

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 = -4aslında normaldir ve bir hata değildir. Gibi gruplandırılır -(2^2). Örneğin Desmos üzerinde deneyin. Kodunuz aslında birkaç hata içeriyor. Birincisi ^artık sağdan sola gruplanmaması. Başka bir deyişle, 2^3^2böyle grubuna gerekiyordu 2^(3^2)çünkü ^sağ ilişkisel, ama sizin modifikasyonlar bu grup gibi yapmak (2^3)^2. İkinci olmasıdır ^daha yüksek önceliğe sahip gerekiyordu *ve /ancak değişiklikler bunu aynı davranın. Bkz. İdeone.com/iN2mMa .
Radiodef

Öyleyse, önerdiğiniz şey, üslemenin olduğu yerde daha iyi tutulması değil mi?
Romeo Sierra

Evet, bunu öneriyorum.
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
Operatör önceliği veya birkaç operatör veya parantezle başa çıkmaz. Kullanmayın.
Lorne Marquis

2

Böyle bir şeye ne dersin:

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

ve benzer şeyleri diğer matematiksel operatörler için de yapın.


9
Etkili matematik ifadesi ayrıştırıcıları yazma hakkında okumalısınız. Bir bilgisayar bilimi metodolojisi var. Örneğin, ANTLR'ye bir göz atın. Ne yazdığınızı iyi düşünüyorsanız, (a + b / -c) * (e / f) gibi şeylerin fikrinizle çalışmadığını veya kodun süper küflü kirli ve verimsiz olacağını göreceksiniz.
Daniel Nuriyev


2

Başka bir seçenek: https://github.com/stefanhaustein/expressionparser

Ben her ikisi de izin basit ama esnek bir seçenek var bunu uyguladık:

Yukarıda bağlanan TreeBuilder , sembolik türetme yapan bir CAS demo paketinin parçasıdır . Ayrıca bir BASIC tercüman örneği var ve bunu kullanarak bir TypeScript yorumlayıcısı oluşturmaya başladım .


2

Matematiksel ifadeleri değerlendirebilen bir Java sınıfı:

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
Operatör önceliğini doğru işlemez. Bunu yapmanın standart yolları var ve bu onlardan biri değil.
Lorne Marquis

EJP, lütfen operatör önceliğiyle ilgili bir sorunun nerede olduğunu gösterebilir misiniz? Bunu yapmak için standart bir yol olmadığı gerçeğine tamamen katılıyorum. önceki yazılarda standart yollardan zaten bahsedilmişti, fikir bunu yapmanın başka bir yolunu göstermekti.
Efi G

2

Javascript çalıştırmak için RHINO veya NASHORN gibi harici kütüphane kullanılabilir. Javascript, dizeyi ayrıştırmadan basit formülü değerlendirebilir. Kod iyi yazılmışsa performans etkisi de yoktur. Aşağıda RHINO ile bir örnek -

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; }
   //.........
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.