2018102996+小学四则运算练习软件项目报告
1、实验目的与要求
(1)掌握软件项目个人开发流程。
(2)掌握Coding.net上发布软件项目的操作方法。
2、实验内容和步骤
任务1:
尝试按照《构建之法》第2章中2.3所述PSP流程,使用JAVA编程语言,独立完成一个3到5个运算符的四则运算练习的命令行软件开发。
软件基本功能要求如下:
程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号±*÷来表示)练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。
每个练习题至少要包含2种运算符。不得出现负数与非整数。
练习题生成好后,将你的学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。
当程序接收的参数为4时,以下为一个输出文件示例。
2018010203
13+17-1=29
11*15-5=160
3+10+4-16=1
15÷5+3-2=4
软件附加功能要求如下:(请有余力的同学完成)
支持有括号的运算式,包括出题与求解正确答案。注意,算式中存在的括号数必须大于2对,且不得超过运算符的个数。(5分)
扩展程序功能支持真分数的出题与运算(只需要涵盖加减法即可),例如:1/6 + 1/8 + 2/3= 23/24。注意在实现本功能时,需支持运算时分数的自动化简,比如 1/2+1/6=2/3,而非4/6,且计算过程中与结果都须为真分数。(5分)
3.设计实现与PSP
1.输入控制
使用技术:正则表达式
2.随机数与随机符号的产生
使用技术:使用ArrayList和HashMap分别产出随机数与随机符号,利用Set的数据结 构特性保证产生符号的唯一性。
3.实现四则运算功能
使用技术:逆波兰式,栈式数据结构
4.文件的输入输出
5.PSP流程
|
任务内容 | 计划共完成需要的时间(min) | 实际完成需要的时间(min) |
---|---|---|
计划 | ||
估计这个任务需要多少时间,并规划大致工作步骤 | ||
开发 | ||
需求分析 (包括学习新技术) | ||
具体设计 | ||
具体编码 | ||
测试(自我测试,修改代码,提交修改) | ||
测试报告并提交到Git | ||
计算工作量 | ||
事后总结, 书写博客 |
4.算法详解
1.正则表达式
正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。
给定一个正则表达式和另一个字符串,我们可以达到如下的目的:
(1)给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”):
(2)可以通过正则表达式,从字符串中获取我们想要的特定部分。
(3) 正则表达式使用详解链接地址
2.Collection数据结构及其特性
数据结构按照逻辑关系(数据组织方式)主要划分为以下四类:
(1)集合
(2)线性结构
(3)树结构
(4)图结构
JDK中提供了Collection接口,用于定义一个聚合类应具有的基本功能(方法),而这些功能由接口的实现类完成。
在JDK1.4之后增加了Map接口,定义了映射集合类应具有的基本功能,并提供了散列映射集(HashMap)的实现。
3.逆波兰式
(1)定义:逆波兰式(Reverse Polish notation,RPN,或逆波兰记法),也叫后缀表达式(将运算符写在操作数之后)
(2)作用:实现逆波兰式的算法,难度并不大,但为什么要将看似简单的中序表达式转换为复杂的逆波兰式?原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。
(3)将一个普通的中序表达式转换为逆波兰表达式的一般算法是:
首先需要分配2个栈,一个作为临时存储运算符的栈S1(含一个结束符号),一个作为输入逆波兰式的栈S2(空栈),S1栈可先放入优先级最低的运算符#,注意,中缀式应以此最低优先级的运算符结束。可指定其他字符,不一定非#不可。从中缀式的左端开始取字符,逐序进行如下步骤:
(1)若取出的字符是操作数,则分析出完整的运算数,该操作数直接送入S2栈
(2)若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,如果该运算符优先级(不包括括号运算符)大于S1栈栈顶运算符优先级,则将该运算符进S1栈,否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符低于(不包括等于)该运算符优先级,最后将该运算符送入S1栈。
(3)若取出的字符是“(”,则直接送入S1栈顶。
(4)若取出的字符是“)”,则将距离S1栈栈顶最近的“(”之间的运算符,逐个出栈,依次送入S2栈,此时抛弃“(”。
(5)重复上面的1~4步,直至处理完所有的输入字符
(6)若取出的字符是“#”,则将S1栈内所有运算符(不包括“#”),逐个出栈,依次送入S2栈。
a’c’n’k
完成以上步骤,S2栈便为逆波兰式输出结果。不过S2应做一下逆序处理。便可以按照逆波兰式的计算方法计算了!
参考文献:百度百科:逆波兰式
4.文件读写操作
5.Git的使用
Git常见命令:https://www.cnblogs.com/chenwolong/p/GIT.html
5.测试运行结果
6.代码实现
Map<Integer, String> opeMap = new HashMap<Integer, String>();//使用HashMap
opeMap.put(1, "+");
opeMap.put(2, "-");
opeMap.put(3, "*");
opeMap.put(4, "÷");
int opeNum = 0;
ArrayList<String> operrayList = new ArrayList<String>();
while(opeNum != nums - 1){//生成(随机数 - 1)个运算符
Random randomOpe = new Random();
int key = randomOpe.nextInt(4)+1;
String ope01 = opeMap.get(key);
operrayList.add(ope01);
opeNum ++;
}
// 操作符栈
private Stack<String> czf_stack = new Stack<>(); // 存放 运算符的栈
private ArrayList<String> ysbds_list = new ArrayList<>(); //存放 原始表达式的 arraylist
private ArrayList<String> nblbds_list = new ArrayList<>(); // 存放转换后的 逆波兰式
private static final int One = 1; //
private static final int Two = 3; //
private static final int Three = 5; //规定优先级 Three 最高
// 定义一个运算栈
private static Stack<String> ys_stack = new Stack<>();
// 初始化 使用StringTokenizer分割 字符串
public Main(String bdString) {
// TODO Auto-generated constructor stub
StringTokenizer stringTokenizer = new StringTokenizer(bdString, "+-*÷()",true);
while(stringTokenizer.hasMoreTokens()){
ysbds_list.add(stringTokenizer.nextToken());
//System.out.println(stringTokenizer.nextToken());
}
}
/**
* @author 李明阳
* @title: isNum
* @date 2016年3月27日 下午7:59:48
* @param str
* @return boolean
*/
// 判断 是否是数字
public boolean isNum(String str){
if(str.matches("[0-9]+")){ //这里使用正则表达式 验证是否是数字
//System.out.println("Y");
return true;
}else{
//System.out.println("N");
return false;
}
}
// 判断 是否是操作符
public boolean isCzf(String str){
if(str.matches("[\\+\\-\\*\\÷\\(\\)]")){
//System.out.println("Y");
return true;
}else{
//System.out.println("N");
return false;
}
}
// 获取 优先级
public int getYxj(String str){
switch(str){
case "(":return Three;
case "*":
case "÷":return Two;
case "+":
case "-":return One;
case ")":return 0;
default : return -1;
}
}
/**
*
* @author 李明阳
* @title: isYxj
* @date 2016年3月28日 上午9:00:02
* @param str1
* @param str2
* @return boolean
*/
// 判断优先级
public boolean isYxj(String str1,String str2){
return getYxj(str1) > getYxj(str2);
}
/**
*
* @author 李明阳
* @title: stack_czf
* @date 2016年3月28日 上午9:01:12
* @param czf void
*/
// ********* 当 当前操作元素为 操作符时********** 这里是 核心代码, 用于操作符栈的判断
public void stack_czf(String czf){
//判断当前栈内是否为空
if(czf_stack.isEmpty()){
czf_stack.push(czf);
return;
}
//判断是否为 (
if("(".equals(czf)){
czf_stack.push(czf);
return;
}
//判断是否为 )
if(")".equals(czf)){
String string = "";
while(!"(".equals(string = czf_stack.pop())){
nblbds_list.add(string);
}
return;
}
//如果 当前栈顶元素是 ( 直接入栈
if("(".equals(czf_stack.peek())){
czf_stack.push(czf);
return;
}
// 判断 与 栈顶元素的优先级 , > 为true
if(isYxj(czf, czf_stack.peek())){
czf_stack.push(czf);
return;
}
if(!isYxj(czf, czf_stack.peek())){
nblbds_list.add(czf_stack.pop());
stack_czf(czf); //这里调用函数 本身,并将本次的操作数传参
}
}
/**
*
* @author 李明阳
* @title: zz_hz
* @date 2016年3月28日 上午9:28:44 void
*/
// 中缀 —> 后缀
public void zz_hz(){
// 遍历原始表达式list
for(String str:ysbds_list){
//System.out.println("-> " + str);
if(isNum(str)){
nblbds_list.add(str);
}else if(isCzf(str)){
stack_czf(str);
}else{
System.out.println("非法");
}
}
// 遍历完原始表达式后 将操作符栈内元素 全部添加至 逆波兰表达式list
while(!czf_stack.isEmpty()){
//System.out.println("即将 " + czf_stack.peek());
nblbds_list.add(czf_stack.pop());
}
}
/**
*
* @author 李明阳
* @title: jsff
* @date 2016年3月28日 上午10:03:01
* @param s1
* @param s2
* @param s3
* @return int
*/
// 具体计算方法
public Float jsff(String s1,String s2,String s3){
float a = Float.parseFloat(s2);
float b = Float.parseFloat(s1);
switch(s3){
case "+":return a+b;
case "-":return a-b;
case "*":return a*b;
case "÷":return a/b;
default : return (float) 0.0;
}
}
/**
*
* @author 李明阳
* @title: js_nbl
* @date 2016年3月28日 下午3:21:41
* @return flaot
*/
// 计算 逆波兰式
public Float js_nbl(){
7.总结