经过测试,我对考评的模板抽题算法做了相应的优化调整,优化后经过单元测试确认是可用的。在上篇博客中,我只是写出了相应的代码片段,并做了相应的分析,在这个博客中,我们首先来看一下模板抽题的整体实现。
public ExamPaperModel randomQuestion(ExamPaperModel examPaperModel,List<ExamQuestionMainModel> mainList,List<TemplateChapterDetailEntity> chapterList){
if (ObjectUtils.allNotNull(examPaperModel,mainList,chapterList))
{
//region 初始化数据:章节分布、题型等级分布、题干大小
//在试卷中获得题型集合
List<ExamTemPaperQuestionTypeModel> typeModelList = examPaperModel.getPaperQuestionTypeList();
//region 为试卷上每个大题以及小题分配空间
//声明变量,用于存放试卷上的所有题干
ExamQuestionMainModel[][] typeMain;
//分配试卷上每个大题的存储空间
typeMain = new ExamQuestionMainModel[typeModelList.size()][];
//分配试卷上每个题型下每个小题的存储空间
for (int i = 0; i < typeModelList.size(); i++) {
typeMain[i] = new ExamQuestionMainModel[typeModelList.get(i).getQuestionTypeNum()];
}
//endregion
//region 为试卷上每个大题中小题的难度等级分配空间
//声明变量,存放每个大题的难度等级,并分配空间
int[][] typeDegrees = new int[typeModelList.size()][];
//声明变量,用于存放每个大题的所有小题的难度等级
int[][] tempTypeDegrees = new int[typeModelList.size()][];
for (int i = 0; i < typeModelList.size(); i++) {
//该大题所有小题的最高难度等级
int maxDegree = typeModelList.get(i).getQuestionMaxDegree();
//该大题期望分值
double questionHopeScore = typeModelList.get(i).getQuestionTypeScore()*(examPaperModel.getHopeScore()/examPaperModel.getScore());
//该大题总分数
double questionScore = typeModelList.get(i).getQuestionTypeScore();
//该大题的所有小题个数
int questionNum = typeModelList.get(i).getQuestionTypeNum();
//返回每个难度等级下有几道题
int[] degrees = scoreDistributionService.distribution(maxDegree+1,questionHopeScore,questionScore,questionNum);
//保存该题型下所有小题的等级分布
typeDegrees[i] = degrees;
//临时变量,用于保存已经抽取的题的难度等级,初始值赋值为0
tempTypeDegrees[i] = new int[degrees.length];
}
//endregion
//region 为每章分配内存空间,并填充每章所对应的分数
//获取试卷的总分数
double paperScore = examPaperModel.getScore();
//声明变量,用于存放每个章节的分数,并分配空间
double[] chapterScore = new double[chapterList.size()];
//声明变量,用于存放已选题每个章节的分数,并分配空间
double[] tempChapterScore = new double[chapterList.size()];
for (int i = 0; i < chapterList.size(); i++) {
//填充每个章节的分数
chapterScore[i] = chapterList.get(i).getChapterRatio() * paperScore/100;
}
//endregion
//endregion
//遍历试卷上每个大题
for (int i = 0; i < typeModelList.size(); i++) {
//region 得到题库中该题型的所有题干集合
//用于存放符合条件的类型的题干集合(如选择题的所有题干)
List<ExamQuestionMainModel> typeMainList = new ArrayList<>();
//获取该课程下某类大题的所有题干
for (int j = 0; j < mainList.size(); j++) {
if (typeModelList.get(i).getQuestionTypeId().equals(mainList.get(j).getQuestionTypeId())) {
typeMainList.add(mainList.get(j));
}
}
//endregion
//region 随机抽题
//根据获取题干的数量与试卷上某大题中所有小题数量对比,执行不同方法
if (typeMainList.size() < typeModelList.get(i).getQuestionTypeNum()) {
//可用题干数量少于试卷要求时
logger.error("题库中该类型的题少于模板要求题数!",typeModelList.get(i).getQuestionTypeName());
return null;
} else if (typeMainList.size() == typeModelList.get(i).getQuestionTypeNum()) {
//region 可用题干数量等于试卷要求时:直接保存(随机排序)
Collections.shuffle(typeMainList);//随机打乱list顺序
for (int j = 0; j < typeMainList.size(); j++) {
//保存到 typeMain 中
typeMain[i][j] = typeMainList.get(j);
}
//endregion
} else {
//region 可用题干数量多余试卷要求时:随机抽题
Random random = new Random();
//region 随机抽题
//定义集合用于去重
Set<Integer> randomcollct = new HashSet<>();
for (int j = 0; j < typeModelList.get(i).getQuestionTypeNum(); j++) {
//判断所有题是否已经随机完
if (randomcollct.size()==typeMainList.size()){
logger.error("题库中符合模板要求的题型不足,章节或难度等级不满足模板条件!");
return null;
}
//region 获取不重复的题干索引
//region 方案一:Set不重复随机
//产生随机数,用于作为所抽题的索引
int typeMainIndex = random.nextInt(typeMainList.size());
//利用set结合的特性,防止产生的随机数有重复的
boolean flag = randomcollct.add(typeMainIndex);
if (flag==false){
j--;
continue;
}
//endregion
//endregion
//根据抽取的 typeMainIndex 索引进行判断
ExamQuestionMainModel examMain = typeMainList.get(typeMainIndex);
//region 等级分布条件判断
//获得当前题干的难度等级
int degree = examMain.getDegree();
//定义变量用于存放某难度等级的题型个数
int tempTypeDegreeNumber = tempTypeDegrees[i][degree] + examMain.getBlankCount();
//当前题型个数与试卷要求的题型个数对比
if (tempTypeDegreeNumber > typeDegrees[i][degree]) {
//说明该难度等级的题目已抽取完毕
j--;
continue;
}
//endregion
//region 章节比例条件判断
String mainChapter = examMain.getChapter();//获取题干章节
//定义章节索引变量
int chapterIndex = -1;
//遍历所有章节
for (int k = 0; k < chapterList.size(); k++) {
//获得本章节在章节集合中的索引
if (chapterList.get(k).getChapter().equals(mainChapter)) {
chapterIndex = k;
break;
}
}
//计算平均每个题的分数
double typeMainScore = typeModelList.get(i).getQuestionTypeScore() / typeModelList.get(i).getQuestionTypeNum();
//计算该小题实际的分数(该题有两个空则按两个小题计算)
double mainScore = typeMainScore * examMain.getBlankCount();
//定义变量并赋值(本章节已选题的分数+该小题分数)
double chapterMainScore = tempChapterScore[chapterIndex] + mainScore;
//判断现在该分数是否超过该章要求的总分数
if (chapterMainScore > chapterScore[chapterIndex]) {
//说明该章节的题目已经抽取完毕
j--;
continue;
}
//endregion
//保存已选题本章节累计总分数
tempTypeDegrees[i][degree] = tempTypeDegreeNumber;
tempChapterScore[chapterIndex] = chapterMainScore;
typeMain[i][j] = examMain;//保存题干
}
//endregion
//endregion
}
//endregion
//region 将取题结果存入试卷中
//将取题结果存入试卷中
for (int j = 0; j < typeModelList.get(i).getQuestionTypeNum(); j++) {
typeModelList.get(i).getQuestionMainList().add(typeMain[i][j]);
}
//endregion
}
//将题干设置到试卷
examPaperModel.setPaperQuestionTypeList(typeModelList);
//返回试卷
return examPaperModel;
}
return null;
}
1、在这次优化中,我首先对可用题数小于试卷上要求题数的情况做了处理,记录到日志,并返回null。
2、删除了不必要的循环赋值,初始化数组后,有些数组没有必要进行初始化赋值如int数组,初始化后默认为0,不用循环赋值为0
3、去重集合set的声明位置应在循环外,这样才能保证set中记录所有所需要的随机数(位置问题要考虑周到)
4、添加了遍历题库中所有题后仍然不能抽到满足要求的题目后的判断退出,避免陷入死循环。