2008-10-06 16 views
19

Ich muss einen mathematischen Ausdruck auswerten, der mir als String in C# präsentiert wird. Beispiel noddy aber bekommt den Punkt über den String als Ausdruck.Operatoren als Strings

Ich brauche das auszuwerten, um dann ein int zu füllen.

Es gibt keine eval() in C# wie in anderen langugaes ...

String myString = "3*4"; 

Edit:

Ich bin auf VS2008

Versucht das Microsoft.JScript. = Die veraltete Methode (aber entspricht nach wie vor - Warnung)

jedoch die Microsoft.JScript DLL, die ich habe Döns auf

arbeiten

öffentliches Objekt InvokeMember (string Name, Binding invokeAttr, Binder Bindemittel, Zielobjekt , Objekt [] args);

Beschwert, dass ein ";" go figure ...

EDIT 2

Lösung - war der CodeDOM ein - es funktioniert, da es kein Sicherheitsproblem ist - immer nur ich gehe, den Code zu laufen. Vielen Dank für die Antworten ...

Und der Link zum neuen Drachen Buch ehrfürchtigen

EDIT 3

Matt dataTable.Compute() funktioniert auch - noch besser für die Sicherheit bewusst. (Parameterprüfung notiert)

+0

Vielen Dank für Ihre Frage – rshimoda

+0

Sie könnten auch auf http://Ncalc.codeplex.com, wie ich in meiner Antwort vorgeschlagen, die aus Gründen, die ich nicht verstehe, gelöscht werden scheint. – GreyCloud

Antwort

13

Die Art, wie ich es sehe, haben Sie zwei Möglichkeiten - verwenden Sie einen Ausdruck Evaluator oder Konstrukt, kompilieren Sie und führen Sie C# -Code im laufenden Betrieb.

Ich würde mit einer Expression-Evaluator-Bibliothek gehen, da Sie sich keine Sorgen über Sicherheitsprobleme machen müssen. Das heißt, Sie können möglicherweise die Codegenerierung nicht in Umgebungen mit mittlerem Vertrauen verwenden, z. B. bei den meisten gemeinsam genutzten Hosting-Servern. Hier

ist ein Beispiel für die Code-Generierung Ausdrücke zu bewerten: http://www.vbforums.com/showthread.php?t=397264

+0

die vbfourms hat den Job dank –

+0

Hallo - die dotMath-Bibliothek scheint nicht von workspace.gotdotnet.com migriert worden - wenn sie Übergang zu msdn haben ... –

+0

Ja :(Scheint so zu sein verfügbar auf CodePlex jedoch: http://www.codeplex.com/dotMath/Release/ProjectReleases.aspx?ReleaseId=875 –

1

Wenn Sie sagen, "wie in anderen Sprachen" sollten Sie stattdessen sagen "wie in dynamischen Sprachen".

Für dynamische Sprachen wie Python, Ruby und viele interpretierte Sprachen, ist eine Eval() - Funktion ein natürliches Element. In der Tat ist es wahrscheinlich sogar trivial, Ihre eigenen zu implementieren.

Wie auch immer, .Net ist im Kern eine statische, stark typisierte, kompilierte Plattform (zumindest bis die Dynamic Language Runtime mehr Unterstützung bekommt). Dies hat natürliche Vorteile wie Code-Injection-Sicherheit und Typprüfung beim Kompilieren, die schwer zu ignorieren sind. Aber es bedeutet, dass eine Eval() - Funktion nicht so gut passt - sie möchte den Ausdruck im Voraus kompilieren können. In dieser Art von Plattform gibt es im Allgemeinen andere, sicherere Wege, die gleiche Aufgabe zu erfüllen.

+0

Vermutung, dass Sie JScript.Net verpasst haben. – NotMe

+0

Gambit Scheme ist eine dynamische, stark typisierte, (optional) kompilierte Plattform, und sie hat eval. –

+0

lol: die Ausnahmen, die die Regel beweisen. –

0

In einer interpretierten Sprache haben Sie möglicherweise die Möglichkeit, die Zeichenfolge mit dem Interpreter auszuwerten. In C# benötigen Sie einen Parser für die Sprache, in der die Zeichenfolge geschrieben ist (die Sprache der mathematischen Ausdrücke). Dies ist eine nicht-triviale Übung. Wenn Sie dies tun möchten, verwenden Sie einen rekursiven Abstiegsparser. Die ersten Kapitel des "Dragon Book" (Compilers: Design, etc. von Aho, Sethi und Ullman - 1. Auflage 1977 oder 2. Ausgabe 2007) haben eine gute Erklärung, was Sie tun müssen.

Eine Alternative könnte darin bestehen, in Ihrem Projekt eine in Perl geschriebene Komponente einzuschließen, die jetzt für .NET verfügbar sein soll, und per Perl die Auswertung durchzuführen.

0

Die jscript interpreter könnte tun, oder Sie können Ihren eigenen Parser schreiben, wenn der Ausdruck einfach ist (Vorsicht, es wird sehr schnell kompliziert).

Ich bin ziemlich sicher, dass es keine direkte "Eval (String)" -Methode in C# gibt, da es nicht interpretiert wird.

Beachten Sie aber, dass Code Interpretation Code-Injektion unterliegt, besonders vorsichtig sein :)

