Ad astra per aspera.|

JohnDoe042

园龄:1年5个月粉丝:0关注:0

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

个人项目互评

代码作者:软件2101胡信航

评价者:软件2101马千凌

一、项目要求

实现中小学数学卷子自动生成程序:

  • 命令行输入用户名和密码,两者之间用空格隔开,如果用户名和密码都正确,将根据账户类型(小学、初中和高中)准备出题。预设的有以下账户:

    账户类型 账户 密码
    小学 张三1 123
    小学 张三2 123
    小学 张三3 123
    初中 李四1 123
    初中 李四2 123
    初中 李四3 123
    高中 王五1 123
    高中 王五2 123
    高中 王五3 123
  • 登录后输入范围“10-30”(含10,30,或-1退出登录)的,程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子。难度要求如下:

    小学 初中 高中
    难度要求 +,-,*./ 平方,开根号 sin,cos,tan
    备注 只能有+,-,*./和() 题目中至少有一个平方或开根号的运算符 题目中至少有一个sin,cos或tan的运算符
  • 生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行。

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

二 、项目结构

  1. 程序的入口为Main文件的main方法,先创建一个Run类的对象,再调用其初始化方法启动程序,main函数简洁易懂。
  2. Run类为Runnable接口的实现,主要包含一些用于处理逻辑跳转的函数,这些方法分为2层,第一层处理用户登录,第二层处理用户输入的指令,层次分明。
  3. Run类中应用到的Login类和PaperGeneration类均由相应接口实现而来,分别实现用户登陆、生成试卷的功能。
  4. 用户类User为抽象类,自带密码检查的方法,小、初、高老师均继承User类,在此基础上增加了获取其学校级别的方法。

运行流程图:

image-20230919194423432

