Einfacher Parser für arithmetische Operationen

Für das Studium war es notwendig, einen Parser für arithmetische Operationen zu schreiben, der nicht nur die einfachsten Operationen berechnen, sondern auch mit Klammern und Funktionen arbeiten konnte.

Ich habe im Internet keine vorgefertigten und geeigneten Lösungen für mich gefunden (einige waren zu kompliziert, andere erfüllten die Bedingungen meiner Aufgabe nicht vollständig). Nach ein wenig Traurigkeit habe ich begonnen, das Problem selbst zu lösen, und jetzt möchte ich meinen ursprünglichen **** Code mit der ursprünglichen Lösung mit der Welt teilen.

Das erste Problem, auf das ich gestoßen bin, sind Klammern. Sie sollten nicht nur zuerst ausgeführt werden, sondern können auch Klammern enthalten. Usw.

(2+2)((22)+((22)(22)))

Genau die gleiche Geschichte mit Funktionen - in den Parametern der Funktion können andere Funktionen und sogar ganze Ausdrücke vorhanden sein.

sqrt(22;log(4;2))

Aber dazu später mehr. Zuerst müssen Sie den gesamten Ausdruck analysieren. Beachten Sie, dass wir entweder auf eine Klammer oder eine Zahl oder einen Operanden (+, -, *, /, ^) oder eine Funktion oder eine Konstante stoßen können.

Erstellen Sie die Listen für diese ganze Sache:

public static List<string> digits = new List<string>() { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "," }; public static List<string> operands = new List<string>() {"^", "/", "*", "+", "-"}; public static List<string> functions = new List<string>() { "sqrt", "sin", "cos", "log", "abs"}; public static List<string> brackets = new List<string>() { "(", ")" }; public static List<string> constants = new List<string>() { "pi" }; public static Dictionary<string, string> constantsValues = new Dictionary<string, string>() { ["pi"] = "3,14159265359" }; 

