Mongo实现分段随机出题

Mongo环境 3.4

需求

我们有一个题库,需要随机生成题目组合,为保证每道题都可能被选中,避免某次选题都是集中在某一段中,所以希望实现分段随机,每段随机取一部分数据

数据样例

db.sample.insert({"name": 1, "age": 20});
db.sample.insert({"name": 2, "age": 20});
db.sample.insert({"name": 3, "age": 20});
db.sample.insert({"name": 4, "age": 20});
db.sample.insert({"name": 5, "age": 20});
db.sample.insert({"name": 6, "age": 20});
db.sample.insert({"name": 7, "age": 20});
db.sample.insert({"name": 8, "age": 20});
db.sample.insert({"name": 9, "age": 20});
db.sample.insert({"name": 10, "age": 20});
db.sample.insert({"name": 11, "age": 20});
db.sample.insert({"name": 12, "age": 20});

实现原理

核心:使用Mongo 语法 $facet , Mongo官方文档-$facet

官方文档说明:对同一输入文档进行多个聚合管道,每个子管道在输出文档中都有自己的字段,其结果存储为文档数组

so: 每个分段就相当于一个聚合管道,分段大小为 从skip开始截至到limit位置, 然后采用Mongo支持的随机函数对每段进行随机取值

最后,查询结果为了方便查看,使用$concatArrays将每个子管道的结果集合合并在一起,最后使用$unwind将集合拆分成单行,这样每行仅有一个题目

算个缺陷吧

如果分段数变化则需要添加$face中的对象,最后在$concatArrays中添加改对象,才可以实现分段数的增加/减少

实现的Mongo语句

db.sample.aggregate([
{
	$facet: {
			"1":[{
				$limit:4
			},{
				$skip:0
			},{
				$sample:{size:2}
		}],
		"2":[{
				$limit:8
			},{
				$skip:4
			},{
				$sample:{size:2}
		}],
		"3":[{
				$limit:20
			},{
				$skip:8
			},{
				$sample:{size:2}
		}]
	}
},
// 将每段获得的数据合并到一个集合中
{
	$project:{
		"questionList":{ 
			$concatArrays: [ "$1", "$2","$3" ] 
		}
	}
},{
	$unwind:"$questionList"
},{
	$project:{
		'name':'$questionList.name',
		'age':'$questionList.age',
	}
}
]);

在这里插入图片描述

JAVA 代码生成Mongo语句示例

创建对象存储分块信息

import lombok.Data;

@Data
public class RandomQuestionBlockEntity {
	
	private Integer limit;// 返回记录数
	private Integer offset; // 跳过记录数
	private Integer index; // 指针位置
	private Integer size;// 块大小
	private Integer returnSize;// 每块返回数据量
	private String indexName;// 块名称
	
}

生成Mongo查询语句方法
需要考虑几种特殊情况:如题目数<分块数, 题目数不能平分到每个块中等

/**
 * 分段随机出题 mongo语句生成
 * @param defaultDb			默认条件
 * @param size				查询题目数
 * @param questionCount		题目总数
 * @return
 */
public static BasicDBList randomQuestionListSQL(BasicDBObject defaultMatchDb, Integer size, Integer questionCount) {
	List<RandomQuestionBlockEntity> splitList = new ArrayList<RandomQuestionBlockEntity>();// 分段信息存储
	RandomQuestionBlockEntity blockEntity = null;
	int splitNum = 10;// 分块数
	if(splitNum > questionCount) {
		// 分块数 大于 题目总数,将分块数设置为1
		splitNum = 1;
	}
	if(splitNum > size) {
		// 分块数 大于 查询记录数,则将分块数设置为记录数
		splitNum = size;
	}
	int blockSize = questionCount/splitNum;// 块大小
	int blockSurplus = questionCount%splitNum;// 不足平分一个块的题目数
	int blockReturnSize = size/splitNum;// 每块返回记录数
	int blockReturnSurplus = size%splitNum;// 不足平分到每个块的题目数
	
	int lastSize = 0;// 之前块大小之和,用于计算当前块跳过的记录数
	for (int i = 1; i <= splitNum; i++) {
		blockEntity = new RandomQuestionBlockEntity();
		blockEntity.setSize(blockSize);// 块大小
		blockEntity.setReturnSize(blockReturnSize);// 返回记录数
		if(blockSurplus > 0) {
			blockEntity.setSize(blockSize+1);
			blockSurplus --;
		}
		if(blockReturnSurplus > 0) {
			blockEntity.setReturnSize(blockReturnSize+1);
			blockReturnSurplus --;
		}
		blockEntity.setIndex(i);
		blockEntity.setIndexName(String.valueOf(i));
		blockEntity.setOffset(lastSize);
		lastSize += blockEntity.getSize();
		blockEntity.setLimit(lastSize);
		splitList.add(blockEntity);
	}
	
	BasicDBList dbList = new BasicDBList();
	BasicDBObject facet = new BasicDBObject();
	
	BasicDBList concatArrays = new BasicDBList();
	BasicDBObject[] dbObjects = null;
	for (RandomQuestionBlockEntity entity : splitList) {
		dbObjects = new BasicDBObject[3];
		dbObjects[0] = new BasicDBObject("$limit", entity.getLimit());
		dbObjects[1] = new BasicDBObject("$skip",entity.getOffset());
		dbObjects[2] = new BasicDBObject("$sample", new BasicDBObject("size", entity.getReturnSize()));
		facet.append(entity.getIndexName(), dbObjects);
		concatArrays.add("$"+entity.getIndexName());
	}
	BasicDBObject match = new BasicDBObject("$match", defaultMatchDb);

	BasicDBObject project = new BasicDBObject("$project", 
			new BasicDBObject("questionList", 
					new BasicDBObject("$concatArrays", concatArrays)));
	
	dbList.add(match);
	dbList.add(new BasicDBObject("$facet", facet));
	dbList.add(project);
	// TODO 最后拆分数组请自行实现
	return dbList;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值