JavaScript借助chance.js随机抽题时 如何避免生成重复的问题数组
随机抽题去重的落地实现方案
问题根因
你现在的逻辑是直接生成随机索引取题,没有对已抽取的条目做去重校验——哪怕题库有2000+道题,多次随机生成索引时仍有小概率命中之前已经抽过的下标,最终出现重复题目。下面两种方案都适配你现有业务(含后续支持1-100自定义抽题量的需求),可以根据改造成本选择:
方案1:Set集合校验索引(改动最小)
完全复用你现有的循环结构,额外加一个存储已抽题目标识的Set结构做快速去重:因为Set的查找时间复杂度是O(1),不会带来额外性能开销。
核心逻辑就是每次生成随机索引后,先看这个索引有没有被用过,如果重复就重新生成,直到拿到没被使用的索引,再推入待作答数组。
this.askQuestions = []; this.questions = JSON.parse(this.questionsData); // 存储已经抽过的题目原始下标,用于快速去重 const usedIndexes = new Set(); // 后续替换成用户自定义抽题量,调试阶段固定为10即可 const needCount = 10; // 兜底:抽题量不能超过题库总长度,避免出现无限循环 const validCount = Math.min(needCount, this.questions.length); for(let i = 0; i < validCount; i++){ let n; // 先生成随机索引,再校验是否被使用过 do { n = chance.integer({min: 0, max: this.questions.length - 1}); } while(usedIndexes.has(n)); usedIndexes.add(n); this.askQuestions.push({ questionIndex: n, question: this.questions[n].question, choices: this.questions[n].possibleAnswers }); }
这个方案的优势是几乎不动原有代码,2000+题库抽10-100题时,索引碰撞的概率极低,极少触发重试,性能完全能覆盖业务场景。
方案2:Fisher-Yates洗牌算法(长期最优)
如果后续抽题量可能持续提升,直接用经典的洗牌算法从根源避免重复:先把题库数组随机打乱顺序,再直接截取需要的题目数量,全程没有索引碰撞、重试的过程,哪怕抽取量接近题库总长度,性能也不会衰减。
this.askQuestions = []; this.questions = JSON.parse(this.questionsData); const needCount = 10; const validCount = Math.min(needCount, this.questions.length); // 拷贝一份题库数组做操作,避免修改原始题库数据 const questionPool = [...this.questions]; // 执行Fisher-Yates无重复随机打乱 for (let i = questionPool.length - 1; i > 0; i--) { // 借助chance生成随机交换位置 const randomIdx = chance.integer({min: 0, max: i}); [questionPool[i], questionPool[randomIdx]] = [questionPool[randomIdx], questionPool[i]]; } // 直接截取前N条就是完全随机、无重复的题目 this.askQuestions = questionPool.slice(0, validCount).map((item, index) => ({ questionIndex: index, question: item.question, choices: item.possibleAnswers }))
注意:不要用题目文本内容做去重判断,实际业务题库可能存在重复录入的同题干题目,用题库里的原始下标做去重标识,才能保证每一条抽取的条目都对应库内唯一的记录。
后续支持自定义抽题量时,只需要把固定的needCount替换为用户传入的1-100区间值,保留和题库总长度的兜底判断即可,两种方案都可以直接适配。
内容的提问来源于stack exchange,提问作者newbiedev
相关产品推荐
相关产品推荐

