Mongoid随机文件

时间:2011-10-13 19:13:35

标签: ruby-on-rails ruby mongodb mongoid

让我们说我有一个用户集合。有没有办法使用mongoid在集合中找到n个随机用户,它不会返回同一个用户两次?现在让我们假设用户集合如下所示:

class User
  include Mongoid::Document
  field :name
end

简单吧?

由于

9 个答案:

答案 0 :(得分:16)

如果您只想要一个文档,并且不想定义新的条件方法,那么您可以这样做:

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

如果您想根据某些标准找到随机模型:

criteria = Model.scoped_whatever.where(conditions) # query example
random_model = criteria.skip(rand(criteria.count)).first

答案 1 :(得分:13)

最佳解决方案将取决于预期的集合大小。

对于小型集合,只需获取所有这些和.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

答案 2 :(得分:5)

MongoDB 3.2通过$samplelink to doc

来解决问题

编辑:最近的Mongoid实施了$ sample,因此您可以拨打YourCollection.all.sample(5)

以前版本的mongoid

Mongoid在Mongoid 6之前不支持sample,因此您必须使用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 :(得分:3)

如果你真的想要简单,你可以改用它:

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)并随机化该范围。

答案 4 :(得分:2)

您可以通过

执行此操作
  1. 生成随机偏移,进一步满足挑选下一个n 元素(不超过限制)
  2. 假设计数为10,n为5
  3. 执行此操作检查给定的n小于总计数
  4. 如果没有将偏移设置为0,则转到步骤8
  5. 如果是,则从总计数中减去n,您将得到一个数字
  6. 使用它来查找随机数,数字肯定是从0到5(假设2)
  7. 使用随机数2作为偏移量
  8. 现在您可以通过简单地传递此偏移量和n(5)作为限制来获取随机的5个用户。
  9. 现在您的用户数从3到7
  10. >> 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)
    

    希望这会有所帮助

答案 5 :(得分:0)

由于我想保留一个标准,我这样做:

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

答案 6 :(得分:0)

刚遇到这样的问题。试图

Model.all.sample

它对我有用

答案 7 :(得分:0)

来自@moox的方法非常有趣,但我怀疑monkeypatching整个Mongoid在这里是一个好主意。所以我的方法就是编写一个问题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

答案 8 :(得分:-2)

我认为最好关注随机化返回的结果集,所以我尝试了:

Model.all.to_a.shuffle

希望这有帮助。