📜 ⬆️ ⬇️

Simple parser for arithmetic operations

For study, it was necessary to write an arithmetic operations parser that could count not only simple operations, but also work with brackets and functions.

I did not find ready and suitable solutions for me on the Internet (some were too complex, others were not completely satisfying the conditions of my task). Having got a little sad, I started solving the problem on my own and now I want to share my r **** code with the original solution with the world.

The first problem I encountered is brackets. Not only should they be executed first, brackets can also be inside them. And so on.

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

Exactly the same story with functions - in the parameters of the function can be other functions and even whole expressions.

sqrt(22;log(4;2))

But more about that a bit later. First you need to parse the whole expression. Note that we can have either a bracket, or a number, or an operand (+, -, *, /, ^), or a function, or a constant.

Create lists for this whole business:

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

And we will check each character in turn. Naturally, if we met the sign "+" or "-" not after the digit, then this sign means positive or negative number, respectively.

 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 = ""; } // Если последним символом была цифра, не забываем его добавить в список 

In the example, the GetParametrs function skipped. It is needed for cases where the function has 2 parameters. The fact is that you can not make a simple Split. We may have the following 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 } ); } 

So, the expression is broken down into smaller ones, and in addition, the values ​​of the functions are already calculated and substituted into the main expression as numbers.

The brackets can be processed according to the same principle - immediately count them and substitute them as numbers:

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

Finding the index of the closing bracket again is a bit more complicated. You can't just take the next closing bracket. This does not work for nested brackets.

The main part of the program is written. It remains to realize the calculation of ordinary arithmetic expressions. In order not to bathe with the order of actions, I decided to write the expression in Polish notation:

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

Finally, with the stack, we calculate the value of the 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]); } 

If everything went well, the result [0] will be the result.

Link to GitHub with full code

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