【JAVA项目】实现完美计算器
文章目录
- JAVA & 完美计算器
- 前言:
- 接下来一步步来设计我们的计算器吧!
- ① 我们要了解什么是中缀表达式与逆波兰后缀表达式
- ② 接下来是将字符串转化为中缀表达式
- ③ 中序表达式转化为逆波兰表达式
- 一. 优先级的处理:
- 二.我自己做的栈实现,这里功能方便展示(也可以用java现成的集合类):
- 三.转化为逆波兰表达式:
- 一.获得中缀表达式
- 二. 处理中缀表达式(结合代码观看,以及结合前面的图)
- ④ 通过逆波兰表达式求结果
- ⑤ 计算器搭建组装
- 一.菜单以及初步框架
- 二.各个组件【代码基本部分阅读简单,不作赘述】
- 测试
JAVA & 完美计算器
前言:
- 我设计的计算器用到的一些知识储备有:
- javase 基础语法
- java数据结构,栈的知识(部分集合类)
- 中缀表达式
- 逆波兰后缀表达式
- 思想(字符串 -> 中缀 -> 逆波兰 -> 结果)
- 优化调修
- 计算器基本用处:
- 十进制运算通用,其他进制只支持进制转化功能
- 计算复杂字符串(高复合括号,处理优先级,处理多位小数)
- 包含(+ -)<(* / % ^)(位操作符不包含,三角函数操作符不考虑),默认 * / % ^ 优先级一样
- 获取上一次的数据以及查看历史记录的功能
接下来一步步来设计我们的计算器吧!
① 我们要了解什么是中缀表达式与逆波兰后缀表达式
中缀表达式
- 其实就是我们平时写的表达式如: -1 / (-11 * 22) + 6 ^ 2 % 3
- 正常的序列,符号一般在数字与数字中间,除了符号表示负数时,带括号
- 补充一说,实际上一个是没两个式子就有一个括号
- 例如上面那个式子应该是:> ((-1 / (-11 * 22)) + ((6 ^ 2 )% 3))
逆波兰后缀表达式
- 这类表达式是专门去计算的,特定计算后就是正确结果
- 格式错误甚至报错,顺序错误大概率影响正确结果甚至报错
- 其实就是让中缀表达式(括号全在的情况下),将 从里往外,从左往右 把运算符往括号右边放,并且删除括号(也可最后一起删除,反正要记住那些部分是整体)
② 接下来是将字符串转化为中缀表达式
待处理问题:(正常人输入情况下!)
- 字符串是否带空格
- 字符串为连续,怎么确定 值与符号
- 负号代表运算还是值呢?
思想:
字符串空格在java中可以用字符串自带函数,将字符串空格转化为空字符,完成删除
replaceAll(String , String)
遍历一次,每次遇到数字或者小数点时循环将整个数值制作成字符串就行,其他字符就直接做成字符串
只需要判断每个负号前面的情况就好:
- 要么没有字符 负值
- 要么为左括号 负值
- 要么为右括号 和 数字 运算符
- 最后将负号改成 -1 * 即可
最终放进顺序表或者链表中
//记得导包!
//所有都用static是因为互相使用方便,并且这个方法不需要new一个对象才能使用,通过类名就足够了public static List<String> transform(String s) {//1. 去空格s = s.replaceAll(" ", "");//2. 存放各元素List<String> list = new ArrayList();//存储中序表达式int i = 0;char c;do {if (((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57)) {list.add("" + c);i++;} else {//这里是为了提高效率用了 StringBuilder//因为本质上字符串相加调用了这个,那么我们多次相加就多次调用,还不如只调用一次!StringBuilder stringBuilder = new StringBuilder();while ((i < s.length() && ((c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57))|| (i < s.length() && (c = s.charAt(i)) == '.')) {stringBuilder.append(c);i++;}list.add(stringBuilder.toString());}} while (i < s.length());//3. 对特殊负号进行修改for (int j = 0; j < ls.size(); j++) {if(list.get(j).equals("-") && (j == 0 || (j > 0 && list.get(j - 1).equals("(")))) {list.set(j, "-1");list.add(j + 1, "*");}}return list;}
③ 中序表达式转化为逆波兰表达式
面临的问题:
- 优先级怎么处理
- 怎么排出正确的序列,在括号缺失,括号存在的情况下
处理:
- 可以写一个方法,判断符号优先级,优先级大的返回值更大 (括号优先级为最小,这里与后面的操作很重要)
- 用栈去处理!【随后解释】
- 其实我们不难想出,可以用递归,递归进一层一层括号内,但是递归每次的空间都不一样,不易操作
- 用栈去实现“倒回的思想”,先弹入的符号,后弹出,模拟出中序表达式中先里面的括号符号弹出,外面括号的符号弹出
有一个新的集合存放逆波兰表达式(能在原来的那个平移操作,那真的牛)
一. 优先级的处理:
当然也可以用switch( String ) 【switch在java中是可以分支字符串的】
public static int getValue(String s) {if (s.equals("+")) {return 1;} else if (s.equals("-")) {return 1;} else if (s.equals("*")) {return 2;} else if (s.equals("/")) {return 2;} else if (s.equals("^")) {return 2;} else if (s.equals("%")) {return 2;} else {return 0;}}
二.我自己做的栈实现,这里功能方便展示(也可以用java现成的集合类):
public class MyStack {private List<String> list;private int size;//已有元素个数public String top;//栈顶元素//构造方法public MyStack() {list = new ArrayList();size = 0;top = null;}//获取栈已有元素个数public int size() {return size;}//压栈public void push(String s) {list.add(s);top = s;size++;}//弹出栈顶public String pop() {String s = list.get(size - 1);list.remove(size - 1);size--;top = size == 0 ? null : list.get(size - 1);return s;}
}
三.转化为逆波兰表达式:
一.获得中缀表达式
List<String> listStr = transform(s);
二. 处理中缀表达式(结合代码观看,以及结合前面的图)
遍历表,识别字符串的种类
- 双精度浮点型的匹配(包含负数整数小数,最全面)(^(-?\d+)(\.\d+)?$ 双精度浮点型是正则表达式),matches(正则表达式)来判断字符串是否是对应种类的数据。【当然识别字符不是负号,并且第一个字符是数字字符or负号也是OK的】
- 左括号
- 右括号
- 运算符(在我们之前的操作,负号已经全是操作符了)
※ 进行不同操作 【特别重要】
对于数值,直接放入 集合 retList 里面就好。
对于左括号
- 压入栈中
对于右括号
- 将栈中不是左括号的全部弹入 retList 中
- 最终将左括号弹出
- 左右括号这套操作,可以使得,在遇到右括号时,将这对括号内的整体进行了操作,也就是完成分割
- 也就是说,数值的顺序是一定的,运算符进入集合 retList 的时机最为重要,而左右括号这套操作,让整个表达式有条不紊的进行了, 让括号在栈中进行分割,让括号内部不干扰外部的
- 提高栈中的符号管理!
对于运算符,想象这么一个场景 (#¥@%¥%¥%@) ==》 (()----()----()----()… ) 一个括号式子中实际应该是这个的,
- 此次运算符的优先级小于等于栈顶优先级,就得弹入retList中(循环)
- 如果栈顶为2,此时为1 --》 这就相当于【前面的整体括号式子已经结束了,该加减后面的整体括号式子了】1 别忘了最后压栈。
* 例如,5*5 + 1 ,5/6 + 1 - 如果栈顶为1, 此时为2–》直接压栈,意味着数值应该先进行该操作。
- 如果栈顶为1/2, 此时与其相等,应该悉数弹入retList中,优先级相同,可以连续进行,既然是连续进行,那么相当于左边整体括号式子结束,别忘了最后压栈。
- 如果栈顶为0,则直接压栈即可。
- 如果栈顶为2,此时为1 --》 这就相当于【前面的整体括号式子已经结束了,该加减后面的整体括号式子了】1 别忘了最后压栈。
- 此次运算符的优先级小于等于栈顶优先级,就得弹入retList中(循环)
最终将栈中剩余的全部弹入retList中(栈顶弹出,满足思想)
public static List<String> parse(String s) {List<String> listStr = transform(s);List<String> retList = new ArrayList<>();for (String ss : listStr) {if (ss.matches("^(-?\\d+)(\\.\\d+)?$")) {retList.add(ss);} else if (ss.equals("(")) {ms1.push(ss);} else if (ss.equals(")")) {while (!ms1.top.equals("(")) {retList.add(ms1.pop());}ms1.pop();} else {while (ms1.size() != 0 && getValue(ms1.top) >= getValue(ss)) {retList.add(ms1.pop());}ms1.push(ss);}}while (ms1.size() != 0) {retList.add(ms1.pop());}return retList;}
下图以 A + ( B - D )- E / F为例的步骤剖析图:
④ 通过逆波兰表达式求结果
重点在于如何利用栈!【下面用到的栈是java自带的栈!】
定义计算规则!【先弹出的放在右,后弹出的放在左,因为栈的特性】
public static void cal(Stack<String> stack, String str) {String str1 = stack.pop();String str2 = stack.pop();Double f1 = Double.parseDouble(str1);Double f2 = Double.parseDouble(str2);switch (str) {case "+":stack.push(f1 + f2 + "");break;case "-":stack.push(f2 - f1 + "");break;case "*":stack.push(f1 * f2 + "");break;case "/":if(f1 == 0) {throw new ArithmeticException("计算");}stack.push(f2 / f1 + "");break;case "%" :if(f1 == 0) {throw new ArithmeticException("计算");}if(((int)(f2 / f1))* f1 == f2) {//确定是否倍数关系的方法stack.push(0 + "");}else {stack.push(f2 % f1 + "");}break;case "^":stack.push(Math.pow(f2, f1) + "");break;}
- 这里要确定倍数关系在去取模
- 是因为计算机在储存小数是,是按照一定精确度的,所以取模的时候,可能存在倍数关系,但是结果又不是0,就是因为偏差了那0.00000000001
- 这里要确定倍数关系在去取模
栈模拟出来那种“套娃”,说到底就是运算“到底”,两个数就通过运算符结合成一个数,直到最后一个数
- 遍历集合,一直压栈直到遇到符号,就“融合”前面两个数,然后继续直到集合末尾
如下图所示:
public static String calculate(String s) {List<String> arrayList = reversePolish.parse(s);Stack<String> stringStack = new Stack<>();for (int i = 0; i < arrayList.size(); i++) {String str = arrayList.get(i);if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/") || str.equals("^") || str.equals("%")) {cal(stringStack, str);//cal为刚才的计算原理}else {stringStack.push(str);}}return stringStack.pop();}
这里埋个伏笔,返回的是字符串,因为我们不仅仅要用到它的值,还要它的字符串。
只需要将字符串转化为double型(用java中String自带的方法)就好了【也可遍历字符串去慢慢构建一个double型也行】
⑤ 计算器搭建组装
一.菜单以及初步框架
public static void menu() {System.out.println("***********### 计算器 ###************");System.out.println(" 1. 获取上一次的值继续运算");System.out.println(" 2. 重新开始计算");System.out.println(" 3. 进制转化(只限整数)");System.out.println(" 4. 获取上一次的n进制序列");System.out.println(" 5. 查看历史记录");System.out.println(" 0. 退出");System.out.println("****************************************");System.out.print("请选择:> ");}
String str = "";Scanner scanner = new Scanner(System.in);PrevNum prevNum = new PrevNum();List<String> history = new ArrayList<>();int input = 0;do {menu();input = scanner.nextInt();scanner.nextLine();switch (input) {case 1://区域1break;case 2://区域2break;case 3://区域3break;case 4 ://区域4break;case 5: //区域5break;case 0://区域6break;default://区域7break;}}while(input != 0);}
二.各个组件【代码基本部分阅读简单,不作赘述】
public class 你他妈脑子异常 extends RuntimeException{public 你他妈脑子异常() {}public 你他妈脑子异常(String message) {super(message);}
}
为了对一些无脑操作痛击!!!而设计的异常(汉字可以做类名,但是不合理(像触发这个异常的操作者一样),我们应该有正确的写代码风格)
组件1区域1:
System.out.print("请输入:> " + str);str += scanner.nextLine();String s1 = str;str = calculate(str);System.out.println("=" + Double.parseDouble(str));history.add(s1 + " = " + Double.parseDouble(str));
获取上一次的值继续运算(那个计算后的字符串被记录下来了,这样就可以追加续写了)
组件2区域2:
System.out.print("请输入:> ");str = scanner.nextLine();String s2 = str;str = calculate(str);System.out.println("=" + Double.parseDouble(str));history.add(s2 + " = " + Double.parseDouble(str));
重新开始计算
组件3区域3:
class PrevNum {String string;int radix;public PrevNum() {}public PrevNum(String string, int radix) {this.string = string;this.radix = radix;}public int getValue() {return Integer.parseInt(string, radix);}public void set(String string, int radix) {this.string = string;this.radix = radix;}@Overridepublic String toString() {if (string == null) {throw new 你他妈脑子异常("明明没有上一个");//自制的异常}return "数值为 '" + string + '\'' +", 进制为 " + radix;} }
为记录上一个n进制序列设计的一个类
System.out.print("请输入你接下来输入的数的进制:>");int r = scanner.nextInt();scanner.nextLine();System.out.print("请输入整数对应的" + r + "进制数:> ");int number = scanner.nextInt(r);scanner.nextLine();System.out.print("请输入你要转化为进制[2, 36]:> ");int radix = scanner.nextInt();//括号内输入radix,代表输入什么进制的scanner.nextLine();String string1 = Integer.toString(number, radix);System.out.println("转化成功:> " + string1);history.add(Integer.toString(number, r) + " = " + string1 + " (" + r + "->" + radix + ")");prevNum.set(string1, radix);
进制转化(只限整数)
组件4区域4:
System.out.println(prevNum);System.out.print("请输入你要转化为进制[2, 36]:> ");int newRadix = scanner.nextInt();scanner.nextLine();String string2 = Integer.toString(prevNum.getValue(), newRadix);System.out.println("转化成功:> " + string2);history.add(prevNum.string + " = " + string2 + " (" + prevNum.radix + "->" + newRadix + ")");prevNum.set(string2, newRadix);
获取上一次的n进制序列
组件5区域5:
System.out.println("================================================");if(history.size() == 0) {System.out.println("空");}else {for(String s : history) {System.out.println("
【JAVA项目】实现完美计算器相关推荐
- java质数和合数的程序_《java项目实训》课程设计计算器.doc
<java项目实训>课程设计计算器.doc 课程设计报告课程名称JAVA项目实训课程设计设计名称基于JAVA计算器的设计与实现学生学号学生姓名学生学号学生姓名学生学号学生姓名学生学号学生姓 ...
- Maven 系列 5:Maven 项目管理生命周期学习——命令界面四大指令完美运行 Hello、HelloFriend Java 项目完整步骤及错误总结
文章目录 前言 一.回顾 Maven 的安装目录结构 二.检查环境变量配置以及全局范围 setting.xml 是否配置好 三.Maven 四大指令详解 四.新建 Maven 演示的 Java 项目 ...
- Java项目:基于Jsp实现网上定餐系统
作者主页:编程指南针 简介:Java领域优质创作者.CSDN博客专家 Java项目.简历模板.学习资料.面试题库.技术互助 文末获取源码 项目编号:BS-SC-001 本项目基于JSP+SERVLE ...
- 为什么在2012/2013年我将在新的Enterprise Java项目中继续使用Spring *和* Java EE
自从我担任技术决策职务以来已经过去了一年多,很高兴看到我仍然与之保持着完美的和谐. 几个月前,我在KaiWähner的一个不错的博客中写了一个有关JEE与Spring的答案. 如果观点没有不同,那么讨 ...
- Android 实战项目:简单计算器
文章目录 实战项目:简易计算器 1.需求分析 2.界面设计 3.关键代码 1.输入按键的合法性校验 2.执行运算并显示计算结果 简单计算器 - 详细操作步骤 总结 实战项目:简易计算器 1.需求分析 ...
- java 适合练手的java项目
Java作为一门古老的语言,已有20年左右的历史,这在发展日新月异的技术圈可以说是一个神话. 虽然不少人曾抱怨Java语言就像老太太的裹脚布,又臭又长,有时写了500行都不能表达程序员的意图. 但从市 ...
- Java——一些适合新手练手的Java项目
转载自 https://blog.csdn.net/luolianxi/article/details/77924728 Java作为一门古老的语言,已有20年左右的历史,这在发展日新月异的技术圈可以 ...
- Java【有哪些适合新手练手的Java项目?】
Java作为一门古老的语言,已有20年左右的历史,这在发展日新月异的技术圈可以说是一个神话. 虽然不少人曾抱怨Java语言就像老太太的裹脚布,又臭又长,有时写了500行都不能表达程序员的意图. 但从市 ...
- 有哪些适合新手的练手Java项目?
源码下载(实例一): jsp开发完整的博研图书馆后台管理系统,不使用框架开发的,太完美了 http://www.zuidaima.com/share/2358272909446144.htm 源码下载 ...
最新文章
- E - Right-Left Cipher CodeForces - 1087A (模拟)
- 线上开票系统设计实践
- Python之分享常用的五款动态数据可视化工具
- elasticsearch安装与基础用法
- Can‘t bind to formControl since it isn‘t a known property of input错误消息的处理
- ARC079F - Namori Grundy(构造,基环树)
- 前端学习(2117):为什么组件data必须是函数
- python list 实现原理,彻底理解Python list切片原理
- 送给程序员:关于性格内向者的10个误解(转)
- 小雷:我的核心定位和远大志向(上次更新2013年11月9日)
- [ACM训练] 算法初级 之 基本算法 之 枚举(POJ 1753+2965)
- IE浏览器,ajax提示错误“no transport”
- 测试图片真假软件,如何找出照片的PS痕迹__如何检测一张图片是否被PS过_飞翔教程...
- 舞蹈艺考生可以报计算机专业吗,全国舞蹈类艺考
- word2010设置护眼背景
- 《从0开始做运营[张亮]》——读书笔记
- 宁夏理工计算机管理与应用,宁夏理工学院计算机科学与技术中小学校(计算机教育)...
- 【Books系列】席慕蓉《回眸》欣赏
- ARIMA时间序列分析——(一)数据平稳性检验
- python爬取网易云音乐飙升榜音乐_python爬取网易云音乐热歌榜 python爬取网易云音乐热歌榜实例代码...
热门文章
- 2021-2027年全球与中国前列腺动脉栓塞行业市场前瞻与投资战略规划分析报告
- 华为机试——名字的漂亮度
- js 毫秒转天时分秒
- 小满nestjs(第二十八章 nestjs 事务)
- ubuntu 使用utorrent
- 2021年考思科认证CCIE还有必要吗
- 二本学计算机好还是金融好,二本院校最好就业的5大专业,“含金量”很高,前途一片光明...
- Inductive Entity Representations from Text via Link Prediction
- html video标签播放直播视频,HTML5 Video 标签播放及控制视频
- Android 开发 之 8.0应用安装权限(未知应用权限安装)
- java质数和合数的程序_《java项目实训》课程设计计算器.doc