Analisador simples para operações aritméticas

Para o estudo, foi necessário escrever um analisador de operações aritméticas, que pudesse calcular não apenas as operações mais simples, mas também trabalhar com colchetes e funções.

Não encontrei soluções prontas e adequadas para mim na Internet (algumas eram muito complicadas, outras não estavam satisfazendo completamente as condições da minha tarefa). Depois de um pouco de tristeza, comecei a resolver o problema sozinho e agora quero compartilhar meu código **** original com a solução original com o mundo.

O primeiro problema que encontrei são colchetes. Não apenas eles devem ser executados primeiro, para que os colchetes também possam estar dentro deles. E assim por diante

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

Exatamente a mesma história com funções - nos parâmetros da função pode haver outras funções e até expressões inteiras.

sqrt(22;log(4;2)

Mas mais sobre isso mais tarde. Primeiro você precisa analisar a expressão inteira. Observe que podemos encontrar um colchete, um número ou um operando (+, -, *, /, ^) ou uma função ou uma constante.

Crie as listas para essa coisa toda:

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

E vamos verificar cada personagem por vez. Naturalmente, se encontrarmos o sinal "+" ou "-" não após o número, esse sinal indica o número positivo ou 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 = ""; } //     ,       

No exemplo, a função GetParametrs foi ignorada. É necessário para casos em que uma função possui 2 parâmetros. O fato é que você não pode fazer uma simples divisão. Podemos ter esta expressão:

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

Assim, a expressão é dividida em menores e, além disso, os valores das funções já são calculados e substituídos na expressão principal como números.

Os colchetes podem ser processados ​​de acordo com o mesmo princípio - calcule-os imediatamente e substitua-os 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 o índice do colchete novamente é um pouco mais complicado. Você não pode simplesmente pegar o próximo colchete. Isso não funcionará para colchetes aninhados.

A parte principal do programa está escrita. Resta implementar o cálculo de expressões aritméticas comuns. Para não me preocupar com a ordem das ações, decidi escrever a expressão na notação polonesa:

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

E, finalmente, com a pilha, calculamos o valor da expressão:

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

Se tudo correu bem, o resultado estará no resultado [0].

Link para o GitHub com código completo

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


All Articles