HNU个人项目评测—中小学数学试卷自动生成程序

发布时间 2023-09-22 08:12:56作者: 熊偲彤

简介

本博客是针对结对编程队友同学的个人项目代码所写的分析与总结,代码使用语言为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”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行;

表1——账号-密码表
image
表2——小学、初中、高中题目难度要求表
image

项目分析

类图

image

流程图

image

代码测试与分析

类分析

接口Difficulty

interface Difficulty {
  String getSymbol();

  String gettype();
}

用于定义出题难度
此处gettype不符合命名规范

相关实现类

PrimaryDifficulty
class PrimaryDifficulty implements Difficulty {
  @Override
  public String getSymbol() {
    return "+-*/";
  }

  @Override
  public String gettype() {
    return "小学";
  }
}

包含小学题目难度所需符号

MiddleDifficulty
class MiddleDifficulty implements Difficulty {
  @Override
  public String getSymbol() {
    return "+-*/^√";
  }

  @Override
  public String gettype() {
    return "初中";
  }
}

包含初中题目难度所需符号

HighDifficulty
class HighDifficulty implements Difficulty {
  @Override
  public String getSymbol() {
    return "+-*/^√sin cos tan";
  }

  @Override
  public String gettype() {
    return "高中";
  }
}

包含高中题目难度所需符号
结构清晰

类ExamSystem

用于生成相应试卷

主要方法

login
public boolean login(String username, String password) {
    User user = authenticateUser(username, password);
    if (user != null) {
      currentUser = user;
      System.out.println("当前选择为" + currentUser.getUserType() + "出题");
      return true; // 登录成功返回 true
    } else {
      return false; // 登录失败返回 false
    }
  }

用来判断登录成功了没

generateExam
 public void generateExam(String currentType, int numQuestions) {
  if (currentUser == null) {
    System.out.println("请先登录");
    return;
  }
  if (numQuestions < 10 || numQuestions > 30) {
    System.out.println("题目数量输入范围不正确");
    return;
  }
  String userType = currentUser.getUserType();
  if ((currentType != userType) && ((currentType.equals("小学")) || (currentType.equals("初中"))
      || (currentType.equals("高中")))) {
    userType = currentType;
  }
  String fileName = generateFileName(userType);
  String check = currentUser.getUsername() + "/" + currentUser.getUsername() + ".txt";
  Set<String> existingQuestions = loadExistingQuestions(check);
  List<String> newQuestions = new ArrayList<>();
  int questionNumber = 1; // 初始化题号
  while (newQuestions.size() < numQuestions) {
    String question = getQuestion(userType);
    if (!existingQuestions.contains(question)) {// 查重
      newQuestions.add(questionNumber + ". " + question + "= (  )");// 添加题号并加入新题目集合
      existingQuestions.add(question);
      questionNumber++; // 题号递增
    }
  }
  saveQuestionsToFile(fileName, newQuestions);
  saveQuestionsToFile2(check, newQuestions);
  System.out.println("题目已生成并保存到文件:" + fileName);
}

利用 QuestionGenerator 类来生成多个题目

getQuestion
private String getQuestion(String userType) {
  String question = new String();
  switch (userType) {
    case "小学": {
      PrimaryQuestion p1 = new PrimaryQuestion();
      question = p1.generateQuestion(1, getNumOperands(userType));
      break;
    }
    case "初中": {
      MiddleQuestion p2 = new MiddleQuestion();
      question = p2.generateQuestion(2, getNumOperands(userType));
      break;
    }
    case "高中": {
      HighQuestion p3 = new HighQuestion();
      question = p3.generateQuestion(3, getNumOperands(userType));
      break;
    }
  }
  return question;
}
authenticateUser
private User authenticateUser(String username, String password) {
  List<User> users = Arrays.asList(new PrimaryUser("张三1", "123"), new PrimaryUser("张三2", "123"),
      new PrimaryUser("张三3", "123"), new MiddleUser("李四1", "123"), new MiddleUser("李四2", "123"),
      new MiddleUser("李四3", "123"), new HighUser("王五1", "123"), new HighUser("王五2", "123"),
      new HighUser("王五3", "123"));

  for (User user : users) {
    if (user.getUsername().equals(username) && user.getPassword().equals(password)) {
      return user;
    }
  }

  return null;
}

