【个人项目互评】结对互评-中小学数学试卷自动生成程序

发布时间 2023-09-20 13:54:57作者: 张小张*_*

目录

  • 1. 简介
  • 2. 项目要求
  • 3. 代码分析
  • 4. 运行测试
  • 5. 优缺点分析

  1.简介

本篇博客是对结对编程队友对于项目《中小学数学卷子自动生成系统》的学习,分析与总结,选用的编程语言为Java.

  2.项目要求

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

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

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

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

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

   3.代码分析

3.1.整体架构

3.2 代码解读

  main方法:

public static void main(String[] args) {
    userList.add(user1);
    userList.add(user2);
    userList.add(user3);
    userList.add(user4);
    userList.add(user5);
    userList.add(user6);
    userList.add(user7);
    userList.add(user8);
    userList.add(user9);
    loginMenu();
  }
}

 User类:

class User {
  private String userType;
  private String userName;
  private String userPassword;
  private List<Question> questions = new ArrayList<>();

  public User() {
    this.userType = "0";
    this.userName = "0";
    this.userPassword = "0";
  }

  public User(String userType, String userName, String userPassword) {
    this.userType = userType;
    this.userName = userName;
    this.userPassword = userPassword;
    FileManager.createFolder(this);
  }

  public String getType() {
    return userType;
  }

  public String getName() {
    return userName;
  }

  public String getPassword() {
    return userPassword;
  }

  public void setUserType(String userType) {
    this.userType = userType;
  }

  public void addQuestion(Question question) {
    this.questions.add(question);
  }

  public List<Question> getQuestions() {
    return this.questions;
  }
}

分析:每个用户会有独自的存储题目的List,在保存进入文件前题目存储在List中。我认为可以在生成题目时用一个List存储数据,这样可以减少内存占用,同时避免了同一用户多次出题需要清空List的操作

 Main类:

public class Main {
  // 预置的用户
  static User user1 = new User("小学", "张三1", "123");
  static User user2 = new User("小学", "张三2", "123");
  static User user3 = new User("小学", "张三3", "123");
  static User user4 = new User("初中", "李四1", "123");
  static User user5 = new User("初中", "李四2", "123");
  static User user6 = new User("初中", "李四3", "123");
  static User user7 = new User("高中", "王五1", "123");
  static User user8 = new User("高中", "王五2", "123");
  static User user9 = new User("高中", "王五3", "123");
  // 用户列表
  public static List<User> userList = new ArrayList<>();
  // 当前用户的历史题目
  public static List<String> questionList = new ArrayList<>();

  // 登陆界面
  public static void loginMenu() {
    while (true) {
      System.out.println("====================\n请输入用户名和密码:");
      Scanner input = new Scanner(System.in);
      String id = input.next();
      User user = new User();
      int searched = 0;
      // 遍历用户列表查找账号
      for (User value : userList) {
        if (id.equals(value.getName())) {
          user = value;
          searched++;
        }
      }
      if (searched == 0) {
        System.out.println("请输入正确的用户名、密码!");
        continue;
      }
      // 检测密码正确性
      String password = input.next();
      if (password.equals(user.getPassword())) {
        userMenu(user);
        break;
      } else {
        System.out.println("请输入正确的用户名、密码!");
      }
    }
  }

  // 用户界面
  public static void userMenu(User user) {
    // 读取用户历史题目
    FileManager.readTxt(user, questionList);
    while (true) {
      System.out.println("====================\n准备生成"
              + user.getType() + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):");
      Scanner input = new Scanner(System.in);
      String quest = input.next();
      int num;
      try {
        num = Integer.parseInt(quest);
      } catch (NumberFormatException e) {
        int law = 0;
        if (quest.startsWith("切换为") && quest.length() > 4) {
          if (quest.startsWith("小学", 3)) {
            user.setUserType("小学");
            law = 1;
          }
          if (quest.startsWith("初中", 3)) {
            user.setUserType("初中");
            law = 1;
          }
          if (quest.startsWith("高中", 3)) {
            user.setUserType("高中");
            law = 1;
          }
        }
        if (law == 0) {
          System.out.println("请输入小学、初中和高中三个选项中的一个");
        }
        continue;
      }
      if (num == -1) {
        loginMenu();
      } else {
        QuestMaker(user, num);
      }
      break;
    }
  }

