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 myr **** 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 ) ∗ ( ( 2 ∗ 2 ) + ( ( 2 ∗ 2 ) ∗ ( 2 ∗ 2 ) ) )
Exactly the same story with functions — other functions and even entire expressions can be found in the parameters of the function.
s q r t ( 2 ∗ 2 ; l o g ( 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:
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.
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:
s q r t ( 2 ∗ 2 ; l o g ( 4 ; 2 ) )
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:
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:
Finally, with the stack, we calculate the value of the expression:
If everything went well, the result [0] will be the result.
→ Link to GitHub with full code
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
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 ) ∗ ( ( 2 ∗ 2 ) + ( ( 2 ∗ 2 ) ∗ ( 2 ∗ 2 ) ) )
Exactly the same story with functions — other functions and even entire expressions can be found in the parameters of the function.
s q r t ( 2 ∗ 2 ; l o g ( 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:
publicstatic List<string> digits = new List<string>() { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "," };
publicstatic List<string> operands = new List<string>() {"^", "/", "*", "+", "-"};
publicstatic List<string> functions = new List<string>() { "sqrt", "sin", "cos", "log", "abs"};
publicstatic List<string> brackets = new List<string>() { "(", ")" };
publicstatic List<string> constants = new List<string>() { "pi" };
publicstatic 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()); // Если встретили скобку - без разбирательств добавляем ее в массив символов
}
elseif (digits.Contains(expression[i].ToString()) || (expression[i] == ',' && lastSymbol.IndexOf(",") == -1)){
lastSymbol += expression[i];
} // если встретили цифру - добавляем ее в специальную переменную, чтобы не разделять числоelseif(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 = "";
} // Если перед нами константа - добавляем ее в список символов как значениеelseif (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 = newchar[functionEnd - functionStart - 1];
expression.CopyTo(functionStart + 1, buffer, 0, functionEnd - functionStart - 1);
string functionParametrs = newstring(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:
s q r t ( 2 ∗ 2 ; l o g ( 4 ; 2 ) )
publicstatic 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 = newchar[functionEnd];
functionParametrs.CopyTo(0, buffer, 0, functionEnd);
string firstParametr = newstring(buffer);
buffer = newchar[functionParametrs.Length - functionEnd - 1];
functionParametrs.CopyTo(functionEnd + 1, buffer, 0, functionParametrs.Length - functionEnd - 1);
string secondParametr = newstring(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){ // Порядок выполнения операций зависит от порядка расположения знаков в массиве operandsvar 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