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
Exatamente a mesma história com funções - nos parâmetros da função pode haver outras funções e até expressões inteiras.
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 = ""; }
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:
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){
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