在总数不满足的条件下均分不同类型数据的代码逻辑

练习题可进行混合题型练习和各题型(单选、多选、判断)分项练习,可指定题目数量。

题库中包含各类型题目,题库中题量未必满足指定题目总数,在混合练习的前提下,如果题库中题目数足够,三种类型题目数量均分,否则,不满足均分后题目数量的题型取全部题目,剩下的均分,依次类推。如果题目总数不满足,则取题库中全部题目。

例:要取10道题进行混合练习,题库中共有2道单选题,5道多选题,8道判断题,那么应取2道单选,(10-2)/2=4道多选,10-2-4=4道判断。假如题库中有8到单选,1道多选,2道判断,那么应取1道多选,2道判断,和7道单选。

public class QuestionType {
    // 混合题
    public static final int ALL = 0;
    // 单选题
    public static final int RADIO = 1;
    // 多选题
    public static final int MULTI = 2;
    // 判断题
    public static final int JUDGE = 3;
}
@Data
@ApiModel(value = "各类题目数量", description = "各类题目数量")
public class QuestionNumber {
    @ApiModelProperty("单选题数目")
    private int radioCount;

    @ApiModelProperty("多选题数目")
    private int multiCount;

    @ApiModelProperty("判断题数目")
    private int judgeCount;
}

传参 int quType 题目类型 0-混合题 1-单选题 2-多选题 3-判断题,int totalNum 题目数量, Long repoId 题库id 

List<QuQuestion> list = new ArrayList<>();

if (quType == QuestionType.ALL) {
    // 混合题型联系,获取题库中各类题目数量
    QuestionNumber qt = questionService.getQuestionNumberByType(repoId);
    if (qt.getRadioCount() + qt.getMultiCount() + qt.getJudgeCount() <= totalNum) {
        // 三种题目总数少于等于练习卷题目数量,取全部题目
        list = questionService.listByRandom(repoId, null, null, totalNum);
    } else {
        if (qt.getRadioCount() < totalNum / 3 || qt.getMultiCount() < totalNum / 3 || qt.getJudgeCount() < totalNum / 3) {
            // 至少有一种题型题目数量不够
            if (qt.getRadioCount() < totalNum / 3) {
                // 假如单选题目数量不够,检查多选题目数量是否足够
                if (qt.getMultiCount() < (totalNum - qt.getRadioCount()) / 2) {
                    // 假如多选题目数量也不够,单选题多选题取全部,判断题重新取剩余数目
                    qt.setJudgeCount(totalNum - qt.getRadioCount() - qt.getMultiCount());
                } else {
                    // 假如多选题数目足够,检查判断题数目是否足够
                    if (qt.getJudgeCount() < (totalNum - qt.getRadioCount()) / 2) {
                        // 假如判断题数目不够,单选题判断题取全部,多选题重新取剩余数目
                        qt.setMultiCount(totalNum - qt.getRadioCount() - qt.getJudgeCount());
                    } else {
                        // 多选题判断题都足够,多选题判断题数量均分
                        qt.setMultiCount((totalNum - qt.getRadioCount()) / 2);
                        qt.setJudgeCount(totalNum - qt.getRadioCount() - qt.getMultiCount());
                    }
                }
            } else {
                // 单选题数目足够,检查多选题数目是否足够
                if (qt.getMultiCount() < totalNum / 3) {
                    // 假如多选题目数量不够,检查判断题数目是否足够
                    if (qt.getJudgeCount() < (totalNum - qt.getMultiCount()) / 2) {
                        // 假如判断题数目不够,多选题判断题取全部,单选题重新取剩余数目
                        qt.setRadioCount(totalNum - qt.getMultiCount() - qt.getJudgeCount());
                    } else {
                        // 单选题判断题都足够,单选题判断题数量均分
                        qt.setRadioCount((totalNum - qt.getMultiCount()) / 2);
                        qt.setJudgeCount(totalNum - qt.getMultiCount() - qt.getRadioCount());
                    }
                } else {
                    // 假如多选题数目足够,判断题数目不够,判断题取全部,单选题多选题数量均分
                    qt.setRadioCount((totalNum - qt.getJudgeCount()) / 2);
                    qt.setMultiCount(totalNum - qt.getJudgeCount() - qt.getRadioCount());
                }
            }
        } else {
            // 三种题型题目数量都足够
            qt.setRadioCount(totalNum / 3);
            qt.setMultiCount(totalNum / 3);
            qt.setJudgeCount(totalNum - qt.getRadioCount() - qt.getMultiCount());
        }
        list = questionService.listByRandom(repoId, QuestionType.RADIO, null, qt.getRadioCount());
        list.addAll(questionService.listByRandom(repoId, QuestionType.MULTI, null, qt.getMultiCount()));
        list.addAll(questionService.listByRandom(repoId, QuestionType.JUDGE, null, qt.getJudgeCount()));
    }
} else {
    // 单题型练习,按照题库id、题目类型和总数筛选题目
    list = questionService.listByRandom(repoId, quType, null, totalNum);
}

