You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB结合Node.js实现多约束条件的试题筛选方案咨询

MongoDB结合Node.js实现多约束条件的试题筛选方案咨询

Hey there! Let's work through building that constrained question selection logic using MongoDB's aggregation framework in Node.js. You've got a solid set of requirements, so I'll break this down step by step to make sure we hit all your constraints, plus handle edge cases like insufficient questions in certain categories.

第一步:先获取用户已完成的题目ID

首先我们需要从visitedQuestions集合中拿到当前用户已经做过的题目ID列表,这样就能在后续筛选中排除这些题。在Node.js里可以先做一个简单的查询:

const userVisitedIds = await VisitedQuestions.findOne(
  { userId: "target-user-id" }, // 替换成实际的用户标识字段
  { _id: 0, questionIds: 1 }
);
const excludedIds = userVisitedIds?.questionIds || [];

第二步:构建聚合查询满足科目与难度约束

接下来我们用MongoDB的聚合框架来处理核心的筛选逻辑。这里会用到$facet分模块处理不同科目组,再结合难度筛选,最后合并并抽取符合数量要求的题目:

const result = await QuestionBank.aggregate([
  // 阶段1:排除用户已做过的题目,仅保留目标科目
  {
    $match: {
      _id: { $nin: excludedIds },
      subject: { $in: [...Array.from({length:5},(_,i)=>`s${i+1}`), ...Array.from({length:5},(_,i)=>`s${i+6}`)] }
    }
  },
  // 阶段2:分面处理两类科目组(s1-s5 / s6-s10)
  {
    $facet: {
      // 处理s1-s5科目组(单科目最多选2道)
      group1Subjects: [
        { $group: { _id: "$subject", questions: { $push: "$$ROOT" } } },
        { $project: { subject: "$_id", questions: { $slice: ["$questions", 2] }, _id: 0 } },
        { $unwind: "$questions" },
        { $replaceRoot: { newRoot: "$questions" } }
      ],
      // 处理s6-s10科目组(单科目最多选3道)
      group2Subjects: [
        { $group: { _id: "$subject", questions: { $push: "$$ROOT" } } },
        { $project: { subject: "$_id", questions: { $slice: ["$questions", 3] }, _id: 0 } },
        { $unwind: "$questions" },
        { $replaceRoot: { newRoot: "$questions" } }
      ]
    }
  },
  // 阶段3:合并两类科目组的所有可用题目
  {
    $project: {
      allAvailableQuestions: { $concatArrays: ["$group1Subjects", "$group2Subjects"] }
    }
  },
  // 阶段4:按难度分组,应用占比约束截取题目
  { $unwind: "$allAvailableQuestions" },
  {
    $group: {
      _id: "$allAvailableQuestions.difficulty",
      questions: { $push: "$allAvailableQuestions" }
    }
  },
  {
    $project: {
      difficulty: "$_id",
      questions: {
        $slice: [
          "$questions",
          {
            $switch: {
              branches: [
                { case: { $eq: ["$_id", "easy"] }, then: 6 }, // 15*40%=6,最多6道简单题
                { case: { $eq: ["$_id", "medium"] }, then: 11 }, // 15*75%≈11,最多11道中等题
                { case: { $eq: ["$_id", "hard"] }, then: 6 } // 15*40%=6,最多6道难题
              ],
              default: 0
            }
          }
        ],
        _id: 0
      }
    }
  },
  // 阶段5:合并所有难度题目,随机抽取15道
  { $unwind: "$questions" },
  { $replaceRoot: { newRoot: "$questions" } },
  { $sample: { size: 15 } },
  // 可选校验阶段:确保最终结果的科目数量完全符合约束
  {
    $group: {
      _id: "$subject",
      count: { $sum: 1 },
      questions: { $push: "$$ROOT" }
    }
  },
  {
    $match: {
      $expr: {
        $cond: [
          { $in: ["$_id", [...Array.from({length:5},(_,i)=>`s${i+1}`)]] },
          { $lte: ["$count", 2] },
          { $lte: ["$count", 3] }
        ]
      }
    }
  },
  { $unwind: "$questions" },
  { $replaceRoot: { newRoot: "$questions" } },
  { $sample: { size: 15 } }
]);

关键逻辑说明

  • 科目约束处理:通过$facet把两类科目分开处理,用$slice限制单科目最大抽取数量,从源头避免超标。
  • 难度占比约束:根据15道题的总数计算出每个难度的上限,用$slice截取对应数量的题目,满足占比要求。
  • 排除已做题:在聚合最开始的$match阶段用$nin排除用户已完成的题目ID。
  • 随机抽取与校验:先用$sample随机选15道,再加一个校验阶段确保科目数量完全合规,防止因部分科目题量不足导致的小概率超标。

注意事项

  • 如果某个科目或难度的可用题数不足,$slice会自动取所有可用题,这时候你可能需要在Node.js代码里额外判断最终返回的题数:如果不足15,可以适当放宽部分约束,或者提示用户当前题量无法满足需求。
  • 记得替换代码中的"target-user-id"为实际的用户标识字段,确保能正确获取用户的已做题列表。

备注:内容来源于stack exchange,提问作者Bhavesh Shaha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.16 08:23:07