Здравствуйте друзья! Хочу вам рассказать об использовании алгоритма Обратной польской записи для создания калькулятора на языке C#.
О самом алгоритме рассказывать не буду, так как такая тема уже поднималась. Заострю внимание на конкретной программе.
Наша программа будет состоять из двух частей:
1) Класс RPN (Reverse Polish Notation) — тут будет реализована работа алгоритма
2) Класс Program — тут, собственно, будет реализовано использование класса RPN — писать будем в консоли, что бы было понятнее.
Класс будет содержать ряд методов:
Это 3 основных метода класса, опишем их подробнее:
Calculate — метод общедоступный, ему будет передаваться выражение в виде строки, и он будет возвращать результат вычисления. Результат он будет получать используя другие методы.
GetExpression — метод, которому основной метод Calculate будет передавать выражение, и получать его уже в постфиксной записи.
Counting — метод, который получая постфиксную запись выражения будет вычислять его результат.
Так же, помимо 3 основных методов, в классе будет еще 3 метода «обеспечения», это:
IsDelimeter — возвращает true, если проверяемый символ — разделитель
IsOperator — возвращает true, если проверяемый символ — оператор
GetPriority — метод возвращает приоритет проверяемого оператора, нужно для сортировки операторов
Сначала рассмотрим неосновные методы, они простые, так что проблем с пониманием быть не должно:
IsDelimeter:
IsOperator:
GetPriority:
А теперь основные методы:
Calculate:
GetExpression:
Counting:
Наш класс готов!
Тут все просто, думаю объяснять большого смысла нет, просто прокомментирую:
Внимание! Ошибки!
Хочу обратить ваше внимание на одну вещь! В нашем классе я не реализовывал проверку входных данных, поэтому при некорректном вводе будут вылетать исключения!
О самом алгоритме рассказывать не буду, так как такая тема уже поднималась. Заострю внимание на конкретной программе.
Об алгоритме
Алгоритм Обратной польской нотации (ОПН) — форма записи математических выражений, в которой операнды расположены перед знаками операций. Также именуется как обратная польская запись, обратная бесскобочная запись (ОБЗ). © Wikipedia
Наша программа будет состоять из двух частей:
1) Класс RPN (Reverse Polish Notation) — тут будет реализована работа алгоритма
2) Класс Program — тут, собственно, будет реализовано использование класса RPN — писать будем в консоли, что бы было понятнее.
Часть первая: класс RPN
Класс будет содержать ряд методов:
class RPN
{
//Метод Calculate принимает выражение в виде строки и возвращает результат, в своей работе использует другие методы класса
static public double Calculate(string input)
{ ... }
//Метод, преобразующий входную строку с выражением в постфиксную запись
static private string GetExpression(string input)
{ ... }
//Метод, вычисляющий значение выражения, уже преобразованного в постфиксную запись
static private double Counting(string input)
{ ... }
}
Это 3 основных метода класса, опишем их подробнее:
Calculate — метод общедоступный, ему будет передаваться выражение в виде строки, и он будет возвращать результат вычисления. Результат он будет получать используя другие методы.
GetExpression — метод, которому основной метод Calculate будет передавать выражение, и получать его уже в постфиксной записи.
Counting — метод, который получая постфиксную запись выражения будет вычислять его результат.
Так же, помимо 3 основных методов, в классе будет еще 3 метода «обеспечения», это:
IsDelimeter — возвращает true, если проверяемый символ — разделитель
IsOperator — возвращает true, если проверяемый символ — оператор
GetPriority — метод возвращает приоритет проверяемого оператора, нужно для сортировки операторов
Сначала рассмотрим неосновные методы, они простые, так что проблем с пониманием быть не должно:
IsDelimeter:
//Метод возвращает true, если проверяемый символ - разделитель ("пробел" или "равно")
static private bool IsDelimeter(char c)
{
if ((" =".IndexOf(c) != -1))
return true;
return false;
}
IsOperator:
//Метод возвращает true, если проверяемый символ - оператор
static private bool IsOperator(char с)
{
if (("+-/*^()".IndexOf(с) != -1))
return true;
return false;
}
GetPriority:
//Метод возвращает приоритет оператора
static private byte GetPriority(char s)
{
switch (s)
{
case '(': return 0;
case ')': return 1;
case '+': return 2;
case '-': return 3;
case '*': return 4;
case '/': return 4;
case '^': return 5;
default: return 6;
}
}
А теперь основные методы:
Calculate:
//"Входной" метод класса
static public double Calculate(string input)
{
string output = GetExpression(input); //Преобразовываем выражение в постфиксную запись
double result = Counting(output); //Решаем полученное выражение
return result; //Возвращаем результат
}
GetExpression:
static private string GetExpression(string input)
{
string output = string.Empty; //Строка для хранения выражения
Stack<char> operStack = new Stack<char>(); //Стек для хранения операторов
for (int i = 0; i < input.Length; i++) //Для каждого символа в входной строке
{
//Разделители пропускаем
if (IsDelimeter(input[i]))
continue; //Переходим к следующему символу
//Если символ - цифра, то считываем все число
if (Char.IsDigit(input[i])) //Если цифра
{
//Читаем до разделителя или оператора, что бы получить число
while (!IsDelimeter(input[i]) && !IsOperator(input[i]))
{
output += input[i]; //Добавляем каждую цифру числа к нашей строке
i++; //Переходим к следующему символу
if (i == input.Length) break; //Если символ - последний, то выходим из цикла
}
output += " "; //Дописываем после числа пробел в строку с выражением
i--; //Возвращаемся на один символ назад, к символу перед разделителем
}
//Если символ - оператор
if (IsOperator(input[i])) //Если оператор
{
if (input[i] == '(') //Если символ - открывающая скобка
operStack.Push(input[i]); //Записываем её в стек
else if (input[i] == ')') //Если символ - закрывающая скобка
{
//Выписываем все операторы до открывающей скобки в строку
char s = operStack.Pop();
while (s != '(')
{
output += s.ToString() + ' ';
s = operStack.Pop();
}
}
else //Если любой другой оператор
{
if (operStack.Count > 0) //Если в стеке есть элементы
if (GetPriority(input[i]) <= GetPriority(operStack.Peek())) //И если приоритет нашего оператора меньше или равен приоритету оператора на вершине стека
output += operStack.Pop().ToString() + " "; //То добавляем последний оператор из стека в строку с выражением
operStack.Push(char.Parse(input[i].ToString())); //Если стек пуст, или же приоритет оператора выше - добавляем операторов на вершину стека
}
}
}
//Когда прошли по всем символам, выкидываем из стека все оставшиеся там операторы в строку
while (operStack.Count > 0)
output += operStack.Pop() + " ";
return output; //Возвращаем выражение в постфиксной записи
}
Counting:
static private double Counting(string input)
{
double result = 0; //Результат
Stack<double> temp = new Stack<double>(); //Dhtvtyysq стек для решения
for (int i = 0; i < input.Length; i++) //Для каждого символа в строке
{
//Если символ - цифра, то читаем все число и записываем на вершину стека
if (Char.IsDigit(input[i]))
{
string a = string.Empty;
while (!IsDelimeter(input[i]) && !IsOperator(input[i])) //Пока не разделитель
{
a += input[i]; //Добавляем
i++;
if (i == input.Length) break;
}
temp.Push(double.Parse(a)); //Записываем в стек
i--;
}
else if (IsOperator(input[i])) //Если символ - оператор
{
//Берем два последних значения из стека
double a = temp.Pop();
double b = temp.Pop();
switch (input[i]) //И производим над ними действие, согласно оператору
{
case '+': result = b + a; break;
case '-': result = b - a; break;
case '*': result = b * a; break;
case '/': result = b / a; break;
case '^': result = double.Parse(Math.Pow(double.Parse(b.ToString()), double.Parse(a.ToString())).ToString()); break;
}
temp.Push(result); //Результат вычисления записываем обратно в стек
}
}
return temp.Peek(); //Забираем результат всех вычислений из стека и возвращаем его
}
Наш класс готов!
Часть 2: класс Program
Тут все просто, думаю объяснять большого смысла нет, просто прокомментирую:
class Program
{
static void Main(string[] args)
{
while (true) //Бесконечный цикл
{
Console.Write("Введите выражение: "); //Предлагаем ввести выражение
Console.WriteLine(RPN.Calculate(Console.ReadLine())); //Считываем, и выводим результат
}
}
}
Внимание! Ошибки!
Хочу обратить ваше внимание на одну вещь! В нашем классе я не реализовывал проверку входных данных, поэтому при некорректном вводе будут вылетать исключения!