用户身份验证,根据用户名和密码匹配用户

getNumOperands
private int getNumOperands(String userType) {
  Random random = new Random();
  int randValue = random.nextInt(100); // 生成一个0到99的随机数
  if ("小学".equals(userType)) {
    if (randValue < 40) {
      return 2; // 40%的概率操作数为2
    } else if (randValue < 70) {
      return 3; // 30%的概率操作数为3
    } else if (randValue < 90) {
      return 4; // 20%的概率操作数为4
    } else {
      return 5; // 10%的概率操作数为5
    }
  }
  if ("初中".equals(userType)) {
    if (randValue < 10) {
      return 1;
    } else if (randValue < 30) {
      return 2;
    } else if (randValue < 60) {
      return 3;
    } else if (randValue < 90) {
      return 4;
    } else {
      return 5;
    }
  }
  if ("高中".equals(userType)) {
    if (randValue < 10) {
      return 1;
    } else if (randValue < 20) {
      return 2;
    } else if (randValue < 40) {
      return 3;
    } else if (randValue < 60) {
      return 4;
    } else {
      return 5;
    }
  }
  return 2; // 默认情况下返回2个操作数
}

根据用户类型返回操作数数量

generateFileName
private String generateFileName(String userType) {
  Date now = new Date();
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
  String folderName = currentUser.getUsername() + "/" + userType + "/" + dateFormat.format(now);
  File folder = new File(folderName);
  folder.mkdirs();
  return folderName + "/" + userType + ".txt";
}

生成文件名,包括基本路径、用户名、用户类型和日期

loadExistingQuestions
private Set<String> loadExistingQuestions(String fileName) {
  Set<String> existingQuestions = new HashSet<>();
  try {
    BufferedReader reader = new BufferedReader(new FileReader(fileName));
    String line;
    while ((line = reader.readLine()) != null) {
      existingQuestions.add(line.trim());
    }
    reader.close();
  } catch (IOException e) {
    // 文件不存在或读取错误,忽略
  }
  return existingQuestions;
}

加载现有题目,返回一个包含题目的集合

saveQuestionsToFile
private void saveQuestionsToFile(String fileName, List<String> questions) {
  try {
    BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
    for (String question : questions) {
      writer.write(question);
      writer.newLine();
      writer.newLine();
    }
    writer.close();
  } catch (IOException e) {
    // 文件保存错误,忽略
  }
}

将题目保存到文件中

saveQuestionsToFile2
private void saveQuestionsToFile2(String fileName, List<String> questions) {
  try {
    BufferedWriter writer = new BufferedWriter(new FileWriter(fileName, true)); // 使用追加模式
    for (String question : questions) {
      writer.write(question);
      writer.newLine();
      writer.newLine();
    }
    writer.close();
  } catch (IOException e) {
    // 文件保存错误,忽略
  }
}

将题目追加到文件中

接口Singlequestion

public interface Singlequestion {
public final Random random = new Random();

// 生成题目的方法
public static String generateQuestion(Difficulty difficulty, int numOperands) {
  return "";
}
}

根据难度和操作数数量生成单个数学题目的字符串。随机生成操作数和运算符,然后组合成一个完整的数学题目,并返回这个题目的字符串。

相关实现类

PrimaryQuestion
class PrimaryQuestion implements Singlequestion {
  public int tag = 0, diff = 0, time = 0;// tag is for simpleset, diff is for difficultset, time is

  public String generateQuestion(int di, int nums) {
    Random random = new Random();
    int numOperands = nums; // 操作数
    StringBuilder question = new StringBuilder();
    for (int i = 0; i < numOperands; i++) {
      int operand = random.nextInt(100) + 1; // 生成1到100的操作数
      question.append(operand);
      if (i < numOperands - 1) {
        question.append(formulas(0, numOperands));
      }
    }
    return question.toString();
  }
public String formulas(int i, int sum) {// i用于判断是否为四则运算,sum用于判断还有多少次
  time++;
  Random random = new Random();
  int operator = random.nextInt(4); // 生成0到3的随机数,代表操作符
  if ((diff == 0) && (time == sum - 1)) {
    operator = 1;
  }
  if (tag > 0) {
    i = 2;
    tag = 0;
  }
  switch (operator) {
    case 0:
      return "-";
    case 1:
      return "*";
    case 2:
      return "/";
    case 3:
      return "+";
  }
  return "";
}
}
MiddleQuestion
class MiddleQuestion implements Singlequestion {
  public int tag = 0, diff = 0, time = 0;// tag is for simpleset, diff is for difficultset, time is
                                         // for time

