首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >从题库里随机抽题,为什么卷子难度忽高忽低

从题库里随机抽题,为什么卷子难度忽高忽低

原创
作者头像
PC电脑医生
发布于 2026-09-22 15:51:22
发布于 2026-09-22 15:51:22
1360
举报

用小黑课堂2026最新版做模拟卷的时候,可能会有这种感觉:

这一套做起来挺顺,下一套却明显吃力。

对完答案,分数差了不少。

但这两套卷子的题量一样,题型也一样。

为什么难度会有明显波动?

如果只是从题库里随机抽题,这种情况就是必然的。

因为"随机"能保证题目不一样,却保证不了难度一致。

先看直觉解法哪里不对

最直接的做法是:从题库里随机抽 N 道题,拼成一套卷子。

在数据库里,这通常写成一句查询——大意是"按随机顺序取前 N 条"。

这个做法在小题库上没问题。

但题量一大,问题就来了。

第一,性能。 这种写法通常要对整个表排序再取前 N 条。

题库有十万道题,那就是每次抽卷都排十万行。

响应时间会很难看。

第二,也是最关键的:难度不可控。

随机抽出来的题目,难度是"碰运气"的。

如果题库里难题占比是 20%,那某次抽题恰好抽中 5 道难题,完全正常。

下次可能只抽中 1 道。

两套卷子的难度就差出来了。

而且这个偏差会随着抽题次数波动,不会自动收敛。

前提是"难度"得能算

要让难度可控,首先得把"难度"变成数字。

这个数字不是拍脑袋定的,是从历史作答数据里算出来的。

行业里有两个常用指标。

  • 难度系数(含义:考生答对这道题的比例;取值方向:值越小,题越难)
  • 区分度(含义:这道题能不能把高分和低分考生区分开;取值方向:值越高,区分能力越强)

难度系数比较好理解。

如果一道题有 70% 的人答对,那它的难度系数就是 0.7。

这个值越低,说明越少人做对,题就越难。

注意方向——系数小代表难,这一点容易记反。

区分度则更微妙。

它的计算思路是:把考生按总分分成高分组和低分组,比较这两组在这道题上的得分差异。

如果高分组几乎全对、低分组几乎全错——说明这道题区分能力强。

如果两组正确率差不多——不管题目本身是难是易,它都没有区分作用。

那种"所有人都不会做"的题,难度系数很低,但区分度也低。

因为它只是难,不产生区分效果。

这两个指标会变

有一件事需要注意:难度系数不是题目的固有属性。

同一道题,给不同水平的考生做,算出来的难度系数不一样。

所以这个值需要基于大量历史作答数据持续校准。

新题没有数据怎么办?

通常先由出题人给一个初始估计值,等考过几次之后,再用实际数据修正。

这也是为什么题库系统要记录每道题的历史使用情况——没有这些数据,难度就是估的,不是算的。

工程上的做法:分层抽题

有了难度标注,就可以控制整卷难度了——这也是小黑课堂2026最新版这类练考系统在组卷时首先要处理的一层。

主流做法分两步。

第一步:先算配额

假设要出一套 30 题的卷子。

教研上定的难度比例是"易 3 成、中 5 成、难 2 成"。

那么拆出来就是:

  • 易:9 题
  • 中:15 题
  • 难:6 题

配额定好了,接下来按配额去抽。

第二步:分别在每个池子里抽

把题库按难度分成三个池子。

从"易"池里抽 9 道,从"中"池里抽 15 道,从"难"池里抽 6 道。

这样抽出来的卷子,难度构成是确定的。

不管抽多少次,都是 9 易 15 中 6 难。

整个卷子的难度自然就稳定了。

这一步把"全局难度控制"转化成了"局部按量抽取"。

约束从"整卷难度要接近某个值"变成了"每个池子抽固定数量"。

前者需要反复试算,后者一次就能做对。

用伪代码表示,大致是这样:

代码语言:python
复制
def build_paper(quota, pool, per_kp_min=2):
    picked, used_ids = [], set()
    for difficulty, count in quota.items():
        candidates = filter_by_difficulty(pool, difficulty)
        candidates = exclude_used(candidates, used_ids)
        start = random.randrange(len(candidates))
        for i in range(count):
            item = candidates[(start %2B i) % len(candidates)]
            picked.append(item)
            used_ids.add(item["id"])
    picked = ensure_coverage(picked, pool, per_kp_min)
    return picked