代码倒是生效了,注释也解释清楚了思路逻辑,但是这样写,将来万一要扩展,光逻辑不得写死在当场。另外测试时发现使用以下这个示例:

要取20道题进行混合练习,题库中共有15道单选题,8道多选题,1道判断题,代码出现了错误,细究之下发现上面冗长的代码依然少考虑了分支,比如说,8道多选,虽然满足20/3=6的第一次均分,但是在(10-1)/2=9的第二次均分时,代码缺少了比较8<9的情况而误判多选满足均分条件,因此单选和多选题的选择数量并不正确。

考虑到代码的可复用性和未来扩展,这里考虑引入多重循环,思路其实和之前几乎一致:每次找出均分后数目不足的题型,取其全部,然后再次均分剩余题型,再次取均分后数目不足的题型全部,如此往复,直到所有剩余题型均满足均分,那么取均分量;或所有题型均不满足均分量,都取全部

int[] num = {qt.getRadioCount(), qt.getMultiCount(), qt.getJudgeCount()};

// 剩余总量
int remain = totalNum;
// 均分分母
int avg = num.length;

for (int i = 0; i < num.length; i++) {
    if (num[i] < totalNum / num.length) {
        // 题目数量不满足总数均分,取全部题目数量,num[i]没有变化
        // 剩余数量更新
        remain = remain - num[i];
        // 更新均分分母
        avg--;
    }
}

for (int i = 0; i < num.length; i++) {
    if (num[i] >= totalNum / num.length) {
        if (avg > 1) {
            // 剩余超过一个题型,还需要继续均分
            num[i] = remain / avg;
            // 剩余数量更新
            remain = remain - num[i];
            // 更新均分分母
            avg--;
        } else {
            // 只剩最后一个题型,不再均分,假如题目数量大于剩余数量,取剩余数量
            num[i] = Math.min(remain, num[i]);
        }
    }
}

以上代码使用两个for循环,如果题库中某些类型的题目不够,那么我们需要其他数量足够的类型的题目来补足总数,但是,仔细套入思路,会发现两个for循环并不能满足需求,因为我们无法保证只循环两次就可以使得所有题型满足均分条件。

为改变这个问题,我们需要及时更新remain/avg,而不是一直使用totalNum/num.length,这里就必须采用while的循环方式。

为使得代码便于理解,这里使用了第三种写法(p.s.前两种写法也依然可以继续修改满足需求,但是这里不再展开),思路与之前完全相反:每次均分剩余题目(最开始的剩余题目为题目总数)。针对每种题型,当其满足均分时取均分值;不满足时取全部,并不再计入下次均分。更新每种题型均分后的剩余量。然后用总剩余量再次均分还有剩余量的题型,满足均分时取均分值;不满足时取全部,如此往复,直到总剩余量清零。

这里定义了三个数组,分别用于标识原始题库中的各题型数量num,最终所取的各题型数量result,以及每种题型均分后的剩余量remain

以要取20道题进行混合练习,题库中共有15道单选题,8道多选题,1道判断题为例,初始阶段

