V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐工具
RoboMongo
推荐书目
50 Tips and Tricks for MongoDB Developers
Related Blogs
Snail in a Turtleneck
dream4ever
V2EX  ›  MongoDB

MongoDB 按题型保持一定比例抽题

  •  
  •   dream4ever · 132 天前 · 1049 次点击
    这是一个创建于 132 天前的主题,其中的信息可能已经有所发展或是发生改变。

    后端技术栈用的 Node.js + Mongoose + MongoDB,数据库中的每道题目分属不同的题型(单选 /多选 /判断),现在需要实现的抽题功能如下:

    1. 抽到的各类型题目数量保持在一个比例上,比如每抽 10 道题,就会有 5 道单选,3 道多选,2 道判断。
    2. 能够实现连续抽题不会抽到同一道题。比如用户 A 回答最初的 10 道题都正确,接着再抽取 10 道题,这新的 10 道题不应当和之前的 10 道题有重复的题目。

    以上两点需求,该如何实现呢?谢谢先。

    12 条回复    2021-08-12 10:01:51 +08:00
    xycost233
        1
    xycost233  
       132 天前 via iPhone
    groupby?
    no1xsyzy
        2
    no1xsyzy  
       132 天前
    不是很懂 MongoDB 对此问题产生了什么影响,我觉得你把题目的 id 全在内存里存一份也没问题
    至于 2. 你要存下之前的 10 道题,不断地随机抽取直到有 10 个与之前不同的。这是基础问题,大概是在编程零基础第三堂课的时候就会讲清楚了(循环)。
    xuanbg
        3
    xuanbg  
       132 天前
    @no1xsyzy 如果每个人都能正确区分自己面临的是哪几个问题,知乎或者本论坛上的问题数量起码少 10 倍。如果再能搞清楚什么是问题的核心和本质,什么是问题的表征的话,就几乎不会有什么问题了。
    dream4ever
        4
    dream4ever  
    OP
       131 天前
    @no1xsyzy 嗯,第二点是我没表述清楚。我是希望对于不同的用户,抽到的题目各不相同,比如多个用户抽到的最初的 10 道题,尽量保证互相之间不一样,各个用户再继续抽 10 道题,互相之间还是不一样,所以才会有这个问题。
    Dganzh
        5
    Dganzh  
       131 天前
    描述还是不够清楚,怎么算连续抽?是一个小时内连续答题算连续抽,还是一天,还是一周?
    no1xsyzy
        6
    no1xsyzy  
       131 天前
    @xuanbg 并不涉及『每个人』的问题,我比较扭曲,别人把问题错误地表述得过于简单的话,我会突出描述一个「小学生都会做」。
    @dream4ever 如果不是严格要求的话,用现成的随机抽样就可以了。
    另一种方案,就是把整个题库打乱,然后顺序每次取若干题,定期重新打乱。或者模仿杀戮尖塔那样,耗尽时 shuffle,但可能造成 1st percentile 的性能问题。
    xuanbg
        7
    xuanbg  
       131 天前
    @no1xsyzy 我不过是对不会提问题这个事感概一下罢了。其实楼主这个随机抽题的问题是一个非常好的问题。

    如果需要严格保证同一用户每轮抽到的题都不同,并且每轮抽题每个用户的题也都不相同。那么你需要把用户抽题的轮次也保存起来。在抽题的时候进行两个维度的唯一性判断,一个是用户维度,一个是轮次维度。具体的做法可以先取一个随机数,然后取大于这个随机数的 m 道题。然后在这个结果里面迭代并判断两次唯一性,通过的加入结果集,并且加入轮次集合和用户集合,直到结果集的数量符合输出要求。如果迭代完还不够数,就再来一次,二次,n 次,直到结果集的数量满足要求。
    no1xsyzy
        9
    no1xsyzy  
       130 天前
    @xuanbg 其实你这样有点复杂了,应当只需要建立一个「跨用户的全局轮次维度」,也就是说,抽题的顺序无论是 AABBCC 还是 ABCABC,抽出来的题都是(预先打乱并切分好的题组) 123456
    具体受抽题顺序影响,组合可能是 A1 A2 B3 B4 C5 C6 或者 A1 B2 C3 A4 B5 C6
    只要事先洗牌,也就不需要去判断唯一性,只需要接着上一次抽到的位置之后继续抽就行。
    dream4ever
        10
    dream4ever  
    OP
       119 天前
    @xuanbg
    @no1xsyzy

    我这几天又思考了一下这个随机抽题的需求和实现思路,整理后的内容如下,欢迎指正:

    前提:
    1. 题库中一共有 N 道题。目前这个 N 为四位数,且在可以预期的未来,也不会有大量的增长。
    2. 每道题目均为单选题、多选题、判断题中的一种。
    3. 单选题、多选题、判断题的数量之比为 N1 : N2 : N3,且 N1 + N2 + N3 = N 。

    需求:
    1. 对每个用户来说,在每一轮游戏中,系统会将题库中的这 N 道题目,最多只有一次地、随机地呈现给用户,让用户回答。
    2. 如果用户答对了题库中的所有题目,或者答错了 1 道题,则本轮游戏结束。
    3. 为减轻系统负担,对于题库中的 N 道题,每次从中抽取 M 道题,全部抽完假设共需 L 次。对于每次抽取到的 M 道题,单选题、多选题、判断题的比例,尽量保持在 N1 : N2 : N3 这个比例上,也就是和这三类题目在总题库中的比例尽量相同。
    4. 对同一个用户而言,各轮游戏的题目出现顺序应当不同,比如某一轮最开始拿到的题目是 1 、5 、9 、7,下一轮就不能也是这个顺序了。对于不同用户则没有要求。

    大致实现思路:
    1. 由于题目数量不多,可以在数据库中给题目增加一个序号字段,用自增的正整数来标记每一道题目的序号。
    2. 在每个用户的每一轮游戏开始前,将所有题目的序号按题目类型进行分组,发给用户。例如用户收到的数组是
    arr = [[1, 5, 7, 10, ...], [2, 3, 4, 8, ...], [6, 9, 11, 13, ...]],那么 arr[0]、arr[1] 、arr[2] 分别是所有单选题、多选题、判断题的序号。
    3. 由于在一轮游戏中,每一次需要抽取 M 道题目,那么可知需要抽取单选题的数量为 Q1 = M * N1 / N,多选题为 Q2 = M * N2 / N,判断题为 Q3 = M * N3 / N 。
    4. 前端在 arr[0] 中随机抽取 Q1 个元素为单选题的序号,在 arr[1] 中随机抽取 Q2 个元素为多选题的序号,在 arr[2] 中随机抽取 Q3 个为判断题的序号,并将这三组序号分别从 arr[0]、arr[1]、arr[2] 中移除。
    5. 前端用这三组序号,从后端抽取 Q1 + Q2 + Q3 共 M 道题目并呈现给用户,让用户答题。
    6. 如果这 M 道题用户全部回答正确,则重复第 4 、5 两步,继续抽取新的 M 道题给用户,直到用户答对所有题目,或者答错 1 道题目。

    上面这个思路把抽题功能的主要部分交给了前端来做,感觉这样后端的负担可以小一些,也是对后端不够熟悉,就选择了这么一个相对比较取巧的办法。

    PS:在整理这个需求和实现思路的时候,发现自己的表述的确不够清晰准确,就上面这段文字,来来回回修改了好几遍,用了两个多小时才完成,就这还是感觉表达得不够好,知易行难呐。
    no1xsyzy
        11
    no1xsyzy  
       119 天前
    其实就是分层抽样。
    在一堆 1-based 中混进了一个 0-based
    放进前端做算是可以对选题作弊吧

    恭喜你学会了小黄鸭调试法。
    dream4ever
        12
    dream4ever  
    OP
       118 天前
    @no1xsyzy 小黄鸭调试法很久以前就看到过,不过一直都没有很好地践行这个方法,工作好几年了,感觉工作的方法和习惯还是很原始 @_@
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1076 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 17ms · UTC 21:51 · PVG 05:51 · LAX 13:51 · JFK 16:51
    ♥ Do have faith in what you're doing.