Получать случайный документ из коллекции Meteor

Какой был бы самый эффективный алгоритм для извлечения случайного документа из коллекции Meteor, учитывая, что нет числового индекса?

(Существует еще один вопрос, который касается этого в MongoDB с использованием метода skip, но это, похоже, не поддерживается в Meteor).

Неэффективный способ, с которым я столкнулся, заключался в выборе всех записей и повторении до случайного числа, но это явно становится дорогим и громоздким по мере увеличения размера коллекции.

Ответ 1

В настоящее время язык запросов MongoDB не имеет случайного оператора (хотя есть открытый билет запроса функции для этого).

Обновить версию 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

Имел ту же проблему, но мне нужно получить случайный элемент из результатов запроса. Я нашел решение, благодаря этому вопросу, касающемуся fetch(): Метеор: поиск объекта из коллекции по _id

Вы можете преобразовать запрос в массив с помощью этого метода. Поэтому преобразование результатов запроса в массив будет Collection.find().fetch(). Затем вы можете просто получить длину этого массива и использовать его для генерации случайного числа и выбрать этот элемент массива.

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

ПРИМЕЧАНИЕ: это работает в Метеор, а не в простом MongoDB! Для MongoDB см. Другой ответ или связанные вопросы, которые используют skip().

Ответ 3

С помощью подчеркивания ниже работала для меня:

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

Ответ 4

Вдохновленный от ответа @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);

Ответ 5

Вы можете использовать случайный атрибут _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}});

Ответ 6

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 }
    });
}