2009-09-17 20 views
17

Istnieje wiele algorytmów do obliczania wyrażeń, na przykład:Najlepszy i najkrótsza droga do oceny wyrażeń matematycznych

  1. By Recursive Descent
  2. Shunting-yard algorithm
  3. Reverse Polish notation

Czy istnieje jakiś sposób, aby ocenić jakikolwiek wyrażenie matematyczne przy użyciu C# .net reflection lub innej nowoczesnej technologii .net?

+0

zadałem podobne pytanie jakiś czas temu. Możesz przyjrzeć się niektórym z tych odpowiedzi: http://stackoverflow.com/questions/234217/is-it-possible-to-translate-a-user-entered-mathematical-equation-into-ccode-at – raven

+0

Czy znalazłeś sposób na połączenie ze zmiennymi użytymi w pozostałej części "statycznego/wstępnie skompilowanego" kodu? –

Odpowiedz

19

Oprócz odpowiedzi Thomasa, faktycznie można uzyskać dostęp do (przestarzałych) bibliotek JScript bezpośrednio z języka C#, co oznacza, że ​​można użyć odpowiednika funkcji JScript's eval.

using Microsoft.JScript;  // needs a reference to Microsoft.JScript.dll 
using Microsoft.JScript.Vsa; // needs a reference to Microsoft.Vsa.dll 

// ... 

string expr = "7 + (5 * 4)"; 
Console.WriteLine(JScriptEval(expr)); // displays 27 

// ... 

public static double JScriptEval(string expr) 
{ 
    // error checking etc removed for brevity 
    return double.Parse(Eval.JScriptEvaluate(expr, _engine).ToString()); 
} 

private static readonly VsaEngine _engine = VsaEngine.CreateEngine(); 
+0

Szkoda, że ​​nie obsługuje kija^dla potęgowania. –

13

Jest to z pewnością możliwe. Klasa CodeSnippetCompileUnit w zasadzie to robi. Napisałem ci przykładowy kod użycia. Będziesz musiał uwzględnić te przestrzenie nazw:

  • System.CodeDom.Compiler;
  • System.CodeDom;
  • Microsoft.CSharp;
  • System.Reflection;

Oto kod:

string source = @" 
class MyType 
{ 
    public static int Evaluate(<!parameters!>) 
    { 
     return <!expression!>; 
    } 
} 
"; 

string parameters = "int a, int b, int c"; 
string expression = "a + b * c"; 

string finalSource = source.Replace("<!parameters!>", parameters).Replace("<!expression!>", expression); 

CodeSnippetCompileUnit compileUnit = new CodeSnippetCompileUnit(finalSource); 
CodeDomProvider provider = new CSharpCodeProvider(); 

CompilerParameters parameters = new CompilerParameters(); 

CompilerResults results = provider.CompileAssemblyFromDom(parameters, compileUnit); 

Type type = results.CompiledAssembly.GetType("MyType"); 
MethodInfo method = type.GetMethod("Evaluate"); 

// The first parameter is the instance to invoke the method on. Because our Evaluate method is static, we pass null. 
int result = (int)method.Invoke(null, new object[] { 4, -3, 2 }); 

zastąpić „parametry” oraz „wyrażenia” przez cokolwiek, i masz sobie ogólną wyrażeń.

Jeśli pojawi się wyjątek FileNotFoundException w results.CompiledAssembly, to nie można skompilować fragmentu kodu.

Możesz również rzucić okiem na klasę System.CodeDom.CodeSnippetExpression. Służy do bardziej szczegółowego czytania wyrażeń, ale samo wyrażenie nie może być skompilowane, więc będziesz potrzebował więcej CodeDom do zbudowania klasy i metody pracy wokół niej. Jest to przydatne, jeśli chcesz programowo manipulować rodzajem klasy, którą generujesz. CodeSnippetCompileUnit jest fajny do generowania całej klasy pracującej jednocześnie (i prostszego przykładu), ale aby nią manipulować, musiałbyś wykonywać niewygodne manipulacje ciągami.

+0

najlepsze rozwiązanie. –

+0

Dla przypomnienia, wydajność tego rozwiązania w porównaniu z użyciem ncalc jest OGROMNA, przetestowałem to dla graphera, a niektóre funkcje wielu zmiennych zajęły ponad 500, aby zostać wykreślone, z czego zajęło mi to mniej niż 5 s, spisek ponad 400 000 punktów. Świetne rozwiązanie! –

3

Mimo że korzystanie z usług kompilatora jest prostym i wydajnym rozwiązaniem, powoduje to poważne problemy z zabezpieczeniami, jeśli wyrażenie zostanie wprowadzone przez użytkownika, ponieważ może ono wykonać praktycznie wszystko: dowolną wartość.

Istnieje jeszcze jedno bardzo proste rozwiązanie, które jest o wiele bardziej bezpieczne: skorzystaj z funkcji JScript Eval. Wystarczy wykonać następujące kroki:

utworzyć plik js nazwane JsMath.js:

class JsMath 
{ 
    static function Eval(expression : String) : double 
    { 
     return eval(expression); 
    }; 
} 

skompilować go w bibliotece klasy:

jsc /t:library JsMath.js 

referencyjny biblioteka JsMath w projekcie C# i używać go tak:

double result = JsMath.Eval(expression); 
+0

Nigdy nawet nie brałem pod uwagę bezpieczeństwa, ani nie wiedziałem o funkcji ewaluacyjnej JScript. Jest to również bardziej zwięzłe niż moje rozwiązanie. Dobra odpowiedź! – Joren

+0

Jest faktycznie możliwe uzyskanie dostępu do funkcji 'eval' bezpośrednio z C#, bez pośredniego kroku kompilacji JScript. Zobacz moją odpowiedź dla szczegółów. – LukeH

+0

Aby uniknąć problemów z bezpieczeństwem za pomocą usług kompilatora, używam ANTL do wstępnego analizowania wyrażeń użytkownika i unikania dziwnych danych wejściowych. Jeśli szukasz wydajności, funkcja 'eval()' może nie działać. –

3

Dla mnie Vici.Parser działa bardzo dobrze: check it out here, jest to najbardziej elastyczny analizator wyrażeń, jaki do tej pory znalazłem.

(użyliśmy go skonfigurować „czytelny dla człowieka” reguł biznesowych, z danych dostarczonych przez bazy danych serwera SQL)

Przykładami są dostępne i jest tam bardzo dobre wsparcie przez dewelopera (sprawdź na stronie internetowej użytkownika forum).

+0

Wygląda bardzo interesująco. – NotMe

+1

@Roel - Link nie działa. –

3

ncalc jest najlepszy. możesz go również znaleźć w codeplex w samorodku.
NCalc jest wylicznikiem wyrażeń matematycznych w .NET. NCalc może analizować dowolne wyrażenie i oceniać wynik, w tym parametry statyczne lub dynamiczne oraz funkcje niestandardowe.

1

Myślę, że to najlepszy sposób na wszystko. Petar Repac's answer jest niesamowity. Używanie „wyrażenie” argument obiektu DataColumn rozwiązuje niesamowicie i łatwo z tematem:

static double Evaluate(string expression) 
{ 
    var loDataTable = new DataTable(); 
    var loDataColumn = new DataColumn("Eval", typeof(double), expression); 
    loDataTable.Columns.Add(loDataColumn); 
    loDataTable.Rows.Add(0); 
    return (double)(loDataTable.Rows[0]["Eval"]); 
} 
Powiązane problemy