Analizador simple para operaciones aritméticas

Para el estudio, era necesario escribir un analizador de operaciones aritméticas, que pudiera calcular no solo las operaciones más simples, sino también trabajar con corchetes y funciones.

No encontré soluciones preparadas y adecuadas para mí en Internet (algunas eran demasiado complicadas, otras no satisfacían por completo las condiciones de mi tarea). Después de un poco de tristeza, comencé a resolver el problema yo mismo y ahora quiero compartir mi código **** original con la solución original con el mundo.

El primer problema con el que me encontré es entre paréntesis. No solo deben ejecutarse primero, sino que los corchetes también pueden estar dentro de ellos. Y así sucesivamente.

(2+2)∗((2∗2)+((2∗2)∗(2∗2)))

La misma historia exacta con funciones: en los parámetros de una función puede haber otras funciones e incluso expresiones completas.

sqrt(2∗2;log(4;2))

Pero más sobre eso más tarde. Primero necesitas analizar toda la expresión. Tenga en cuenta que podemos encontrar un paréntesis, o un número, o un operando (+, -, *, /, ^), o una función, o una constante.

Crea las listas para todo esto:

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

Y comprobaremos cada personaje por turno. Naturalmente, si encontramos el signo "+" o "-" no después del número, entonces este signo indica el número positivo o negativo, respectivamente.

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

En el ejemplo, se omitió la función GetParametrs. Es necesario para los casos en que una función tiene 2 parámetros. El hecho es que no puedes hacer una división simple. Podemos tener esta expresión:

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

Entonces, la expresión se divide en otras más pequeñas y, además, los valores de las funciones ya están calculados y sustituidos en la expresión principal como números.

Los corchetes se pueden procesar de acuerdo con el mismo principio: calcúlelos inmediatamente y sustitúyalos como números:

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

Encontrar el índice del corchete nuevamente es un poco más complicado. No puedes simplemente tomar el siguiente corchete de cierre. Esto no funcionará para paréntesis anidados.

La parte principal del programa está escrita. Queda por implementar el cálculo de expresiones aritméticas ordinarias. Para no preocuparme por el orden de las acciones, decidí escribir la expresión en notación polaca:

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

Y finalmente, con la pila, calculamos el valor de la expresión:

 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 todo salió bien, el resultado será el resultado [0].

→ Enlace a GitHub con código completo

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


All Articles