Und wir werden jedes Zeichen der Reihe nach überprüfen. Wenn wir das Zeichen "+" oder "-" nicht nach der Zahl getroffen haben, zeigt dieses Zeichen natürlich die positive bzw. negative Zahl an.

 for (int i = 0; i < expression.Length; i++) { if (brackets.Contains(expression[i].ToString())){ if (lastSymbol != ""){ symbols.Add(lastSymbol); lastSymbol = ""; } //  ,      symbols.Add(expression[i].ToString()); //    -        } else if (digits.Contains(expression[i].ToString()) || (expression[i] == ',' && lastSymbol.IndexOf(",") == -1)){ lastSymbol += expression[i]; } //    -     ,     else if(operands.Contains(expression[i].ToString())) { if (lastSymbol != ""){ symbols.Add(lastSymbol); lastSymbol = ""; } if (symbols.Count > 0 && operands.Contains(symbols[symbols.Count - 1]) || symbols.Count == 0) { string number = ""; switch (expression[i].ToString()) { case "-": number += "-"; break; case "+": number += "+"; break; } i++; while (i < expression.Length && digits.Contains(expression[i].ToString())){ number += expression[i]; i++; } symbols.Add(number); i--; } //   "-"  "+",   -        else symbols.Add(expression[i].ToString()); }else{ lastFunction += expression[i].ToString().ToLower(); //       =>      if (constants.Contains(lastFunction)) { symbols.Add(constantsValues[lastFunction]); lastFunction = ""; } //     -        else if (functions.Contains(lastFunction)) { int functionStart = i + 1; //    int functionEnd = 0; int bracketsSum = 1; for (int j = functionStart + 1; j < expression.Length; j++) { if (expression[j].ToString() == "(") bracketsSum++; if (expression[j].ToString() == ")") bracketsSum--; if (bracketsSum == 0) { functionEnd = j; i = functionEnd; break; } } //   .    - ,      char[] buffer = new char[functionEnd - functionStart - 1]; expression.CopyTo(functionStart + 1, buffer, 0, functionEnd - functionStart - 1); string functionParametrs = new string(buffer); if (lastFunction == "sqrt"){ var parametrs = GetParametrs(functionParametrs); symbols.Add(Math.Pow(CalculateExpression(parametrs[0]), 1 / CalculateExpression(parametrs[1])).ToString()); } if (lastFunction == "log"){ var parametrs = GetParametrs(functionParametrs); symbols.Add(Math.Log(CalculateExpression(parametrs[0]), CalculateExpression(parametrs[1])).ToString()); } if (lastFunction == "sin") symbols.Add(Math.Sin(CalculateExpression(functionParametrs)).ToString()); if (lastFunction == "cos") symbols.Add(Math.Cos(CalculateExpression(functionParametrs)).ToString()); if (lastFunction == "abs") symbols.Add(Math.Abs(CalculateExpression(functionParametrs)).ToString()); //    lastFunction = ""; } } } if (lastSymbol != ""){ symbols.Add(lastSymbol); lastSymbol = ""; } //     ,       

Im Beispiel wurde die GetParametrs-Funktion übersprungen. Es wird für Fälle benötigt, in denen eine Funktion 2 Parameter hat. Tatsache ist, dass Sie keinen einfachen Split machen können. Wir können diesen Ausdruck haben:

sqrt(22;log(4;2))

 public static List<string> GetParametrs(string functionParametrs){ int bracketsSum = 0; int functionEnd = 0; for (int j = 0; j < functionParametrs.Length; j++){ if (functionParametrs[j].ToString() == "(") bracketsSum++; if (functionParametrs[j].ToString() == ")") bracketsSum--; if (functionParametrs[j].ToString() == ";" && bracketsSum == 0){ functionEnd = j; break; } } var buffer = new char[functionEnd]; functionParametrs.CopyTo(0, buffer, 0, functionEnd); string firstParametr = new string(buffer); buffer = new char[functionParametrs.Length - functionEnd - 1]; functionParametrs.CopyTo(functionEnd + 1, buffer, 0, functionParametrs.Length - functionEnd - 1); string secondParametr = new string(buffer); return ( new List<string>() { firstParametr, secondParametr } ); } 

Der Ausdruck ist also in kleinere unterteilt, und außerdem werden die Werte der Funktionen bereits berechnet und als Zahlen in den Hauptausdruck eingesetzt.

Klammern können nach dem gleichen Prinzip verarbeitet werden - berechnen Sie sie sofort und ersetzen Sie sie durch Zahlen:

 while (symbols.Contains("(")) { int bracketsStart = 0; int bracketsEnd = 0; int bracketsSum = 0; for (int i = 0; i < symbols.Count; i++) { if (symbols[i] == "(") { bracketsStart = i; bracketsSum = 1; break; } } for (int i = bracketsStart + 1; i < symbols.Count; i++) { if (symbols[i] == "(") bracketsSum++; if (symbols[i] == ")") bracketsSum--; if (bracketsSum == 0) { bracketsEnd = i; break; } } string bracketsExpression = ""; for (int i = bracketsStart + 1; i < bracketsEnd; i++) bracketsExpression += symbols[i]; symbols[bracketsStart] = CalculateExpression(bracketsExpression).ToString(); symbols.RemoveRange(bracketsStart + 1, bracketsEnd - bracketsStart); } 

Das Wiederfinden des Index der schließenden Klammer ist etwas komplizierter. Sie können nicht einfach die nächste schließende Klammer nehmen. Dies funktioniert nicht für verschachtelte Klammern.

Der Hauptteil des Programms ist geschrieben. Es bleibt die Berechnung gewöhnlicher arithmetischer Ausdrücke zu implementieren. Um mir keine Sorgen um die Reihenfolge der Aktionen zu machen, habe ich beschlossen, den Ausdruck in polnischer Notation aufzuschreiben:

 foreach(var j in operands){ //           operands var flagO = true; while (flagO){ flagO = false; for (int i = 0; i < symbols.Count; i++){ if (symbols[i] == j){ symbols[i - 1] = symbols[i - 1] + " " + symbols[i + 1] + " " + j; symbols.RemoveRange(i, 2); flagO = true; break; } } } } 

Und schließlich berechnen wir mit dem Stapel den Wert des Ausdrucks:

 List<string> result = new List<string>(); string[] temp = symbols[0].Split(' '); for (int i = 0; i < temp.Length; i++) { if (operands.Contains(temp[i])) { if (temp[i] == "^") { result[result.Count - 2] = Math.Pow(double.Parse(result[result.Count - 2]), double.Parse(result[result.Count - 1])).ToString(); result.RemoveRange(result.Count - 1, 1); } if (temp[i] == "+") { result[result.Count - 2] = (double.Parse(result[result.Count - 2]) + double.Parse(result[result.Count - 1])).ToString(); result.RemoveRange(result.Count - 1, 1); } if (temp[i] == "-") { result[result.Count - 2] = (double.Parse(result[result.Count - 2]) - double.Parse(result[result.Count - 1])).ToString(); result.RemoveRange(result.Count - 1, 1); } if (temp[i] == "*") { result[result.Count - 2] = (double.Parse(result[result.Count - 2]) * double.Parse(result[result.Count - 1])).ToString(); result.RemoveRange(result.Count - 1, 1); } if (temp[i] == "/") { result[result.Count - 2] = (double.Parse(result[result.Count - 2]) / double.Parse(result[result.Count - 1])).ToString(); result.RemoveRange(result.Count - 1, 1); } } else result.Add(temp[i]); } 

Wenn alles gut gegangen ist, ist das Ergebnis Ergebnis [0].

Link zu GitHub mit vollständigem Code

Source: https://habr.com/ru/post/de439966/


All Articles