  public String generateQuestion(int di, int nums) {
    Random random = new Random();
    StringBuilder question = new StringBuilder();
    if (nums == 1) {
      question.append(random.nextInt(100) + 1);
      question = question.append("^2");


      return question.toString();
    }
    for (int i = 0; i < nums; i++) {
      int operand = random.nextInt(100) + 1; // 生成1到100的操作数
      question.append(operand);
      if (i < nums - 1) {
        question.append(formulas(0, nums));
      }
    }


    return question.toString();
  }
public String formulas(int i, int sum) {// i用于判断是否为四则运算,sum用于判断还有多少次
  time++;
  Random random = new Random();
  int operator = random.nextInt(5); // 生成0到4的随机数,代表操作符
  if ((diff == 0) && (time == sum - 1)) {
    operator = 1;
  }
  if (tag > 0) {
    i = 2;
    tag = 0;
  }

  switch (i + operator) {
    case 0:
      String str1 = formulas(2, sum) + "sqrt";
      tag = 1;
      return str1;
    case 1:
      String str0 = "^2" + formulas(2, sum);
      diff++;
      return str0;
    case 2:
      return "-";
    case 3:
      return "*";
    case 4:
      return "/";
    case 5:
      return "+";
  }
  return "+";
}
HighQuestion
class HighQuestion implements Singlequestion {
public int tag = 0, diff = 0, time = 0;// tag is for simpleset, diff is for difficultset, time is

public String generateQuestion(int di, int nums) {
  Random random = new Random();
  StringBuilder question = new StringBuilder();
  if (nums == 1) {
    question.append("sin(");
    question.append(random.nextInt(100) + 1);
    question.append(")");
    return question.toString();
  }
  for (int i = 0; i < nums; i++) {
    int operand = random.nextInt(100) + 1; // 生成1到100的操作数
    question.append(operand);
    if (i < nums - 1) {
      question.append(formulas(0, nums));
    }
  }
  if (tag == 1)
    question.append(")");
  return question.toString();
}
public String charsget(int i, String str, int sum) {

  switch (i) {
    case 0:
      String str1 = formulas(3, sum) + "sin(";
      tag = 1;
      str = str + str1;
      break;
    case 1:
      String str2 = formulas(3, sum) + "cos(";
      tag = 1;
      str = str + str2;
      break;
    case 2:
      String str3 = formulas(3, sum) + "tan(";
      tag = 1;
      str = str + str3;
      break;
    case 3:
      str = str + "+";
      break;
    case 4:
      str = str + "-";
      break;
    case 5:
      str = str + "*";
      break;
    case 6:
      str = str + "/";
      break;
    default:
      str = str + "+";
      break;
  }
  return str;
}

User类

abstract class User {
  protected String username;
  protected String password;
  protected Difficulty difficulty;

  public User(String username, String password, Difficulty difficulty) {
    this.username = username;
    this.password = password;
    this.difficulty = difficulty;
  }

  public abstract String getUserType();

  public String getUsername() {
    return username;
  }

  public String getPassword() {
    return password;
  }

  public Difficulty getDifficulty() {
    return difficulty;
  }
}

功能展示

image
image
image
image

总结

  • 优点:
    代码逻辑清晰、注释明确、格式规范,可读性很高。
    代码结构合理,使用了接口类与其实现,可拓展性很好。
    文件读写操作设计合理,可适应性高。
    功能完整。
  • 缺点:
    一处函数命名不规范。
    加法交换律查重没考虑,我个人认为将1+2和2+1看做一题不合理,但是群里老师这样说了,我也没办法。想出这个问题的人蛮天才的。非常细致的问题。
    用户输入格式的提示可以再详细一点点。