在学习《数据结构》这门课的时候,老是会想到模拟计算器的运算。虽说实验里面也有设计逆波兰计算器的题目,但它只考察了栈和队列的操作思想,没有考虑到运算符的优先级以及复杂组合情况(比如多层括号),因此其实用性并不大。
今天试着写了一下,看似很简单,还是花费了一段时间的。
处理纯符号(+-)字符串(1)
这是最简单的情况。
1. 首先+-的运算等级最低,而且没有括号的限制。
2. 其次我们只需要把相应的数字部分读到一个对象中,运算符存放到另外一个对象里面。
3. 最后将字符串数字转换为int型,运算符转换为char型,依次计算得出结果。
下面直接贴代码了:
public static double HandleJJFH(String s) {
int q = 0;
int i = 0;
Queue<Double> Q = new LinkedList<Double>();
String strnum = null;
String fuhao = new String("");
while (i < s.length()) {
if (JudgeNumAndDot(s.charAt(i)) && i + 1 != s.length()) {
i++;
continue;
}
if (JudgeFH(s.charAt(i)) || (i + 1 == s.length())) {
//截取数字部分
if (i + 1 == s.length())
strnum = s.substring(q, i + 1);
if (JudgeFH(s.charAt(i))) {
fuhao += s.charAt(i);
strnum = s.substring(q, i);
}
Q.offer(StrToNum(strnum));
q = i + 1;
i++;
}
}
//取出头部元素
Double a = Q.poll();
i = 0;
while (!Q.isEmpty()) {
a = TwoCalu(a, fuhao.charAt(i), Q.poll());
i++;
}
return a;
}
需要注意的是Stack和Queue的泛型参数必须是类类型。
用到的辅助函数如下:
public static double TwoCalu(double a, char c, double b);
public static double StrToNum(String s);
同样的*/运算符也适用
处理带有(+-*/)字符串(2)
*/的运算等级要比+-的高,因此情况稍微复杂一点。
大致思路是:将处于相邻+-之间的字符串截取下来。那么截取下来的字符串只有*/运算符或者没有,这样情况便简化成了处理纯符号(+-)字符串(1)
直接贴代码:
//该方法可能要做优化
public double HandleAll_NonBracket(String s) {
if(s.charAt(0) == '-' && JudgeStrnum(s.substring(1,s.length())))//可能s为-3
return StrToNum(s);
int i = 0, q = 0, countJJ = 0;
String CCstr = "", fuhao = "";
Queue<Double> Q = new LinkedList<Double>();
while (i < s.length()) {
//数字或*/
if (JudgeNumAndDot(s.charAt(i)) || JudgeCC(s.charAt(i))) {
i++;
continue;
}
//发现+/-运算符
if (JudgeJJ(s.charAt(i))) {
countJJ++;
fuhao += s.charAt(i);
//发现第一个+-
if (countJJ == 1) {
CCstr = s.substring(0, i);
//如果含有*/
if (JudgeStrcc(CCstr))
Q.offer(HandleJJFH(CCstr));
else
Q.offer(StrToNum(CCstr));
}
//发现非第一个+-
else {
//截取是否含有连续*/的字符串
CCstr = s.substring(q + 1, i);
//中间纯数字
if (JudgeStrnum(CCstr))
Q.offer(StrToNum(CCstr));
//中间包含*/
if (JudgeStrcc(CCstr))
Q.offer(HandleJJFH(CCstr));
}
q = i;
i++;
}
}
if (countJJ == 0)
return HandleJJFH(s);
CCstr = s.substring(q + 1, s.length());
//如果含有*/
if (JudgeStrcc(CCstr))
Q.offer(HandleJJFH(CCstr));
else//如果纯数字
Q.offer(StrToNum(CCstr));
//取出头部并且返回头部值
double a = Q.poll();
i = 0;
while (!Q.isEmpty()) {
a = TwoCalu(a, fuhao.charAt(i), Q.poll());
i++;
}
return a;
}
用到的辅助函数如下:
public static boolean JudgeNum(char c);
public static boolean JudgeCC(char c);
public static double HandleJJFH(String s);
public static double StrToNum(String s);
public static double TwoCalu(double a,char c,double b);
处理带( )的字符串(3)
带有( )的字符串运算优先级是最高的,但无非也就是首先考虑它的运算,与*/类似。
我们首先考虑只有一对括号的字符串:
把带( )的String a截取下来,计算得出结果double b,并将结果b转换为String类型返回替代原有a的位置,这样情况便简化成了处理带有(+-*/)字符串(2)
Queue< Kuohao > L ,Stack< Kuohao > R是需要借助的数据结构。L用于记录左括号的位置,并依次入队列;R用于记录右括号的位置,并且依次入栈。这样左括号和右括号便能够匹配成功。截取括号便有了成功的把握。
多括号的字符串处理
涉及到多括号处理的时候,最简单的方法是使用递归。
public double HandleBracket(String s) {
Queue<Kuohao> L = new LinkedList<Kuohao>();
Stack<Kuohao> R = new Stack<Kuohao>();
String t = "";
int num = KuohaoNum(s, L, R);
if (num == 0)
return HandleAll_NonBracket(s);
else {
Kuohao q = L.poll();
Kuohao p = R.pop();
String str = s.substring(q.index + 1, p.index);//将最外层括号去掉
t += s.substring(0, q.index);//括号左边部分字符串
double temp = HandleBracket(str);//处理左右括号中间字符串
String midres = NumToStr(temp);
if (t.length() == 0)//例如(1+2*3),那么t=1+2*3
t = t + midres;
if (t.charAt(t.length() - 1) == '+' && midres.charAt(0) == '-')//可能会出现123+ -4的情况
t = t.substring(0, t.length() - 1);
if (!t.equals(midres))//例如(1+2.5*4),那么t=11,midres=11,但结果只能返回7而不是14
t = t + midres;
t = t + s.substring(p.index + 1, s.length());
return HandleAll_NonBracket(t);
}
}
用到的辅助函数如下:
public double KuohaoNum(String s,Queue<Kuohao> left,Stack<Kuohao> right);
public double HandleAll(String s);
public static String NumToStr(double t);
计算器的运算过程大致就是这样了!
源代码
不足之处
- 对于用户输入的字符串并未进行合法性的检查,因此计算时程序可能会出现异常或报错。
- 递归虽然通俗易懂,但是执行效率低,方法需要改进。
- 一些辅助函数过于冗余,耦合性较高。
- 最终程序应以图形界面的方式显示。
感触
看似简单的计算器实则大有门道。对于非程序员而言,他们可能对实现这样的功能不屑一顾,但对程序员却是极大的挑战。就像现在的我,熬夜的痛苦谁人知!果然是术业有专攻,以后不敢小瞧其它专业了!