num={15,8,1}; remain{15,8,1}; result={0,0,0}; total=20; avg=3; total/avg=6;

第一轮while循环之后

num={9,2,0}; remain{9,2,0}; result={6,6,1}; total=7; avg=2; total/avg=3;

第二轮while循环之后

num={6,0,0}; remain{6,0,0}; result={9,8,1}; total=2; avg=1;

最后进入avg==1的分支,得到最终结果result={11,8,1};

List<QuQuestion> list = new ArrayList<>();

// 各题型题目数量
int[] num = {qt.getRadioCount(), qt.getMultiCount(), qt.getJudgeCount()};
// 各题型剩余题目数量
int[] remain = num;
// 各题型应取题目数量
int[] result = new int[num.length];

// 剩余数量
int total = totalNum;
// 均分分母
int avg = num.length;

while (total > 0) {
    int temp = avg;
    // 每一轮循环,各类型题目取均分数量,若不满足均分数量,取全部,记录该题型剩余量
    for (int i = 0; i < num.length; i++) {
        if (remain[i] > 0) {
            if (avg == 1 || total / avg == 0) {
                // 只剩一个题型,取全部数值
                result[i] = result[i] + Math.min(remain[i], total);
                total = 0;
                break;
            }
            if (remain[i] < total / avg) {
                // 某题型题目数量不满足均分,取该题型全部数量,剩余量归零,分母减一
                result[i] = num[i] + result[i];
                remain[i] = 0;
                temp--;
            } else {
                // 某题型题目数量满足均分,取均分数量,剩余量减去均分值
                result[i] = total / avg + result[i];
                remain[i] = remain[i] - total / avg;
            }
        }
    }
    // 更新剩余总量和均分分母
    avg = temp;
    total = totalNum - IntStream.of(result).sum();
}
list = questionService.listByRandom(repoId, QuestionType.RADIO, null, result[0]);
list.addAll(questionService.listByRandom(repoId, QuestionType.MULTI, null, result[1]));
list.addAll(questionService.listByRandom(repoId, QuestionType.JUDGE, null, result[2]));

以上逻辑已经满足了基本需求,但是仍然忽略了一种情况,即,剩余总量最后一轮有余数的情况。示例:应取题目20,题库中单选7,多选7,判断6。在当前逻辑的情况下,可能会出现{8,6,6}的误判。这也就说明total/avg=0这个分支的逻辑并不完整。好在这最后一轮的逻辑简单,只要稍加变化即可。

 while (total > 0) {
     int temp = avg;
     // 每一轮循环,各类型题目取均分数量,若不满足均分数量,取全部,记录该题型剩余量
     for (int i = 0; i < num.length; i++) {
         if (remain[i] > 0) {
             if (avg == 1) {
                 // 只剩一个题型,取全部数值
                 result[i] = result[i] + Math.min(remain[i], total);
                 total = 0;
                 break;
             }
             if (total / avg == 0) {
                 // 最后一轮,但是有余数total
                 if (remain[i] > 0) {
                     result[i]++;
                     remain[i]--;
                     total--;
                 }
             } else {
                 if (remain[i] < total / avg) {
                     // 某题型题目数量不满足均分,取该题型全部数量,剩余量归零,分母减一
                     result[i] = num[i] + result[i];
                     remain[i] = 0;
                     temp--;
                 } else {
                     // 某题型题目数量满足均分,取均分数量,剩余量减去均分值
                     result[i] = total / avg + result[i];
                     remain[i] = remain[i] - total / avg;
                 }
             }
             if (total == 0) break;

             // 避免死循环,强制跳出while
             cnt++;
             if (cnt > 10) {
                 break;
             }
         }
         // 更新剩余总量和均分分母
         avg = temp;
         total = totalNum - IntStream.of(result).sum();
     }
 }
 list = questionService.listByRandom(repoId, QuestionType.RADIO, null, result[0]);
 list.addAll(questionService.listByRandom(repoId, QuestionType.MULTI, null, result[1]));
 list.addAll(questionService.listByRandom(repoId, QuestionType.JUDGE, null, result[2]));

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值