  // 题目生成界面
  public static void QuestMaker(User user, int num) {
    while (true) {
      if (num < 10 || num > 30) {
        System.out.println("请输10~30内的数字");
        userMenu(user);
        break;
      }
      // 生成多个题目
      int i = 0;
      while (i < num) {
        Question question = QuestionFactory.creat(user);
        // 对新生成的题目查重
        if (!questionList.contains(question.expression)) {
          i++;
          user.addQuestion(question);
          System.out.println(i + ".  " + question.expression);
        }
      }
      FileManager.createTxt(user);
      userMenu(user);
    }
  }

分析:main类进行了用户数据的初始化,值得一提的是,输入时处理数据采用了try catch结构对输入进行判断为切换模式或者题目数量或-1,其他情况均会给出输入不合法提示,避免了处理其他未知输入的情况,值得学习. 

  NumberMaker类:

class NumberMaker {
  public static String makeNum(String type) {
    Random rNum = new Random();
    //随机生成1~100的数字
    int num = rNum.nextInt(100) + 1;
    String variable = Integer.toString(num);
    if (type.equals("初中")) {
      switch (rNum.nextInt(4)) {
        case 0:
          variable = variable + "²";
          break;
        case 1:
          variable = "√(" + variable + ")";
          break;
        default:
          // Nothing
      }
    }
    if (type.equals("高中")) {
      switch (rNum.nextInt(10)) {
        case 0:
          variable = "sin" + variable;
          break;
        case 1:
          variable = "cos" + variable;
          break;
        case 2:
          variable = "tan" + variable;
          break;
        case 3:
          variable = variable + "²";
          break;
        case 4:
          variable = "√(" + variable + ")";
          break;
        default:
          // Nothing
      }
    }
    return variable;
  }
}

分析:number类将各个难度特殊运算符与操作数共同生成,并且在根号,平方,三角函数运算符后加入括号来避免歧义,值得学习。但其中“rNum”的命名最好采用小驼峰式命名法

 Question类:

abstract class Question {
  public String type;
  public String expression;

  // 拓展题目的方法
  public static void questionEx(StringBuilder buf, User user) {
    Random rNum = new Random();
    // 通过四则运算拓展题目,包含加括号
    switch (rNum.nextInt(8)) {
      case 0:
        buf.append("+").append(NumberMaker.makeNum(user.getType()));
        break;
      case 1:
        buf.append("-").append(NumberMaker.makeNum(user.getType()));
        break;
      case 2:
        buf.append("*").append(NumberMaker.makeNum(user.getType()));
        break;
      case 3:
        buf.append("/").append(NumberMaker.makeNum(user.getType()));
        break;
      case 4:
        buf.insert(0, NumberMaker.makeNum(user.getType()) + "+(").append(")");
        break;
      case 5:
        buf.insert(0, NumberMaker.makeNum(user.getType()) + "-(").append(")");
        break;
      case 6:
        buf.insert(0, NumberMaker.makeNum(user.getType()) + "*(").append(")");
        break;
      case 7:
        buf.insert(0, NumberMaker.makeNum(user.getType()) + "/(").append(")");
        break;
      default:
        //Nothing
    }
  }
}

分析:通过抽象类Question完成包含加减乘除的基础题目,接下来三个具体类QuestionLevel1,QuestionLevel2,QuestionLevel3继承抽象类,在生成题目过程中加入相应难度运算符.

