从Meteor集合中获取随机文档

时间:2013-12-02 19:34:17

标签: mongodb meteor

如果没有数字索引,从Meteor集合中获取随机文档的最有效算法是什么?

There is another question使用skip方法在MongoDB中处理,但Meteor似乎不支持这种方法。

我提出的低效方法是选择所有记录并迭代到随机数,但随着集合规模的增长,这显然变得昂贵和繁琐。

6 个答案:

答案 0 :(得分:11)

有同样的问题,但我需要从查询结果中获取一个随机元素。 我找到了一个解决方案,感谢这个提到fetch()的问题: Meteor: Finding an object from a collection by _id

您可以使用此方法将查询转换为数组。因此,将查询结果转换为数组将为Collection.find().fetch()。然后,您可以简单地获取此数组的长度并使用它来生成随机数,并选择该数组的元素。

var array = Collection.find().fetch();
var randomIndex = Math.floor( Math.random() * array.length );
var element = array[randomIndex];

注意:这适用于Meteor,而不是普通的MongoDB!对于MongoDB,请参阅其他答案或使用skip()的链接问题。

答案 1 :(得分:7)

目前MongoDB查询语言没有随机运算符(尽管有an open feature-request ticket for that)。

更新版本3.2:您现在可以使用$sample聚合运算符来获取随机样本。

collection.aggregate(
   [ { $sample: { size: 1 } } ]
)

如果你不能或不想使用它,有一些解决方法,但它们并不漂亮。

一种方法是使用db.collection.count()来获取集合中的文档数量。然后,您可以使用n选择db.collection.find().skip(n).limit(1)个文档。但是当集合很大时,这可能需要一段时间,因为需要使用游标迭代整个集合。

另一种方法是在插入每个文档时为每个文档添加一个字段rand,其随机浮点数介于0.0和1.0之间。然后,您可以生成另一个随机数r并执行db.collection.find({rand:{$gt:r}}).sort({rand:1}).limit(1)以获得下一个更大的随机数。当您在rand字段上有索引时,这将非常快。但随机性不会均匀分布,因为那些恰好在它们与其前身之间存在较大差距的文档将被更频繁地选取。此外,当r恰好大于集合中的最大值时,将不会返回任何结果。在这种情况下,您应该使用相同的号码重试,但这次使用rand:{$lte:r}sort({rand:-1})。如果这也没有返回文档,则该集合为空(或者至少没有包含rand字段的文档。

您可以快速公平地选择随机文档的唯一角落情况是您的收藏不会更改(或者至少不会经常更改)。在这种情况下,您可以使用从0开始的连续整数,索引该字段和find()为0和您已知数量的文档之间的随机数编号所有文档。

答案 2 :(得分:7)

使用下划线,下面对我有用:

function(){
    var random = _.sample(Collection.find().fetch());
    return Collection.find({_id: random && random._id});
}

答案 3 :(得分:0)

受到@dillygirl回应的启发。 如何从N random users集合中选择Meteor.users。我创建了getRandomBots()方法(测试twitter API):

function getRandomBots(numberOfBots){
    var usersCollectionArray = Meteor.users.find().fetch();
    //if you want all the users just getRandomBots();
    if(typeof numberOfBots === "undefined"){
        return usersCollectionArray;
    }
    /***
     * RandomNumbers
     * @param numberOfBots
     * @param max
     * @returns {Array}
     */
    function randomNumbers(numberOfBots, max){
        var arr = []
        while(arr.length < numberOfBots){
            var randomnumber=Math.ceil(Math.random()*max);
            var found=false;
            for(var i=0;i<arr.length;i++){
                if(arr[i]==randomnumber){found=true;break}
            }
            if(!found)arr[arr.length]=randomnumber;
        }
        return arr;
    }
    //length of the users collection
    var count = Meteor.users.find().count();

    //random numbers between 0 and Max bots, selecting the number of bots required
    var numbers = randomNumbers(numberOfBots,count);

    //the bots we are gonna select
    var bots = [];

    //pushing users with using the random number as index.
    _.each(numbers,function(item){
        bots.push(usersCollectionArray[item]);
    });

    //testing on server console ...
    _.each(bots,function(item){
       console.log(item.services.twitter.screenName);
    });
}

//selecting 8 bots
getRandomBots(8);

答案 4 :(得分:0)

您可以使用out集合的random _id属性来解决此问题,它将在第一次调用中随机。由于“选择”,它也可以按不同的顺序排列,但它不是完全随机的,尽管解决了我的任务:

collection.find({},{sort: _id:Random.choice([1,-1])}})

“真正的”随机是:

var items = collection.find({}).fetch();
var random_items = _.shuffle(items);
var random_items_id = _.map(random_items,function(element){return element._id});
return collection.find({_id:{$in:random_items_id}});

答案 5 :(得分:0)

import { Random } from 'meteor/random';

const getRandomDocuments = function(amount) {
    // finds the next _id >= from a random one
    const randomId = Random.id();
    const gteRandom = MyCollection.find({
        _id: { $gte: randomId }
    }, {
        fields: { _id: 1 },
        sort: [
            ['_id', 'asc']
        ],
        limit: amount
    });
    const remainingToGet = Math.max(0, amount - gteRandom.count());
    // if it didn't find enough looks for next _id < the random one
    const ltRandom = MyCollection.find({
        _id: { $lt: randomId }
    }, {
        fields: { _id: 1 },
        sort: [
            ['_id', 'asc']
        ],
        limit: remainingToGet
    });
    // combine the two queries
    let allIds = [];
    gteRandom.forEach(function(doc) {
        allIds.push(doc._id);
    });
    ltRandom.forEach(function(doc) {
        allIds.push(doc._id);
    });
    return MyCollection.find({
        _id: { $in: allIds }
    });
}