0

Werden Sie die Werte von anderen Variablen zugreifen müssen, wenn Sie einen Ausdruck der Berechnung?

+0

Nein nur das Ergebnis = danke deestan –

0

Nach einigen googeln, ich sehe es gibt die Möglichkeit, zu erstellen und kompilieren Code on the fly mit CodeDom. (Siehe a tutorial).

Ich persönlich glaube nicht, dass dieser Ansatz eine sehr gute Idee ist, da der Benutzer den Code eingeben kann, den er will, aber das könnte ein Bereich sein, den man untersuchen kann (zB nur die Eingabe validieren und nur Zahlen und einfache mathematische Operationen).

21

Alle anderen Antworten sind möglich Overkill.

Wenn Sie nur einfache Arithmetik benötigen, tun Sie dies.

EDIT: ein wenig mehr Informationen. Überprüfen Sie die MSDN-Dokumentation für die Expression Eigenschaft der System.Data.DataColumn Klasse. Das Zeug in "Ausdruck Syntax" skizziert eine Liste von Befehlen, die Sie zusätzlich zu den arithmetischen Operatoren verwenden können. (zB IIF, LEN usw.). Vielen Dank an alle, die meine erste Antwort abgegeben haben!

+0

netter kleiner hack. Nur neugierig: wie wird sich das für schlechte Eingaben verhalten? –

+0

nice das funktioniert auch => Vielen Dank matt –

+0

es löst eine System.Data.EvaluateException oder System.Data.SyntaxErrorException sagt Ihnen was ist falsch .. –

0

Einige andere Vorschläge:

  • Mono 2.0 (herauskam heute) eine eval-Methode.
  • Sie können leicht eine kleine Domäne in boo schreiben.
  • Sie können einen Old School Recursive-Descent-EBNF-Parser erstellen.
11

Ich tat dies als eine persönliche Übung in C# vor ein paar Wochen.

Es ist ziemlich viel Code und schlecht an einigen Stellen kommentiert. Aber es hat mit vielen Testfällen funktioniert.

Viel Spaß!

using System; 
using System.Collections.Generic; 
using System.Text.RegularExpressions; 

namespace StackOverflow 
{ 
    class Start 
    { 
     public static void Main(string[] args) 
     { 
      Evaluator ev; 
      string variableValue, eq; 
     Console.Write("Enter equation: "); 
     eq = Console.ReadLine(); 

     while (eq != "quit") 
     { 
      ev = new Evaluator(eq); 
      foreach (Variable v in ev.Variables) 
      { 
       Console.Write(v.Name + " = "); 
       variableValue = Console.ReadLine(); 
       ev.SetVariable(v.Name, Convert.ToDecimal(variableValue)); 
      } 

      Console.WriteLine(ev.Evaluate()); 

      Console.Write("Enter equation: "); 
      eq = Console.ReadLine(); 
     } 
    } 
} 

class EvalNode 
{ 
    public virtual decimal Evaluate() 
    { 
     return decimal.Zero; 
    } 
} 

class ValueNode : EvalNode 
{ 
    decimal value; 

