简单计算器(逆波兰表达式)
for LeetCode 逆波兰表达式求值 , 基本计算器, 基本计算器II
之前其实在编译原理课上用lex写过一个计算器的小作业,但是对于计算器的universal的实现依然是一知半解,因此就借着做LeetCode自己写了一个比较通用的版本。
我们在离散数学中学过后缀表示法,也叫逆波兰表示法(Reverse Polish notation)。计算机可以很轻松的算出逆波兰表示法下的算式,这也是逆波兰表达式求值这题要求我们写的东西。因此,写一个支持加减乘除括号等操作的简单运算器,最主要的问题是如何将一个算式转化为逆波兰表示。
下面是我处理时的几个小问题
-
取负数符号和减号、取正数符号和加号
这也是基本计算器这一题里面大多数人的问题。 考虑到取负和取正都是一元运算符,这些符号的特征是该符号的前一个有效字符都不是数字或者’)’,而加减号的前一个有效字符必定是数字。 因此我们可以引入一个
pre
的变量记录上一个有效字符来进行区别。注意取正符号其实最后在进行逆波兰计算的时候是不需要计算的,所以我们可以直接忽略掉它。但是取负符号是需要进行转换的,它的优先级介于乘除和加减之间,又因为它的符号和减号一致,但优先级不同,所以我在代码中使用特殊符号‘_’用作取负运算。
-
括号的处理
括号是优先级最高的运算符。其实括号里面都是算式,我们以括号为分界一个算式一个算式的进行转换
-
运算符优先级
从左到右遍历算式时,我们依次将数字push入数据栈,但是符号需要考虑优先级。
我们人工去检查哪一项先算的时候其实是多看了好几个符号,确认当前符号的下一个符号优先级要小于等于当前符号时,当前符号才可以被执行。因此代码中也是这么体现的:
当扫描到的当前符号优先级大于符号栈顶符号时,将该符号推入符号栈
当扫描到的当前符号优先级小于等于符号栈顶符号时,将符号栈中的符号依次推出到数据栈,直到:
- 栈顶符号为’(’ 或 符号栈为空
- 栈顶符号的优先级小于当前符号
然后再将当前符号推入符号栈。
-
数据栈从栈底到栈顶的顺序即为逆波兰序
ps: 用到了stack to vector的一个转化方法,看上去很有意思。stack默认的存储方式是deque,但是如果改成vector的话就能比较优雅的实现stack to vector,代价是数据量较大的时候vector实现的stack会有比deque更大的来自reallocation的代价。可以用类似vector<int> a(anothervec.begin(),anothervec.end())
的方法将用vector存储的stack转化为vector。 下次有机会要看看vector的具体实现。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <stack>
#include <map>
#include <math.h>
using namespace std;
class Calculater
{
private:
map<char, int> priority = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'_', 3}, {'^', 4}, {'(', 5}}; // '_'作为取负数的符号与减号做区别
bool isOperand(char c)
{
return (c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7' || c == '8' || c == '9');
}
//判断是否为运算符
bool isOpertor(char c)
{
return (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')' || c=='^');
}
vector<string> Transform2RPN(string &a)
{
vector<string> RPN;
stack<string, std::vector<string>> operand; // 使用vector来存储而非默认的deque
stack<char> opertor;
char pre = '-';
for (int i = 0; i < a.length(); i++)
{
int res = 0;
while (isOperand(a[i]))
{
res = (a[i] - '0') + res * 10;
if (i + 1 == a.length() || isOperand(a[i + 1]) == false)
{
operand.push(to_string(res));
break;
}
i++;
}
if (isOpertor(a[i]))
{
// 负数的符号
if (a[i] == '-' && (isOpertor(pre) && pre != ')'))
{
a[i] = '_';
}
else if (a[i] == '+' && isOpertor(pre) && pre != ')')
{
continue;
}
if (a[i] == '(')
{
opertor.push(a[i]);
}
else if (a[i] == ')')
{
while (opertor.top() != '(')
{
char op = opertor.top();
opertor.pop();
operand.push(string(1, op));
}
//将'('出栈
opertor.pop();
}
else
{
if (opertor.empty())
{
opertor.push(a[i]);
continue;
}
if (opertor.top() == '(')
{
opertor.push(a[i]);
continue;
}
//(b) 若比运算符堆栈栈顶的运算符优先级高,则直接存入运算符堆栈。
if (priority[a[i]] > priority[opertor.top()])
{
opertor.push(a[i]);
}
else
{
// (c) 若比运算符堆栈栈顶的运算符优先级低或相等,则输出栈顶运算符到操作数堆栈,并将当前运算符压入运算符堆栈。
while (!opertor.empty() && opertor.top()!='(' && priority[a[i]] <= priority[opertor.top()])
{
char op = opertor.top();
operand.push(string(1, op));
opertor.pop();
}
opertor.push(a[i]);
}
}
}
if (a[i] == ' ')
continue;
else
{
pre = a[i];
}
}
while (!opertor.empty())
{
char op = opertor.top();
operand.push(string(1, op));
opertor.pop();
}
RPN.assign(&operand.top()+1-operand.size(),&operand.top()+1); // 优雅的将stack 转换为vector https://stackoverflow.com/questions/4346010/copy-stdstack-into-an-stdvector
/*
while (!operand.empty())
{
string op = operand.top();
RPN.push_back(op);
operand.pop();
}
reverse(RPN.begin(), RPN.end()); */
return RPN;
}
int CalculateRPN(vector<string> &RPN)
{
stack<int> s;
for (int i = 0; i < RPN.size(); i++)
{
if (RPN[i] == "+")
{
int t = s.top();
s.pop();
s.top() += t;
}
else if (RPN[i] == "-")
{
int t = s.top();
s.pop();
s.top() -= t;
}
else if (RPN[i] == "*")
{
int t = s.top();
s.pop();
s.top() *= t;
}
else if (RPN[i] == "/")
{
int t = s.top();
s.pop();
s.top() /= t;
}
else if (RPN[i] == "_")
{
int t = s.top();
s.top() = -s.top();
}
else if (RPN[i] == "^")
{
int t = s.top();
s.pop();
s.top() = pow(s.top(),t);
}
else
{
s.push(stoi(RPN[i]));
}
}
return s.top();
}
public:
int calculate(string a)
{
vector<string> RPN = Transform2RPN(a);
return CalculateRPN(RPN);
}
};
int main()
{
Calculater c;
string in;
cin >> in;
cout << c.calculate(in) << endl;
}