基本要求
描述
获取一个表示四则运算算式的字符串,计算它的结果并输出。要求程序有基本的判断能力,能够判断这个式子是否正确,能够判断其中的数字的正负号。用Java代码实现。
样例
- 程序识别错误能力:
输入: 12312..5+1546
输出: 请输入正确的算式
输入:((1+5)*3
输出: 请输入正确的算式
- 正负号判断能力:
输入: 1++5
输出: 计算结果为:6
输入: 1--5
输出: 计算结果为6
- 小数点判断能力:
输入: 1/.1
输出: 计算结果为10
输入: 1+.5
输出: 计算结果为1.5
- 综合判断
输入: 1+-(10 * -.1+5)
输出: 计算结果为-3
基本思路
首先进行四则运算的设计,不考虑小数点判断、正负号判断和错误式子判断的情况。
由于运算符号优先度不同,比如1+5*2先运算乘法,括号也会改变运算顺序,所以需要给运算符(包括括号在内)设置优先级,进行比较。
而设置优先级的情况下用栈比较好,栈的特点是先进先出,后进后出,相当于栈顶的优先级一定高于非栈顶元素,出栈顺序与优先级对应。
乘除的有限级高于加减号;左括号的优先级应该高于一切,因为括号内的式子应该最先运算,右括号比较特殊,优先级应该最低,但是符号栈的第一个左括号出栈后就会失效。
设置两个栈,一个用于存放数字,一个用于存放运算符。下文中分别叫数字栈和符号栈。
经过计算后确定的优先级
符号 | 优先级 |
+ | 1 |
– | 1 |
* | 2 |
/ | 2 |
( | 3 |
) | -3 |
# | -4 |
其中#是结束符,加在字符串末尾。(感觉这个#可能有点鸡肋,大家可以帮我思考一下这个#有没有必要存在)
算法描述
文字描述
1、在字符串末尾加上“#”,字符栈首先进栈一个#;
2、从第一个字符开始,循环读取字符串,读取到非运算符则拼取为数字,进栈数字栈,读取到运算符,则判断:
若若准备进栈元素优先级高于栈顶元素优先级:
进栈并继续读取;
若准备进栈元素优先级不高于栈顶元素优先级且栈顶元素优先级+准备进栈元素优先级=0:
符号栈栈顶出栈;(去括号)
否则若当前栈顶不是#:
符号栈栈顶出栈,数字栈出栈两个,进行运算后,运算结果进栈数字栈,并继续判断栈顶元素优先级
3、字符串全部读取完毕,判断符号栈栈顶是否为起始符,若不是则提示算式错误,否则数字栈栈顶元素出栈作为计算结果。
伪Java代码
/**
*formula传入经过处理的(结尾已经加上#)的表示算式的字符串
*返回计算的结果
*/
public float calculate(String formula)
{
初始化数字栈;
初始化符号栈; //进栈一个#
StringBuffer 数字字符串; //用于存放读取到的数字,将单个的数字字符连接成字符串
for (int i = 0;i < formula.length();i ++)
{
if (!formula.charAt(i) 是运算符)
数字字符串.append(formula.charAt(i)); //连接到StringBuffer
else
{
数字栈进栈(数字字符串 转化为数字); //转不成数字则提示算式错误
数字字符串清空;
while(!(formula.charAt(i)的优先级 > 栈顶元素优先级) && formula.charAt(i)的优先级 + 栈顶元素优先级 != 0 &&当前栈顶不是#)
{
float a = 数字栈栈顶元素出栈;
float b = 数字栈栈顶元素出栈;
char f = 符号栈栈顶元素出栈;
float result = 根据a,b,f计算的结果;
result进栈数字栈;
}
if (formula.charAt(i)的优先级 + 栈顶元素优先级 == 0)
符号栈栈顶出栈; //去括号
else
formula.charAt(i)进栈符号栈;
}
}
if (符号栈栈顶元素优先级 != -4)
//字符串读取完成,但是符号栈内仍然有运算符
错误;
else
return 数字栈栈顶元素出栈;
}
整理
到这里为止,四则运算的基本逻辑已经完成了,核心部分大概只需要30行代码,接下来看这个算法中需要用到哪些类和方法,哪些是java中还没有需要自己编写的。
最重要的部分是栈,栈是Java中自带的,所有的进栈出栈等方法都有,所以不需要编写。数字字符串有许多操作,如拼接,清空,数字字符串间转化,这些也不需要自己编写。
判断一个符号是不是运算符,运算符的优先级比较和根据符号和两个数字来计算出运算结果的方法Java中都没有,因此需要自己编写。
代码实现
准备
首先先把上一步中确定了需要自己编写的部分写好。
public class Calculator
{
//判断符号c是否为运算符,若是则返回true
private boolean isOperator(char c)
{
return getPriority(c) != -5;
}
//获取字符c的优先级并返回
private int getPriority(char c)
{
switch(c)
{
case '+':return 1;
case '-':return 1;
case '*':return 2;
case '/':return 2;
case '(':return 3;
case ')':return -3;
case '#':return -4;
default:return -5;
}
}
//根据运算符和两个数字计算结果
private float operate(char c,float u,float d)
{
switch(c)
{
case '+':return u + d;
case '-':return u - d;
case '*':return u * d;
case '/':return u / d;
default:return 0;
}
}
}
栈和字符串
实现
在之前准备那一步中创建的Calculator类中添加方法。
/**
* 根据传入的代表算式的字符串计算结果并返回
* @param formula 代表算式的字符串
* @return 计算结果
*/
public float calculate(String formula)
{
formula += "#";
boolean flag = true; //标记,若前一个运算符不是右括号,则为true
Stack<Float> figure = new Stack<Float>(); //数字栈
Stack<Character> operator = new Stack<Character>(); //符号栈
operator.push('#');
StringBuffer figureString = new StringBuffer(); //数字字符串,用于暂时存放读取到的数字
for (int i = 0;i < formula.length();i ++)
{
//读取到非运算符,连接到StringBuffer
if (!isOperator(formula.charAt(i)))
figureString.append(formula.charAt(i));
else
{
//若当前符号是左括号或前一个符号是右括号,数字栈不进栈(左括号前不应当是数字而是运算符,右括号后同理)
if (formula.charAt(i) != '(' && flag == true)
{
figure.push(Float.valueOf(figureString.toString())); //将数字字符串的内容转为数字并进栈数字栈
figureString.setLength(0); //清空数字字符串
}
flag = true;
while((getPriority(formula.charAt(i)) < getPriority(operator.peek()) || getPriority(formula.charAt(i)) == getPriority(operator.peek())) && getPriority(operator.peek()) != 3 && getPriority(operator.peek()) != -4)
{
float d = figure.pop();
float u = figure.pop();
figure.push(operate(operator.pop(),u,d));
}
if (getPriority(formula.charAt(i)) < getPriority(operator.peek()) && getPriority(formula.charAt(i)) + getPriority(operator.peek()) == 0)
{
operator.pop(); //去括号
flag = false;
}
else
operator.push(formula.charAt(i));
}
}
if (getPriority(operator.pop()) != -4)
//字符串读取完成,但是符号栈内仍然有运算符
return 0;
else
return figure.pop();
}
在编写和测试过程中过程中,会发现有一些逻辑之前没有考虑到,如左括号前和右括号后不能直接跟数字,导致数字字符串转化为数字时空字符。在上方这段代码中已经将这个问题修复了。
到这里为止,四则运算就算基本实现了,接下来是一些附加功能的编写,如判断正负号,小数点,算式正确性等。
目前代码可能会比较难看,这个在后期进行修改。