Analyseur simple pour les opérations arithmétiques

Pour l'étude, il était nécessaire d'écrire un analyseur des opérations arithmétiques, qui pourrait non seulement calculer les opérations les plus simples, mais aussi travailler avec des crochets et des fonctions.

Je n'ai pas trouvé de solutions toutes faites et adaptées pour moi sur Internet (certaines étaient trop compliquées, d'autres ne satisfaisaient pas complètement les conditions de ma tâche). Après un peu de tristesse, j'ai commencé à résoudre le problème moi-même et maintenant je veux partager mon code **** original avec la solution originale avec le monde.

Le premier problème que j'ai rencontré est celui des crochets. Non seulement ils doivent être exécutés en premier, mais les crochets peuvent également être à l'intérieur d'eux. Et ainsi de suite.

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

Exactement la même histoire avec des fonctions - dans les paramètres d'une fonction, il peut y avoir d'autres fonctions et même des expressions entières.

sqrt(22;log(4;2))

Mais plus à ce sujet plus tard. Vous devez d'abord analyser l'expression entière. Notez que nous pouvons rencontrer soit une parenthèse, soit un nombre, soit un opérande (+, -, *, /, ^), soit une fonction, soit une constante.

Créez les listes pour tout cela:

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

Et nous vérifierons chaque personnage tour à tour. Naturellement, si nous rencontrons le signe "+" ou "-" pas après le nombre, alors ce signe indique le nombre positif ou négatif, respectivement.

 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 = ""; } //     ,       

Dans l'exemple, la fonction GetParametrs a été ignorée. Il est nécessaire pour les cas où une fonction a 2 paramètres. Le fait est que vous ne pouvez pas faire un simple Split. Nous pouvons avoir cette expression:

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

Ainsi, l'expression est divisée en plus petites, et en outre, les valeurs des fonctions sont déjà calculées et substituées dans l'expression principale sous forme de nombres.

Les parenthèses peuvent être traitées selon le même principe - calculez-les immédiatement et remplacez-les par des nombres:

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

Retrouver l'indice de la parenthèse fermante est un peu plus compliqué. Vous ne pouvez pas simplement prendre le prochain crochet de fermeture. Cela ne fonctionnera pas pour les crochets imbriqués.

La partie principale du programme est écrite. Il reste à mettre en œuvre le calcul des expressions arithmétiques ordinaires. Afin de ne pas vous soucier de l'ordre des actions, j'ai décidé d'écrire l'expression en notation polonaise:

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

Et enfin, avec la pile, nous calculons la valeur de l'expression:

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

Si tout s'est bien passé, le résultat sera dans le résultat [0].

Lien vers GitHub avec le code complet

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


All Articles