  FileManager类:

class FileManager {
  // 创建文件夹
  public static void createFolder(User user) {
    File fileTotal = new File("D:\\questions");
    if (fileTotal.mkdir()) {
      System.out.println("已在D盘创建questions文件夹");
    }
    File fileUser = new File("D:\\questions\\" + user.getName());
    if (fileUser.mkdir()) {
      System.out.println("已在questions文件夹内创建" + user.getName() + "的文件夹");
    }
  }

  // 创建txt文件
  public static void createTxt(User user) {
    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
    Date date = new Date(System.currentTimeMillis());
    try {
      File file = new File("D:\\questions\\"
              + user.getName() + "\\" + formatter.format(date) + ".txt");
      if (file.createNewFile()) {
        System.out.println("结果已保存至D盘questions对应文件夹内");
      }
    } catch (IOException e) {
      System.out.println("结果保存失败!");
      e.printStackTrace();
    }
    // 向txt写入题目
    try {
      FileWriter write = new FileWriter("D:\\questions\\"
              + user.getName() + "\\" + formatter.format(date) + ".txt");
      BufferedWriter questions = new BufferedWriter(write);
      int sNum = 0;
      // 将用户类中暂存的题目存入文件中
      while (!user.getQuestions().isEmpty()) {
        sNum++;
        questions.write(sNum + ".  " + user.getQuestions().get(0).expression + "\n\n");
        user.getQuestions().remove(0);
      }
      questions.close();
      write.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  // 读取用户之前的题目
  public static void readTxt(User user, List<String> questionList) {
    // 清空当前列表
    while (!questionList.isEmpty()) {
      questionList.remove(0);
    }
    File file = new File("D:\\questions\\" + user.getName());
    File[] files = file.listFiles();
    // 遍历用户文件夹内文件
    if (files != null) {
      for (File f : files) {
        try {
          // 从缓冲区读取文件内容
          FileReader read = new FileReader(f.getPath());
          BufferedReader questions = new BufferedReader(read);
          String question;
          question = questions.readLine();
          do {
            if (question.length() > 4) {
              question = question.substring(question.indexOf(".  ") + 3);
              questionList.add(question);
            }
            // 跳过空行
            questions.readLine();
            question = questions.readLine();
          } while (question != null);
          questions.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

分析:FileManager类用于文件读取写入查重操作,结构清晰,代码简洁易懂。但是存储方式采取绝对路径,此处可以考虑改进。其次,“sNum”的命名最好采用小驼峰式命名法。

  QuestionFactory类:

class QuestionFactory {
  public static Question creat(User user) {
    return switch (user.getType()) {
      case "小学" -> new questionLevel1(user);
      case "初中" -> new questionLevel2(user);
      case "高中" -> new questionLevel3(user);
      default -> null;
    };
  }
}

分析:采用了设计模式中的工厂模式,通过swtich case结构使得难度的选择更为简洁.

 总结:

1.代码作者了解并善于运用java的设计模式,以及高级复杂的结构来使代码更加简洁,高级。

2.作者采用的生成题目的方式具有较高的灵活性。并且合理规范的使用括号来避免题目歧义,值得学习。

3.存储的方式没有采取相对路径,一些“rNum”,“sNum”的小驼峰式命名语义不明确。除此之外,作者对于java的代码规范较为熟悉,并且循规蹈矩,没有其他问题。

 

  4.运行测试

 登录界面:

当输入错误用户名或者不合法同户名,错误密码时均为输出提示

出题界面:

出题界面可以切换模式,当输入符号要求时切换对应模式,否则给出对应提示,输入为-1时退出登录,重新登录

生成题目:

输入不符合10-30的范围时均报错,给出提示信息

在生成题目后可以在输出栏看到题目,并且可以看到存储的文件夹位置

测试三次输出高中题目结果,没有重复题目。

  5.优缺点分析

优点:

  • 代码中包含高级语法,代码结构简洁清晰,可读性高
  • 使用了工厂模式进行设计,增加了可拓展性
  • 正确实现了抽象类与继承抽象类的应用

缺点:

  • 文件存储采取绝对路径存储
  • 一处命名不规范