Монгоидный случайный документ - PullRequest
9 голосов
/ 13 октября 2011

Допустим, у меня есть коллекция пользователей.Есть ли способ использовать mongoid для поиска n случайных пользователей в коллекции, где он не возвращает одного и того же пользователя дважды?А теперь допустим, что пользовательская коллекция выглядит следующим образом:

class User
  include Mongoid::Document
  field :name
end

Просто, да?

Спасибо

Ответы [ 9 ]

16 голосов
/ 25 сентября 2013

Если вам нужен только один документ и вы не хотите определять новый метод критериев, вы можете просто сделать это:

random_model = Model.skip(rand(Model.count)).first

Если вы хотите найти случайную модель на основе некоторых критериев:

criteria = Model.scoped_whatever.where(conditions) # query example
random_model = criteria.skip(rand(criteria.count)).first
13 голосов
/ 13 октября 2011

Лучшее решение будет зависеть от ожидаемого размера коллекции.

Для крошечных коллекций просто возьмите их все и .shuffle.slice!

Для небольших размеров n вы можете получить что-то вроде этого:

result = (0..User.count-1).sort_by{rand}.slice(0, n).collect! do |i| User.skip(i).first end

Для больших размеров n я бы рекомендовал создать «случайный» столбец для сортировки. Подробности здесь: http://cookbook.mongodb.org/patterns/random-attribute/ https://github.com/mongodb/cookbook/blob/master/content/patterns/random-attribute.txt

6 голосов
/ 17 апреля 2016

MongoDB 3.2 приходит на помощь с $sample ( ссылка на документ )

РЕДАКТИРОВАТЬ : Самый последний из Mongoid реализовал $ sample, так что выможно вызвать YourCollection.all.sample(5)

Предыдущие версии mongoid

Mongoid не поддерживает sample до Mongoid 6, поэтому вы должны выполнить этот агрегированный запрос с Mongoводитель:

samples = User.collection.aggregate([ { '$sample': { size: 3 } } ])
# call samples.to_a if you want to get the objects in memory

Что вы можете сделать с этим

Я считаю, что функциональность должна скоро появиться в Mongoid, но в то же время

module Utility
  module_function
  def sample(model, count)
    ids = model.collection.aggregate([ 
      { '$sample': { size: count } }, # Sample from the collection
      { '$project': { _id: 1} }       # Keep only ID fields
    ]).to_a.map(&:values).flatten     # Some Ruby magic

    model.find(ids)
  end
end

Utility.sample(User, 50)
3 голосов
/ 20 января 2012

Если вы действительно хотите простоты, вы можете использовать это вместо:

class Mongoid::Criteria

  def random(n = 1)
    indexes = (0..self.count-1).sort_by{rand}.slice(0,n).collect!

    if n == 1
      return self.skip(indexes.first).first
    else
      return indexes.map{ |index| self.skip(index).first }
    end
  end

end

module Mongoid
  module Finders

    def random(n = 1)
      criteria.random(n)
    end

  end
end

Вам просто нужно позвонить User.random(5), и вы получите 5 случайных пользователей.Это также будет работать с фильтрацией, поэтому, если вы хотите, чтобы только зарегистрированные пользователи могли делать User.where(:registered => true).random(5).

Это займет некоторое время для больших коллекций, поэтому я рекомендую использовать альтернативный метод, где вы бы сделали случайное делениеподсчитать (например: от 25 000 до 30 000) и рандомизировать этот диапазон.

2 голосов
/ 14 октября 2011

Вы можете сделать это с помощью

  1. сгенерировать случайное смещение, которое будет дополнительно удовлетворять выбору следующих n элементов (без превышения лимита)
  2. Предположим, что количество равно 10, а nравен 5
  3. , чтобы сделать эту проверку, заданное n меньше, чем общее число
  4. , если нет, установить смещение равным 0, и перейти к шагу 8
  5. , если да, вычестьn от общего числа, и вы получите число 5
  6. Используйте это, чтобы найти случайное число, число определенно будет от 0 до 5 (Предположим, 2)
  7. Используйте случайноечисло 2 в качестве смещения
  8. теперь вы можете взять случайных 5 пользователей, просто передав это смещение и n (5) в качестве предела.
  9. теперь вы получаете пользователей от 3 до 7

код

>> cnt = User.count
=> 10
>> n = 5
=> 5
>> offset = 0
=> 0
>> if n<cnt
>>    offset = rand(cnt-n)
>>  end
>> 2
>> User.skip(offset).limit(n)

, и вы можете поместить это в метод

def get_random_users(n)
  offset = 0
  cnt = User.count
  if n < cnt
    offset = rand(cnt-n)
  end
  User.skip(offset).limit(n)
end

и назовите это как

rand_users = get_random_users(5)

надеюсь, это поможет

0 голосов
/ 18 ноября 2015

Подход от @moox действительно интересен, но я сомневаюсь, что хорошая идея - это обезьянничать весь Монгоид. Так что мой подход - просто написать сообщение Randomizable, которое может быть включено в каждую модель, в которой вы используете эту функцию. Это идет к app/models/concerns/randomizeable.rb:

module Randomizable
  extend ActiveSupport::Concern

  module ClassMethods
    def random(n = 1)
      indexes = (0..count - 1).sort_by { rand }.slice(0, n).collect!

      return skip(indexes.first).first if n == 1
      indexes.map { |index| skip(index).first }
    end
  end
end

Тогда ваша User модель будет выглядеть так:

class User
  include Mongoid::Document
  include Randomizable

  field :name
end

и тесты ....

require 'spec_helper'

class RandomizableCollection
  include Mongoid::Document
  include Randomizable

  field :name
end

describe RandomizableCollection do
  before do
    RandomizableCollection.create name: 'Hans Bratwurst'
    RandomizableCollection.create name: 'Werner Salami'
    RandomizableCollection.create name: 'Susi Wienerli'
  end

  it 'returns a random document' do
    srand(2)

    expect(RandomizableCollection.random(1).name).to eq 'Werner Salami'
  end

  it 'returns an array of random documents' do
    srand(1)

    expect(RandomizableCollection.random(2).map &:name).to eq ['Susi Wienerli', 'Hans Bratwurst']
  end
end
0 голосов
/ 13 сентября 2013

Я думаю, что лучше сосредоточиться на рандомизации возвращенного набора результатов, поэтому я попытался:

Model.all.to_a.shuffle

Надеюсь, это поможет.

0 голосов
/ 11 февраля 2013

Только что столкнулся с такой проблемой. Пробовал

Model.all.sample

и у меня это работает

0 голосов
/ 09 января 2013

Поскольку я хочу сохранить критерии, я делаю:

scope :random, ->{
  random_field_for_ordering = fields.keys.sample
  random_direction_to_order = %w(asc desc).sample
  order_by([[random_field_for_ordering, random_direction_to_order]])
}
...