【Java】个人项目互评——中小学数学卷子自动生成程序

发布时间 2023-09-20 22:19:41作者: 醉花鱼

 目 录

  一、简介

  二、项目要求

  三、测试与分析

    1、功能测试

    2、代码分析

  四、项目总结

    1、代码优点

    2、代码缺点

  五、结语


一、简介

  本博客用于分析和总结我的结对编程队友王晓婧的个人项目代码,代码使用语言为Java,与本人项目所用编程语言一致。在分析王晓婧同学代码的优缺点的同时,我也同时发现了自己的不足,在这个过程中学习到了很多不同的经验,为之后的结对编程打下了良好的基础。


二、项目要求  

个人项目:中小学数学卷子自动生成程序

用户:

小学、初中和高中数学老师。

功能:

1、命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为小学、初中和高中三个选项中的一个。否则提示“请输入正确的用户名、密码”,重新输入用户名、密码;

2、登录后,系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,XX为小学、初中和高中三个选项中的一个,用户输入所需出的卷子的题目数量,系统默认将根据账号类型进行出题。

每道题目的操作数在1-5个之间,操作数取值范围为1-100;

3、题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录),程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子(具体要求见附表)。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复(以指定文件夹下存在的文件为准,见5);

4、在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,显示“”系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题;

5、生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行;

6、个人项目9月17日晚上10点以前提交至创新课程管理系统。提交方式:工程文件打包,压缩包名为“几班+姓名.rar”。迟交2天及以内者扣分,每天扣20%。迟交2天及以上者0分。

附表-1:账户、密码

 

账户类型

账户

密码

备注

小学

张三1

123

 

张三2

123

 

张三3

123

 

初中

李四1

123

 

李四2

123

 

李四3

123

 

高中

王五1

123

 

王五2

123

 

王五3

123

 

 

附表-2:小学、初中、高中题目难度要求

 

小学

初中

高中

 

难度要求

+,-,*./

平方,开根号

sin,cos,tan

 

备注

只能有+,-,*./和()

题目中至少有一个平方或开根号的运算符

题目中至少有一个sin,cos或tan的运算符

 

 


 

三、测试与分析

  1、功能测试 

  ①登录  

 

 

 

   登录功能很好的完成了题目的需求,并且可以处理前置空格和连续空格的情况,对于输入错误的情况也有处理,对情况的处理很全面,值得表扬。

  ②出题和切换

   对于出题的和切换的测试都正确完成。输入切换为高中能够正常切换,输入其他识别为不需要切换,输入为xx而XX不输入小学初中高中时会输出没有这个切换选项。输入数量不在10-30之间时提示正确的范围,输入范围正确时显示已出题。界面提示和互动比较完整。

  但是这个互动逻辑比较冗杂,功能分隔不明显,很难一下子辨别出最主要功能所在位置。而且测试的时候功能没有单独分隔,而是几个功能分块为一个循环逻辑。(测试的时候我都要爆炸了!!!!!测一个功能要重复多个不必要的逻辑!!!!!比如测试数量的时候不在范围,又要重新输入要不要切换。)而且"Y"的输入不太方便,需要切换中英文输入法,建议改成(“是”/其他输入)。并且加入功能分隔。

   有一点点不算bug的小问题,如果输入数量时没有输入数字,程序会检测到异常自动退出。应该是只在切换时考虑了异常输入,忘记在输入数量时处理异常输入(估计没想到会有人恶意乱打hhh),后续可以进行优化,但是只要是正常使用程序不恶意乱输入,对于整个程序的逻辑是不影响的。

  ③题目

  小学题目