    public ValueNode(decimal v) 
    { 
     value = v; 
    } 

    public override decimal Evaluate() 
    { 
     return value; 
    } 

    public override string ToString() 
    { 
     return value.ToString(); 
    } 
} 

class FunctionNode : EvalNode 
{ 
    EvalNode lhs = new ValueNode(decimal.Zero); 
    EvalNode rhs = new ValueNode(decimal.Zero); 
    string op = "+"; 

    public string Op 
    { 
     get { return op; } 
     set 
     { 
      op = value; 
     } 
    } 

    internal EvalNode Rhs 
    { 
     get { return rhs; } 
     set 
     { 
      rhs = value; 
     } 
    } 

    internal EvalNode Lhs 
    { 
     get { return lhs; } 
     set 
     { 
      lhs = value; 
     } 
    } 

    public override decimal Evaluate() 
    { 
     decimal result = decimal.Zero; 

     switch (op) 
     { 
      case "+": 
       result = lhs.Evaluate() + rhs.Evaluate(); 
       break; 

      case "-": 
       result = lhs.Evaluate() - rhs.Evaluate(); 
       break; 

      case "*": 
       result = lhs.Evaluate() * rhs.Evaluate(); 
       break; 

      case "/": 
       result = lhs.Evaluate()/rhs.Evaluate(); 
       break; 

      case "%": 
       result = lhs.Evaluate() % rhs.Evaluate(); 
       break; 

      case "^": 
       double x = Convert.ToDouble(lhs.Evaluate()); 
       double y = Convert.ToDouble(rhs.Evaluate()); 

       result = Convert.ToDecimal(Math.Pow(x, y)); 
       break; 

      case "!": 
       result = Factorial(lhs.Evaluate()); 
       break; 
     } 

     return result; 
    } 

    private decimal Factorial(decimal factor) 
    { 
     if (factor < 1) 
      return 1; 

     return factor * Factorial(factor - 1); 
    } 

    public override string ToString() 
    { 
     return "(" + lhs.ToString() + " " + op + " " + rhs.ToString() + ")"; 
    } 
} 

public class Evaluator 
{ 
    string equation = ""; 
    Dictionary<string, Variable> variables = new Dictionary<string, Variable>(); 

    public string Equation 
    { 
     get { return equation; } 
     set { equation = value; } 
    } 

    public Variable[] Variables 
    { 
     get { return new List<Variable>(variables.Values).ToArray(); } 
    } 

    public void SetVariable(string name, decimal value) 
    { 
     if (variables.ContainsKey(name)) 
     { 
      Variable x = variables[name]; 
      x.Value = value; 
      variables[name] = x; 
     } 
    } 

    public Evaluator(string equation) 
    { 
     this.equation = equation; 
     SetVariables(); 
    } 

    public decimal Evaluate() 
    { 
     return Evaluate(equation, new List<Variable>(variables.Values)); 
    } 

    public decimal Evaluate(string text) 
    { 
     decimal result = decimal.Zero; 
     equation = text; 
     EvalNode parsed; 

     equation = equation.Replace(" ", ""); 

     parsed = Parse(equation, "qx"); 

     if (parsed != null) 
      result = parsed.Evaluate(); 

     return result; 
    } 

    public decimal Evaluate(string text, List<Variable> variables) 
    { 
     foreach (Variable v in variables) 
     { 
      text = text.Replace(v.Name, v.Value.ToString()); 
     } 

     return Evaluate(text); 
    } 

    private static bool EquationHasVariables(string equation) 
    { 
     Regex letters = new Regex(@"[A-Za-z]"); 

     return letters.IsMatch(equation); 
    } 