这里有几处细节值得说明。

used_ids 是用于同卷去重——同一套卷子里不能出现重复的题。

从随机起点开始取连续的几道,效率比每次随机选一道更高——因为它避免了重复选中同一道题后反复重试。

ensure_coverage 是在后面补一道校验——保证每个知识点至少出现若干次。

这一层单独拿出来做,是因为知识点分布和难度分布是两个独立的约束。

混在一起算,冲突会很难解。

冷门题怎么不被淹没

分层抽题解决了难度问题,但带来一个新问题。

如果每次都从池子里随机取,热门题可能被反复抽到,冷门题永远出不来。

这会造成两个后果:一是不利于题库的更新迭代,二是考生可能反复碰到同一批题。

解决办法是加权。

给每道题维护一个"使用次数"记录。

抽题的时候,使用次数少的题目,被选中的概率调高。

这样冷门题有更多机会露面,热门题不会垄断。

权重可以这样设计:使用次数越多,权重越低。

具体实现上,常见做法是先按权重洗牌,再按顺序取。

另外还有一条更实际的规则:同一考生近期做过的题,权重直接降到很低。

这样能避免他在连续几次练习中反复遇到同一道题。

题型和知识点也要控

难度只是一个维度。

一套合格的卷子还要满足其他条件:

  • 题型配比:选择题、填空题、操作题各多少道
  • 知识点覆盖:每个章节至少出现几次,不能全挤在一章
  • 分值分布:各题型的总分要符合考纲
  • 不重复:同卷内不重复,近期用过的也要避开

约束一多,就可能互相打架。

比如按难度配额抽完之后,发现某个知识点一道题都没抽到。

这时候就得做调整——要么换掉一道题,要么补一道进来。

这种调整叫约束冲突解决。

简单场景下用一个办法就够了:从已抽中的题里挑一道替换掉,换一道能补上知识点的题进来。

替换之后重新校验,不合格就再换。

这在约束不多的时候收敛很快。

但如果约束很复杂——既要满足难度分布、又要满足知识点覆盖、还要满足题型配比,同时不能重复——单纯的替换可能会陷入反复调整。

这种情况下会用更重的算法,比如回溯(试到不合就退回上一步换一个选择),或者遗传算法(把一份试卷当成一个解,通过反复变异和筛选逼近最优)。

这些算法计算量大,一般只在高考、资格考这类高利害场景使用,日常练习系统通常用不上。

为什么每套卷子结构一样、题目不同

最后说一个相关的设计。

大规模考试时,通常需要一次生成很多套卷子。

目的是防止相邻考生对答案。

但这里有个要求:这些卷子的难度结构必须一样。

否则对考生不公平。

做法叫同质题组。

思路是:把知识点、难度、区分度都相近的题目归成一组。

生成多套卷子时,从每一组里各抽一道。

这样出来的卷子,结构完全一致,但具体题目不同。

难度自然也就一致了。

考试系统里的"题序打乱""选项打乱",原理类似——内容不变,顺序变化,同样能起到防抄袭的作用,而且实现成本更低。

回到体验

现在可以解释开头那个现象了。

如果一套练习系统的组卷逻辑只是"随机抽题",那卷子难度会随抽题结果波动。

这个波动不是设计缺陷,是随机抽样的固有性质。

而要做成难度稳定的卷子,需要在背后先算难度系数、定配额、分层抽取、再做约束校验。

每一步都有对应的技术方案,只是用户看不到。

小黑课堂2026最新版这类练考系统,在组卷环节要处理的也是这套逻辑——既要让每次抽的题不一样,又要保证难度结构可预期。

这两件事本身就是矛盾的,需要用算法去平衡。

理解了这一点,也就明白为什么有些系统明明题目很多,卷子质量却不稳定——题量只是原料,抽题策略才决定成品。

安装包地址:

https://www.ijinshan.com/software/xiaoheiketang.html?channel=4019

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 先看直觉解法哪里不对
  • 前提是"难度"得能算
    • 这两个指标会变
  • 工程上的做法:分层抽题
    • 第一步:先算配额
    • 第二步:分别在每个池子里抽
  • 冷门题怎么不被淹没
  • 题型和知识点也要控
  • 为什么每套卷子结构一样、题目不同
  • 回到体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档