三、代码分析

  • Main类

    public class Main {
      public static void main(String[] args) {
        Run run = new Run();
        run.init();
      }
    }
    

    通过创建一个Run类的对象,再调用其初始化方法开始程序。

  • User抽象类

    public abstract class User {
      private String userName;
      private String password;
    
      User(String userName, String password) {
        this.userName = userName;
        this.password = password;
      }
    
      public String getUserName() {
        return userName;
      }
    
      /**
       * 验证输入的账号密码是否匹配.
       */
      public boolean check(String userName, String password) {
        return this.userName.equals(userName) && this.password.equals(password);
      }
    
      /**
       * 返回年级信息(小学、初中、高中).
       */
      public abstract String getLevel();
    }
    

    User抽象类有三个继承:

    1. public class PrimaryTeacher extends User {
        public PrimaryTeacher(String userName, String password) {
          super(userName, password);
        }
      
        @Override
        public String getLevel() {
          return "小学";
        }
      }
      
    2. public class MiddleTeacher extends User {
        public MiddleTeacher(String userName, String password) {
          super(userName, password);
        }
      
        @Override
        public String getLevel() {
          return "初中";
        }
      }
      
    3. public class HighTeacher extends User {
        public HighTeacher(String userName, String password) {
          super(userName, password);
        }
      
        @Override
        public String getLevel() {
          return "高中";
        }
      }
      

    对于不同的继承,在调用getLevel()函数时返回不同的结果,以此表现不同的用户类型。

  • Runnable接口

    public interface Runnable {
    
      /**
       * 初始化和运行函数,第一层处理登录,第二层处理输入.
       */
      void init();
    
      /**
       * 判断输入是否是数字.
       */
      boolean isNumber(String num);
    
      /**
       * 处理输入的字符,输入正确,实现当前试卷类型的切换,输入错误,给出提示信息.
       */
      int inputString(String s);
    
      /**
       * 处理输入的字符,输入正确,生成指定数量的题目,输入错误,给出提示信息.
       */
      int inputNumber(String num);
    
      /**
       * 处理输入命令,先判断是否为数字,再调用函数判断格式.
       */
      int inputCommand(String command);
    
      /**
       * 将题目类型转换为数字.
       */
      int inputLevel(String level);
    
    }
    

    运行类Run的接口,比较重要的几个方法如下:

    1. @Override
      public void init() {
        User user = login.login();
        state = user.getLevel();
        System.out.println("当前选择为" + state + "出题");
        System.out.println("准备生成" + state + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):");
        int command;
        while (true) {
          command = inputCommand(in.nextLine());
          if (command == 1) {
            continue;
          }
          if (command == -1) {
            init();
          } else {
            paperGeneration.paperGeneration(command, user.getUserName(), inputLevel(state));
          }
        }
      }
      

      负责登录信息的显示、处理输入命令解析后的结果以及试卷的生成。

    2.   @Override
        public int inputCommand(String command) {
          String[] param = command.split(" ");
          int l = param.length;
          if (l != 1) {
            System.out.println("请输入有效格式");
            return 1;
          }
          if (!isNumber(command)) { //如果输入的不是数字,返回0
            if (!command.startsWith("切换为")) {
              System.out.println("请输入正确格式");
            } else if (inputString(command) == 1) { //如果成功切换
              System.out.println("准备生成" + state + "数学题目,请输入生成题目数量");
            }
            return 1;
          } else { //如果输入的是数字,返回输入值
            int n = inputNumber(command);
            if (n == 0) {
              return 1;
            }
            return n;
          }
        }
      

      将用户输入的命令解析为返回值提供给init()。

    3. @Override
      public int inputString(String s) {
        switch (s) {
          case "切换为初中" -> {
            if (state.equals("初中")) {
              System.out.println("类型已经为初中,请勿重复设置");
            } else {
              state = "初中";
              return 1;
            }
          }
          case "切换为小学" -> {
            if (state.equals("小学")) {
              System.out.println("类型已经为小学,请勿重复设置");
            } else {
              state = "小学";
              return 1;
            }
          }
          case "切换为高中" -> {
            if (state.equals("高中")) {
              System.out.println("类型已经为高中,请勿重复设置");
            } else {
              state = "高中";
              return 1;
            }
          }
          default -> System.out.println("请输入小学、初中和高中三个选项中的一个");
        }
        return 0;
      }
      

      处理切换出题难度的方法。

  • Loginable接口

    public interface Loginable {
    
      /**
       * 用户文件读取,用于读取文件并初始化用户数组.
       */
      void readFile();
    
      /**
       * check函数用于遍历用户数组,账号密码匹配返回在数组的下标,否则返回-1.
       */
      int check(String userName, String password);
    
      /**
       * login函数用于命令行实现用户登录.
       */
      User login();
    }
    

    Login类的接口,主要实现登录功能。

  • PaperGenerationable接口

    public interface PaperGenerationable {
    
      String[] symbols = {
        "+", "-", "*", "/"
      };
      String[] power = {
        "²", "√(", ")"
      };
      String[] trigonometric = {
        "sin", "cos", "tan", "°"
      };
    
      /**
       * 按格式返回当前时间.
       */
      String time();
    
      /**
       * 用于改变数字的值,使数字带上平方或者根号,满足初中难度.
       */
      String changeMiddle(int input);
    
      /**
       * 用于改变数字的值,使数字带上三角函数,满足高中难度.
       */
      String changeHigh(int input);
    
      /**
       * 根据难度处理单个数字.
       *
       * @param level 试卷类型
       * @param n 要处理的数字
       * @param j 现在的数字位置
       * @param r 一个问题中总共有多少数字
       * @return String 处理完的数字
       */
      String getChange(int level, int n, int j, int r);
    
      /**
       * 为单个数字加上符号或者括号.
       *
       * @param j 处理1个问题中的第几个数字
       * @param num 括号位置
       * @param p 经过难度处理的数字
       * @param question 问题
       * @param s 符号在数组中的位置
       * @param r 题目有多少个数字
       * @return String 对一个数字处理后的问题
       */
      String getQuestion(int j, int num, String p, String question, int s, int r);
    
      /**
       * 按类型生成单个题目.
       */
      String generateQuestion(int level);
    
      /**
       * 按照题目的数量,当前用户,要生成的类型生成对应试卷.
       */
      void paperGeneration(int number, String userName, int level);
    
      /**
       * 读取指定用户已经出的题,放入paperSet中.
       */
      void readFile(String userName);
    
      /**
       * 将一份试卷写入文件中.
       */
      void writeFile(HashSet<String> paper, String userName, String filename);
    
      /**
       * 对指定题目进行查重,没有重复返回真,否则返回假.
       */
      boolean duplicateCheck(String question, String userName);
    
    }
    

    此为生成试卷的类PaperGeneration的接口,定义了几个符号集和一些接下来要用到的函数。比较重要的几个函数的实现如下:

    1. @Override
      public String getChange(int level, int n, int j, int r) {
        String p;
        if (level == 0) { //小学
          p = Integer.toString(n);
        } else { //初中
          flag = 0;
          if (level == 1) {
            p = changeMiddle(n);
            if (j == r && flag == 0) {
              p = power[1] + n + power[2]; //如果一道题没有一个平方或者根号,使最后一个数加上根号
            }
          } else {
            p = changeHigh(n);
            if (j == r  && flag == 0) {
              p = trigonometric[0] + n + trigonometric[3]; //如果一道题没有一个三角函数,使最后一个数变为sin
            }
          }
        }
        return p;
      }
      

      检查题目是否含有特殊运算(在初高中难度下),如果没有则给最后的操作数加上特殊运算。

    2. @Override
      public String getQuestion(int j, int num, String p, String question, int s, int r) {
        if (j == num && r != j) { //添加左括号,前提不是题目中最后一个数字
          if (j == 0) {
            question = "(" + p + " ";
          } else {
            question += symbols[s] + " (" + p + " ";
          }
        } else if (j == num + 1 && j != 0) { //添加右括号
          question += symbols[s] + " " + p + ") ";
        } else { //不添加括号
          if (j == 0) {
            question = p + " ";
          } else {
            question += symbols[s] + " " + p + " ";
          }
        }
        return question;
      }
      

      为文件中的某个操作数加上符号或者括号。

    3. @Override
      public String generateQuestion(int level) {
        String question = "";
        int r = random.nextInt(5); //r表示这个题目有多少个数字
        if (level == 0 && r == 0) {
          r++;
        }
        int num = -1; //在哪个地方插入括号
        if (r >= 2) {
          num = random.nextInt(4);
        }
        for (int j = 0; j <= r; j++) {
          int s = random.nextInt(4); //生成符号+-*/中的一个
          String p; //p表示1个数字的变化
          int n = random.nextInt(100) + 1;
          p = getChange(level, n, j, r);
          if (j == num && s == 3) { //如果括号在除法下,去掉括号,防止0的出现
            num = -1;
          }
          question = getQuestion(j, num, p, question, s, r);
        }
        question += "=";
        return question;
      }
      

      生成单道题目。

    4. @Override
      public void paperGeneration(int number, String userName, int level) {
        HashSet<String> paper = new HashSet<>();
        for (int i = 0; i < number; i++) {
          String question = generateQuestion(level); //生成1个题目
          if (duplicateCheck(question, userName)) { //查重
            paper.add(question); //添加到HashSet中
          } else {
            i--; //如果重复,重新生成题目
          }
        }
        writeFile(paper, userName, time()); //写入文件
      }
      

      按照题目的类型、当前用户、题目数量生成符合要求的文件。

      生成的题目经过查重后加入历史题目集。

    5. @Override
      public boolean duplicateCheck(String question, String userName) {
        readFile(userName); //读取已经出过的题,添加到HashSet中
        for (String s : paperSet) { //遍历集合
          if (question.equals(s)) { //如果重复,返回false
            return false;
          }
        }
        return true;
      }
      

      将生成的单道题目和用户历史题目比较,相同则重复,返回真。