    private void SetVariables() 
    { 
     Regex letters = new Regex(@"([A-Za-z]+)"); 
     Variable v; 

     foreach (Match m in letters.Matches(equation, 0)) 
     { 
      v = new Variable(m.Groups[1].Value, decimal.Zero); 

      if (!variables.ContainsKey(v.Name)) 
      { 
       variables.Add(v.Name, v); 
      } 
     } 
    } 

    #region Parse V2 

    private Dictionary<string, string> parenthesesText = new Dictionary<string, string>(); 

    /* 
    * 1. All the text in first-level parentheses is replaced with replaceText plus an index value. 
    *  (All nested parentheses are parsed in recursive calls) 
    * 2. The simple function is parsed given the order of operations (reverse priority to 
    *  keep the order of operations correct when evaluating). 
    *  a. Addition (+), subtraction (-)     -> left to right 
    *  b. Multiplication (*), division (/), modulo (%) -> left to right 
    *  c. Exponents (^)         -> right to left 
    *  d. Factorials (!)         -> left to right 
    *  e. No op (number, replaced parentheses) 
    * 3. When an op is found, a two recursive calls are generated -- parsing the LHS and 
    *  parsing the RHS. 
    * 4. An EvalNode representing the root node of the evaluations tree is returned. 
    * 
    * Ex. 3 + 5     (3 + 5) * 8 
    *   +       * 
    *  /\      /\ 
    *   3 5      + 8 
    *         /\ 
    *  3 + 5 * 8     3 5 
    *   + 
    *   /\ 
    *   3 * 
    *   /\ 
    *   5 8 
    */ 

    /// <summary> 
    /// Parses the expression and returns the root node of a tree. 
    /// </summary> 
    /// <param name="eq">Equation to be parsed</param> 
    /// <param name="replaceText">Text base that replaces text in parentheses</param> 
    /// <returns></returns> 
    private EvalNode Parse(string eq, string replaceText) 
    { 
     int randomKeyIndex = 0; 

     eq = eq.Replace(" ", ""); 
     if (eq.Length == 0) 
     { 
      return new ValueNode(decimal.Zero); 
     } 

     int leftParentIndex = -1; 
     int rightParentIndex = -1; 
     SetIndexes(eq, ref leftParentIndex, ref rightParentIndex); 

     //remove extraneous outer parentheses 
     while (leftParentIndex == 0 && rightParentIndex == eq.Length - 1) 
     { 
      eq = eq.Substring(1, eq.Length - 2); 
      SetIndexes(eq, ref leftParentIndex, ref rightParentIndex); 
     } 

     //Pull out all expressions in parentheses 
     replaceText = GetNextReplaceText(replaceText, randomKeyIndex); 

     while (leftParentIndex != -1 && rightParentIndex != -1) 
     { 
      //replace the string with a random set of characters, stored extracted text in dictionary keyed on the random set of chars 

      string p = eq.Substring(leftParentIndex, rightParentIndex - leftParentIndex + 1); 
      eq = eq.Replace(p, replaceText); 
      parenthesesText.Add(replaceText, p); 

      leftParentIndex = 0; 
      rightParentIndex = 0; 

      replaceText = replaceText.Remove(replaceText.LastIndexOf(randomKeyIndex.ToString())); 
      randomKeyIndex++; 
      replaceText = GetNextReplaceText(replaceText, randomKeyIndex); 

      SetIndexes(eq, ref leftParentIndex, ref rightParentIndex); 
     } 

     /* 
     * Be sure to implement these operators in the function node class 
     */ 
     char[] ops_order0 = new char[2] { '+', '-' }; 
     char[] ops_order1 = new char[3] { '*', '/', '%' }; 
     char[] ops_order2 = new char[1] { '^' }; 
     char[] ops_order3 = new char[1] { '!' }; 

     /* 
     * In order to evaluate nodes LTR, the right-most node must be the root node 
     * of the tree, which is why we find the last index of LTR ops. The reverse 
     * is the case for RTL ops. 
     */ 

     int order0Index = eq.LastIndexOfAny(ops_order0); 

     if (order0Index > -1) 
     { 
      return CreateFunctionNode(eq, order0Index, replaceText + "0"); 
     } 

     int order1Index = eq.LastIndexOfAny(ops_order1); 

     if (order1Index > -1) 
     { 
      return CreateFunctionNode(eq, order1Index, replaceText + "0"); 
     } 

     int order2Index = eq.IndexOfAny(ops_order2); 

     if (order2Index > -1) 
     { 
      return CreateFunctionNode(eq, order2Index, replaceText + "0"); 
     } 

     int order3Index = eq.LastIndexOfAny(ops_order3); 

     if (order3Index > -1) 
     { 
      return CreateFunctionNode(eq, order3Index, replaceText + "0"); 
     } 

     //no operators... 
     eq = eq.Replace("(", ""); 
     eq = eq.Replace(")", ""); 

     if (char.IsLetter(eq[0])) 
     { 
      return Parse(parenthesesText[eq], replaceText + "0"); 
     } 

     return new ValueNode(decimal.Parse(eq)); 
    } 