初中题目

   高中题目

   题目符合难度要求,生成题目的位置和文件命名也符合要求。出题逻辑非常全面,考虑到了所有情况,括号位置、数量也完全随机,难度符号也完全随机,题目也完全随机,很值得我学习。(虽然这个减号有一点小奇怪哈)

  2、代码分析

  ①代码结构

   项目共使用了11个类和1个接口。其中SetPtext、SetMtext、SetHtext是对抽象类SetQuestion的实现,分别实现生成小学、初中、高中题目的功能。SaveFile类是SaveText接口的实现,用于创建文件夹、创建文件、实现文件写入。Teacher类是对抽象类User的具体实现,增加了type属性。LoginTeach是对抽象类Login的实现,实现teacher的登录的操作出题的功能。InitList类实现了用户teacher的初始化。Main函数实现主要的项目流程。

  项目对于类的使用很得心应手,体现了面向对象的思想,方便后续的更改和结对项目的代码移植,非常值得夸奖。不过不同的类中使用相同名字方法operate实现不同功能,容易混淆,后续应该进行优化。接下来对功能主要显示的类进行详细分析。

  ①Main

  1 import java.io.File;
  2 import java.io.IOException;
  3 import java.util.Scanner;
  4 
  5 public class Main {
  6   public static void main(String[] args) {
  7 
  8     Scanner sc = new Scanner(System.in);
  9     InitList init = new InitList(); // 生成初始化操作类
 10     Teacher[] teachers = init.operate(); // 初始化教师列表
 11     Login loginTeach = new LoginTeach(teachers); // 生成教师登录操作类
 12 
 13     boolean onOff = true; // 系统开关
 14 
 15     // 外循环,处在系统内状态
 16     while (onOff) {
 17       Teacher teacher = (Teacher) loginTeach.operate();
 18       if (teacher == null) {
 19         break;
 20       }
 21 
 22       // 内循环,已登录状态
 23       while (onOff) {
 24         String type = choose(teacher); // 切换难度
 25         int n = sc.nextInt(); // 输入生成题目数量
 26         if (n == 0) {
 27           System.out.print("已退出系统\n");
 28           onOff = false;
 29         } else if (n != -1) {
 30           if (n >= 10 && n <= 30) {
 31             if (!produce(teacher, type, n)) {
 32               break;
 33             }
 34           } else {
 35             System.out.print("题目数量的有效输入范围是“10-30“,请重新输入\n");
 36           }
 37         } else {
 38           System.out.print("重新登录中……\n");
 39           break;
 40         }
 41       }
 42     }
 43   }
 44 
 45   // 切换难度
 46   public static String choose(Teacher teacher) {
 47     String type = teacher.type;
 48     while (true) {
 49       System.out.print("是否需要切换类型选项?\n");
 50       System.out.print("(输入“切换为XX”/其他)\n");
 51 
 52       Scanner sc = new Scanner(System.in);
 53       String s = sc.next();
 54 
 55       if (s.startsWith("切换为")) {
 56         if (s.startsWith("小学", 3) && s.length() == 5) {
 57           type = "小学";
 58           break;
 59         } else if (s.startsWith("初中", 3) && s.length() == 5) {
 60           type = "初中";
 61           break;
 62         } else if (s.startsWith("高中", 3) && s.length() == 5) {
 63           type = "高中";
 64           break;
 65         } else {
 66           System.out.print("没有这个切换选项,请重新输入!\n");
 67         }
 68       } else {
 69         System.out.print("不需要切换类型选项\n");
 70         break;
 71       }
 72     }
 73 
 74     System.out.printf("当前选择为%s出题\n", type);
 75     System.out.printf("准备生成%s数学题目,请输入生成题目数量\n", type);
 76     System.out.print("(输入-1将退出当前用户,重新登录)\n");
 77     System.out.print("(输入0将退出系统)\n");
 78 
 79     return type;
 80   }
 81 
 82   // 生成题目
 83   public static boolean produce(Teacher teacher, String type, int n) {
 84     File folder;
 85     File file;
 86     SaveFile saveText = new SaveText();
 87     SetQuestion setQuestion;
 88     if (type.equals("小学")) {
 89       setQuestion = new SetPText();
 90     } else if (type.equals("初中")) {
 91       setQuestion = new SetMtext();
 92     } else {
 93       setQuestion = new SetHtext();
 94     }
 95 
 96     try {
 97       folder = saveText.createFolder(teacher); // 创建文件夹
 98       file = saveText.createFile(folder); // 创建文件
 99       saveText.WriteInFile(file, setQuestion.operate(n, folder)); // 生成题目,写入文件
100     } catch (IOException e) {
101       e.printStackTrace(); // 抛出异常
102     }
103 
104     Scanner sc = new Scanner(System.in);
105     System.out.print("已出题,是否需要继续出题?(Y/其他)\n");
106     if (!sc.next().equals("Y")) {
107       System.out.print("不需要,已退出当前账号\n");
108       return false;
109     }
110     System.out.print("需要,请继续出题\n");
111     return true;
112   }
113 }

 

  main函数运用了一个boolean变量,巧妙的决定外层循环,当输入0时退出系统。这里检测int时没有对其他类型输入进行处理,也就是上面贴出的异常出现的原因,可以使用try和catch进行优化。  

  choose实现了切换功能,之前说的切换的交互冗余课可以在此处优化。

  produce实现了出题和保存的功能。这里比较好地方的就是想到了如果文件夹不存在就创建(我当时只记得创建用户的文件夹,忘记创建外层文件夹了,因为当时默认存在了,不过题目也没硬性要求不能提前创建,但是还是能想到创建会更好)。并合理的运用了try和catch处理可能会出现的异常。

  ②SetQuestion

 1 import java.io.File;
 2 import java.io.FileNotFoundException;
 3 import java.io.FileReader;
 4 import java.util.Random;
 5 import java.util.Scanner;
 6 
 7 public abstract class SetQuestion {
 8   protected String[] p_symbol;
 9   protected String[] m_symbol;
10   protected String[] h_symbol;
11   Random random;
12 
13   SetQuestion() {
14     p_symbol = new String[] {"×", "", "", "÷"}; // 小学运算符
15     m_symbol = new String[] {"²", ""}; // 初中运算符
16     h_symbol = new String[] {"sin", "cos", "tan"}; // 高中运算符
17     random = new Random();
18   }
19 
20   // 执行
21   public abstract String[] operate(int n, File file);
22 
23   // 调整
24   protected void adjust(
25       String[] text, int j, int leftBracket, int rightBracket, StringBuffer topic) {
26 
27     // 如果左括号比右括号多,就在末尾添上足够数量的右括号
28     topic.append(")".repeat(Math.max(0, leftBracket - rightBracket)));
29 
30     /*如果最前面有左括号,最后面有右括号,
31     且去掉最前面的左括号后,第一个左括号的位置要在第一个右括号的后面,
32     则把前后这两个括号删掉
33      */
34     if (topic.charAt(0) == '(' && topic.charAt(topic.length() - 1) == ')') {
35       if (topic.indexOf("(", 1) < topic.indexOf(")", 1)) {
36         topic.deleteCharAt(0);
37         topic.deleteCharAt(topic.length() - 1);
38       }
39     }
40 
41     text[j] = topic.toString();
42   }
43 
44   // 随机添加右括号和小学初中特定运算符,返回右括号的数量
45   protected int getRightBracket(
46       int n, int leftBracket, int rightBracket, StringBuffer topic, int i, boolean leftFlag) {
47 
48     topic.append(random.nextInt(100) + 1);
49 
50     if (!leftFlag && (rightBracket < leftBracket) && random.nextBoolean()) {
51       topic.append(")");
52       rightBracket++;
53     }
54 
55     if (random.nextBoolean()) {
56       topic.append(m_symbol[0]);
57     }
58 
59     if (i != (n - 1)) {
60       topic.append(p_symbol[random.nextInt(4)]);
61     }
62     return rightBracket;
63   }
64 
65   // 查重
66   protected boolean check(File file, String topic) {
67     File[] files = file.listFiles(); // 获取文件夹中的所有文件
68 
69     assert files != null;
70     for (File value : files) {
71       String fileName = value.getPath();
72       try (Scanner sc = new Scanner(new FileReader(fileName))) {
73         while (sc.hasNext()) {
74           String line = sc.nextLine();
75           if (line.equals("")) {
76             continue;
77           }
78           int begin = line.indexOf(". ") + 2;
79           int end = line.indexOf("=");
80           String str = line.substring(begin, end); // 取“. ”到“=”这一段题目出来
81           if (topic.equals(str)) { // 对比
82             System.out.print("题目有重复,已重新出题\n");
83             return true;
84           }
85         }
86       } catch (FileNotFoundException e) {
87         e.printStackTrace();
88       }
89     }
90     return false;
91   }
92 }

  operate方法实现了出题,不同子类分别重写实现不同类型的出题方法。getRightBracket方法得到右括号数量,用于最后式子的调整。adjust方法是对最后式子的调整,补全括号的数量,并且删除冗余的括号。check函数用于查重,将文件中的题目取出来一一对比。

  不得不说队友对于出题的思考真的很全面,在精简的代码下实现了题目的完全随机没有限制,而我却因为害怕麻烦害怕代码冗杂而进行了部分题目的限制,这点值得我好好学习。

  ③SetPText实现小学出题

 1 import java.io.File;
 2 
 3 public class SetPText extends SetQuestion {
 4   public String[] operate(int counter, File file) {
 5     String[] text = new String[counter];
 6     for (int j = 0; j < counter; j++) {
 7       int n = random.nextInt(4) + 2;
 8       int leftBracket = 0;
 9       int rightBracket = 0;
10       StringBuffer topic = new StringBuffer();
11 
12       for (int i = 0; i < n; i++) {
13         boolean leftFlag = random.nextBoolean();
14         if (leftFlag && (i != (n - 1))) { // 随机添加左括号
15           topic.append("(");
16           leftBracket++;
17         }
18 
19         topic.append(random.nextInt(100) + 1);
20 
21         // 当操作数前面没有添加左括号时,且右括号数小于左括号数时,随机添加右括号
22         if (!leftFlag && (rightBracket < leftBracket) && random.nextBoolean()) {
23           topic.append(")");
24           rightBracket++;
25         }
26         // 随机添加4个操作符中的一个
27         if (i != (n - 1)) {
28           topic.append(p_symbol[random.nextInt(4)]);
29         }
30       }
31       // 调整
32       adjust(text, j, leftBracket, rightBracket, topic);
33       if (check(file, text[j])) {
34         j--;
35       }
36     }
37 
38     return text;
39   }
40 }

  ④SetMText实现初中出题

 1 import java.io.File;
 2 
 3 public class SetMtext extends SetQuestion {
 4   public String[] operate(int counter, File file) {
 5     String[] text = new String[counter];
 6     for (int j = 0; j < counter; j++) {
 7       int n = random.nextInt(5) + 1;//操作数
 8       int leftBracket = 0;//左括号数
 9       int rightBracket = 0;//右括号数
10       StringBuffer topic = new StringBuffer();//单条题目
11 
12       for (int i = 0; i < n; i++) {
13         boolean leftFlag = random.nextBoolean();//是否添加左括号(随机生成)
14         if (leftFlag && (i != (n - 1))) {
15           if (random.nextBoolean()) {
16             topic.append(m_symbol[1]);//要添加左括号的情况下,再随机在其前面添加根号
17           }
18           topic.append("(");
19           leftBracket++;
20         }
21         //获取右括号数
22         rightBracket = getRightBracket(n, leftBracket, rightBracket, topic, i, leftFlag);
23       }
24       //调整
25       adjust(text, j, leftBracket, rightBracket, topic);
26       //如果整个题目中都没有根号或者平方号,就在末尾加上平方号
27       if ((text[j].indexOf('²') == -1) && (text[j].indexOf('') == -1)) {
28         text[j] = text[j] + "²";
29       }
30       //查重
31       if (check(file, text[j])) {
32         j--;
33       }
34     }
35 
36     return text;
37   }
38 }

  ⑤SetHText实现高中出题

 1 import java.io.File;
 2 
 3 public class SetHtext extends SetQuestion {
 4   public String[] operate(int counter, File file) {
 5     String[] text = new String[counter];
 6     for (int j = 0; j < counter; j++) {
 7       int n = random.nextInt(5) + 1;
 8       int k = random.nextInt(n); // 挑出一个操作数,在它前面必须加上三角函数运算符
 9       int leftBracket = 0;
10       int rightBracket = 0;
11       StringBuffer topic = new StringBuffer();
12 
13       for (int i = 0; i < n; i++) {
14         boolean leftFlag = random.nextBoolean();
15         // 当这个操作数是必须加上三角函数运算符的操作数的时候必须执行,其他时候随机执行
16         if (random.nextBoolean() || (i == k)) {
17           topic.append(h_symbol[random.nextInt(3)]);
18           leftFlag = true;
19         }
20         // 随机加左括号
21         if (leftFlag && (i != (n - 1))) {
22           // 随机加根号
23           if (random.nextBoolean()) {
24             topic.append(m_symbol[1]);
25             // 随机加三角函数运算
26             if (random.nextBoolean()) {
27               topic.append(h_symbol[random.nextInt(3)]);
28             }
29           }
30           topic.append("(");
31           leftBracket++;
32         }
33 
34         rightBracket = getRightBracket(n, leftBracket, rightBracket, topic, i, leftFlag);
35       }
36 
37       adjust(text, j, leftBracket, rightBracket, topic);
38       if (check(file, text[j])) {
39         j--;
40       }
41     }
42 
43     return text;
44   }
45 }

 

四、项目总结

  1、代码优点

  ①对于出题的思考非常全面,在精简的代码下实现了题目的完全随机没有限制。

  ②合理的运用了try和catch处理可能会出现的异常。

  ③对于类的使用很得心应手,体现了面向对象的思想,方便后续的更改和结对项目的代码移植

  ④互动提示设置比较完整。

  2、代码缺点

  ①不同的类中使用相同名字方法operate实现不同功能,容易混淆,后续应该进行优化。

  ②但是这个互动逻辑比较冗杂,功能分隔不明显,很难一下子辨别出最主要功能所在位置。而且测试的时候功能没有单独分隔,而是几个功能分块为一个循环逻辑。

五、结语

  在对于队友代码的互相评价,我们互相发现了对方的优缺点,让我们改正了自己不足的地方,同时变得更加进步更好。希望接下来的结对编程我们能一起加油,做出更好的项目,提高自己。