四、程序测试

1. 登陆:

image-20230918205511890

可以发现如果输入错误的格式、未输入密码、密码错误均会有相应报错,报错后可以继续进行输入。

输入正确的账号、密码,程序会有相应提示,且会说明当前的出题难度。

image-20230918210101763

退出当前账号并登录其他账号,退出过程没有问题,且重新登录后均有正确的提示。

经过测试,登录部分可以正常运行且没有bug。

2. 切换命令测试

输入错误指令、一行输入多个指令均会有相应提示,输入正确指令可以正常切换,在已经是某一难度的情况下再次切换该难度同样会有提示。

经过各种测试,切换难度部分逻辑完备。

3. 题目生成测试

输入超过范围的数量、同一行输入多个数字会有错误提示。

输入符合要求的值则会在当前用户的文件夹内生成所需的文件,文件名为当前时间,符合要求。

生成完文件后程序提示继续输入命令,可以退出或者继续生成题目。

生成的题目符合当前难度的要求,数量也没有问题。

切换难度,生成的题目同样符合要求。

切换用户,文件生成位置、题目难度发生相应改变,验证后均准确无误。

4. 查重测试

查重有关代码如下:

@Override
public void paperGeneration(int number, String userName, int level) {
  HashSet<String> paper = new HashSet<>();
  for (int i = 0; i < number; i++) {
    String question = generateQuestion(level); //生成1个题目
    if (i == 0) { //测试查重
      question = "91 - 76 =";
      i++;
    }
    if (duplicateCheck(question, userName)) { //查重
      paper.add(question); //添加到HashSet中
    } else {
      i--; //如果重复,重新生成题目
    }
  }
  writeFile(paper, userName, time()); //写入文件
}

检查上传效果的方法如下:修改paperGeneration函数,添加一条if语句测试查重功能。将第一个问题改为“91 - 76 =”,这道题在张三1出过的试卷中已经出现,为避免进入死循环,将i++,最后会生成number-1道题目,只需检查试卷中有无指定题目“91 - 76 =”即可。

查看文本文件,发现并没有出现91-76这道题,查重有效。

将91-76改为91+76,这道题没有在之前的试卷中出现,重新生成,发现有这道题目,证明查重功能有效。

五、优缺点分析

优点

  1. 代码在功能方面没有问题,对各种错误输入均有处理。
  2. 代码符合Google语言规范,注释齐全。
  3. 文件保存在相对路径output/paper/name下,文件名和需求相同。
  4. 具体类均有相应接口类或抽象类(不考虑Main),根据功能不同分为几个类,较为直观。
  5. 方法行数均未超过40行,阅读难度不高。
  6. 咋子高中难度下对操作数的值进行了限制,防止出现tan(90°)等情况。

可改进的点

  1. 因为随机性的原因乘、除号常常在同一题中连续出现,导致题目计算过于困难。

  2. 涉及到三角函数时会生成难以手算的式子。

  3. 文件结构可以进行些许优化,利用package可以让文件结构更清晰。

  4. 方法命名、传入参数的命名不是很直观,可以改进。

六、评分

该个人项目两个方法将单个字母作为比较重要的变量,扣10分。

本文作者:hnu-mql's

本文链接:https://www.cnblogs.com/hnu-mql/p/17715438.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   JohnDoe042  阅读(404)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起