    private string GetNextReplaceText(string replaceText, int randomKeyIndex) 
    { 
     while (parenthesesText.ContainsKey(replaceText)) 
     { 
      replaceText = replaceText + randomKeyIndex.ToString(); 
     } 
     return replaceText; 
    } 

    private EvalNode CreateFunctionNode(string eq, int index, string randomKey) 
    { 
     FunctionNode func = new FunctionNode(); 
     func.Op = eq[index].ToString(); 
     func.Lhs = Parse(eq.Substring(0, index), randomKey); 
     func.Rhs = Parse(eq.Substring(index + 1), randomKey); 

     return func; 
    } 

    #endregion 

    /// <summary> 
    /// Find the first set of parentheses 
    /// </summary> 
    /// <param name="eq"></param> 
    /// <param name="leftParentIndex"></param> 
    /// <param name="rightParentIndex"></param> 
    private static void SetIndexes(string eq, ref int leftParentIndex, ref int rightParentIndex) 
    { 
     leftParentIndex = eq.IndexOf('('); 
     rightParentIndex = eq.IndexOf(')'); 
     int tempIndex = eq.IndexOf('(', leftParentIndex + 1); 

     while (tempIndex != -1 && tempIndex < rightParentIndex) 
     { 
      rightParentIndex = eq.IndexOf(')', rightParentIndex + 1); 
      tempIndex = eq.IndexOf('(', tempIndex + 1); 
     } 
    } 
} 

public struct Variable 
{ 
    public string Name; 
    public decimal Value; 

    public Variable(string n, decimal v) 
    { 
     Name = n; 
     Value = v; 
     } 
    } 
} 
+0

Warum nicht "EvalNode" zu einer Schnittstelle machen? Immer noch +1. – pyon

+0

Mit der anfänglichen Arbeit an der Evaluierung wollte ich nur den Code etwas zurückgeben. Die Evolution des Codes hat mich dazu gebracht, was gepostet wurde und du hast Recht, EvalNode könnte zu einer Schnittstelle geändert werden. –

+0

Fehler mit Klammern. ZB: '(1 + 3) + 2' –

1

MS hat ein Beispiel namens Dynamic Query Library. Es wird vom LINQ-Team bereitgestellt, um LINQ-Abfragen dynamisch zu erstellen: Dim Query = Northwind.Products.Where ("CategoryID = 2") Sie können überprüfen, ob es rudimentäre mathematische Funktionen bietet.

0

Ich habe Quelle für eine ultra kompakte (1 Klasse, < 10 KiB) Java Math Evaluator auf meiner Website veröffentlicht. Es sollte trivial sein, dies nach C# zu portieren. Es gibt andere da draußen, die mehr tun könnten, aber das ist sehr fähig, und es ist winzige.

Verwandte Themen