四则运算之结对作业
一、
上周,老师在之前四则运算作业的基础上,又布置下了结对作业,即两人组队,完成一个完整的四则运算的项目。由于我是工作室后端的成员,于是,我便担任起了后端的任务。
我们先分析了老师提出的需求。老师提出的需求有一下几点:
- 定制出题要求。每次出题时用户都可以在界面上定制如下参数:题目数量,算式数值范围(仅包括原始题目与最终结果的绝对值的数值范围),题目中最多有多少个运算符,题目中是否包含乘除法,题目中是否包含括号。在点击相应出题按钮后将生成题目文件(不包含答案)。
- 做题功能。出题后,用户可以开始答题
- 判题功能。用户答题过程中或者全部完成后可以判断对错,并统计分数和时间。
- 本次界面可以用网页版或客户端任何一种形式完成。
我分析了老师提出的需求,再结合了我第一次写的函数,认为只需要改动一些小小的地方,就可以完成老师的要求。
这次作业,我用了当下web开发中最流行的一个框架——springboot,这让我真正的体验到了springboot的便捷之处,不用再配一堆xml文件,也不用配tomcat,真的是太方便了!
二、功能展示
本小组完成了老师要求的任务,并且,又新增了文件下载的功能,用户可以*选择是否下载题目。
文件下载到浏览器默认的下载路径
三、代码展示
(一)后端
这是我的项目目录
后端代码主要分为三块:
1、entity
主要用来传JSON数组
2、Servicce
Service又分为service和impl两大部分,service用来放接口,而impl用来放最底层的代码,也就是最核心的算法部分
(1)接口部分
package com.example.demo.service;
public interface QuestionService {
public int algorithm(String s);
public int calculate(int a, int b, String stmp);
public int[] index(int n, int m);
public String[] create(int max, int min, int maxOper, int isBrac, int isMul);
//用来测试的方法
//public String[] create();
}
(2)核心算法
我的核心算法和我第一次的作业并没有做太大的改动,只修改了一些细节部分。
首先是逆波兰表达式
public int algorithm(String s) {
//放数字
Stack<Integer> stack1 = new Stack<>();
//放操作符
Stack<String> stack2 = new Stack<>();
//存放运算符优先级
HashMap<String, Integer> hashmap = new HashMap<>();
hashmap.put("(", 0);
hashmap.put("+", 1);
hashmap.put("-", 1);
hashmap.put("*", 2);
hashmap.put("÷", 2);
for (int i = 0; i < s.length();) {
//设置可变长的字符串
StringBuffer digit = new StringBuffer();
//将式子字符串切割为c字符
char c = s.charAt(i);
//判断字符是否为10进制数字,将一个数加入digit
while (Character.isDigit(c)) {
digit.append(c);
i++;
c = s.charAt(i);
}
//当前digit里面已经无数字,即当前处理符号
if (digit.length() == 0){
switch (c) {
case '(': {
stack2.push(String.valueOf(c));
break;
}
//遇到右括号了计算,因为(的优先级最高
case ')': {
String stmp = stack2.pop();
while (!stack2.isEmpty() && !stmp.equals("(")) {
int a = stack1.pop();
int b = stack1.pop();
int sresulat = calculate(b, a, stmp);
if(sresulat<0)
return -1;
stack1.push(sresulat);
//符号指向下一个计算符号
stmp = stack2.pop();
}
break;
}
case '=': {
String stmp;
while (!stack2.isEmpty()) {
stmp = stack2.pop();
int a = stack1.pop();
int b = stack1.pop();
int sresulat = calculate(b, a, stmp);
if(sresulat<0)
return -1;
stack1.push(sresulat);
}
break;
}
default: {
String stmp;
while (!stack2.isEmpty()) {
stmp = stack2.pop();
//比较优先级
if (hashmap.get(stmp) >= hashmap.get(String.valueOf(c))) {
int a = stack1.pop();
int b = stack1.pop();
int sresulat =calculate (b, a, stmp);
if(sresulat<0)
return -1;
stack1.push(sresulat);
}
else {
stack2.push(stmp);
break;
}
}
//将符号压入符号栈
stack2.push(String.valueOf(c));
break;
}
}
}
else {
//处理数字
stack1.push(Integer.valueOf(digit.toString()));
continue;
}
i++;
}
//返回栈底得到答案
return stack1.peek();
}
接下来是生成表达式的函数
public String[] create(int max, int min, int maxOper, int isBrac, int isMul){
// int max=10 ;
// int min=0 ;
// int maxOper=5;
// int isBrac=1;
// int isMul=4;
String[] string = new String[2];
Random r = new Random();
String[] opertor = {"+","-","*","÷"};
//操作符的个数
int operatorNum = maxOper;
//新建数组来保存操作数
int[] number = new int[operatorNum+1];
//操作符的下标,若isMul等于2,则没有乘除法,若isMul等于4,则含有乘除法
int[] arr = index(operatorNum, isMul);
String s = new String();
for(int j=0;j<operatorNum+1;j++){
number[j] = min + r.nextInt(max-min);
}
//如果flag=0,则该式子加括号,如果flag=1,则该式子不加括号(自己设定)
switch (operatorNum){
case 3:{
if(isBrac == 0){
s = "(" + number[0] + opertor[arr[0]] + number[1] + ")" +opertor[arr[1]]+ "(" + number[2] + opertor[arr[2]] + number[3] + ")";
}
else{
s = number[0] + opertor[arr[0]] + number[1]+opertor[arr[1]]+number[2] + opertor[arr[2]] + number[3] ;
}
break;
}
case 4:{
if(isBrac ==0){
s = "(" + "(" + number[0] + opertor[arr[0]] + number[1] + ")" + opertor[arr[1]] + number[2] + ")" + opertor[arr[2]] + "(" + number[3] + opertor[arr[3]] + number[4] + ")";
}
else{
s = number[0] + opertor[arr[0]] + number[1] +opertor[arr[1]] + number[2] + opertor[arr[2]] + number[3] + opertor[arr[3]] + number[4] ;
}
break;
}
case 5:{
if(isBrac ==0){
s = "(" + "(" + number[0] + opertor[arr[0]]+number[1]+")" + opertor[arr[1]] + number[2] + ")" +opertor[arr[2]]+ "(" + "(" + number[3] + opertor[arr[3]] + number[4] + ")" + opertor[arr[4]] + number[5] + ")";
}
else{
s = number[0] + opertor[arr[0]]+number[1]+ opertor[arr[1]] + number[2] +opertor[arr[2]] + number[3] + opertor[arr[3]] + number[4] + opertor[arr[4]] + number[5] ;
}
break;
}
}
s+="=";
int answer = algorithm(s);
//判断式子是否符合要求,凡是返回负数的就是不合格的
if(answer>min && answer<max){
string[0] = s;
string[1] = String.valueOf(answer);
return string;
}else{
return create(max, min, maxOper, isBrac, isMul);
}
}
这两个函数是最重要的两个函数了,其他的函数就不做展示了,可以到我的代码仓库中去看完整的代码
3、Controller
主要用来放和前端进行交互的代码
我一共写了两个controller,一个是createFile,另一个是downloadFile
createFile用来在本地生成文件,即生成在本地目录下,另一个用来下载文件,从本地读取文件然后在页面下载
我在与前端传值的时候用到了阿里开发的fastjson方法,将式子和答案分别转成json对象,然后存入json数组传给前端;在接受前端传来的数据时,我用到了框架中的@RequestParam注解接受前端的传来的值。
@RequestMapping("/create")
public String creatFile(@RequestParam("num") int n, @RequestParam("max") int max, @RequestParam("min") int min, @RequestParam("maxOper") int maxOper, @RequestParam("isBrac") int isBrac, @RequestParam("isMul") int isMul)throws IOException{
File file = new File("../result.txt");
if (file.exists()) { //如果文件已存在,则删除文件
file.delete();
}
JSONArray jsonArray = new JSONArray();
if(file.createNewFile()){
FileOutputStream txtfile = new FileOutputStream(file);
PrintStream p = new PrintStream(txtfile);
// p.println("2017012842");
for(int i=0;i<10;i++){
String[] s = questionService.create(max, min, maxOper, isBrac, isMul);
//将式子和答案分开
String que = s[0];
String answer = s[1];
//用阿里巴巴开发的fastjson将式子和答案分别转成JSON,传给前端
JSONObject jsonObject = new JSONObject();
jsonObject.put("QUESTION", que);
jsonObject.put("ANSWER", answer);
jsonArray.add(jsonObject);
p.print(que);
p.println(answer);
}
txtfile.close();
p.close();
System.out.println("文件创建成功!");
}
return jsonArray.toJSONString();
}
@RequestMapping("/downloadFile")
public String downloadFile(HttpServletRequest request, HttpServletResponse response) {
String fileName = "result.txt";
if (fileName != null) {
String realPath = "D:\\nenuscfx";
File file = new File(realPath, fileName);
if (file.exists()) {
response.setContentType("application/force-download");// 设置强制下载不打开
response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);// 设置文件名
byte[] buffer = new byte[1024];
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
OutputStream os = response.getOutputStream();
int i = bis.read(buffer);
while (i != -1) {
os.write(buffer, 0, i);
i = bis.read(buffer);
}
System.out.println("success");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return null;
}
(二)前端
前端文件结构
前端实现:本次使用的是vue.js结合element-ui进行的前端开发,目的在于实现前后端分离。前端实现了单页面应用,在一个页面内划分不同组件,操作时只需进行组件的替换。同时基于vue.js进行前端开发,避免了多余的DOM渲染,脱离了对DOM进行操作,更加专注于对数据进行操作。
代码展示:
四、PSP
五、总结
这次作业让我收获良多。在这次作业之前,我虽说自己学习了springboot,但还是对其没有很清楚的了解,但通过这次作业,让我熟悉了springboot,终于感觉自己跟上了时代的潮流^-^。