2011-10-13 125 views
9

可以說我有一個用戶集合。有沒有使用mongoid在集合中找到n個隨機用戶的方式,它不會將同一用戶返回兩次?現在可以說用戶集合看起來像這樣:Mongoid隨機文檔

class User 
    include Mongoid::Document 
    field :name 
end 

簡單吧?

謝謝

+1

這是由MongoDB的團隊考慮。他們根據需求優先考慮問題;所以如果你想要這個功能,請查看[Ticket#533:從Collection中獲取隨機項目](https://jira.mongodb.org/browse/SERVER-533),閱讀並相應投票。 –

+0

票已關閉,現在有一個MongoDB的'sample'運算符。似乎沒有整合到Mongoid,但查詢必須手動完成。你可能也想看看'snapshot'來真正避免併發的重複。 –

回答

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

+1

謝謝......這可能是矯枉過正,但想知道是否有一種簡單的方法將其轉換回Mongoid :: Criteria – GTDev

+0

SQL有ORDER BY RAND(),但據我所知在mongodb中沒有相應的東西。所以你可以創建「隨機」列,然後創建User.order_by,這將是一個單一的查詢。 –

+1

根據[SO:MongoDB查找隨機數據集的性能](http://stackoverflow.com/questions/9434969/mongodb-find-random-dataset-performance),對於大值,「skip」效率不是很高:「Skip強制Mongo遍歷結果集,直到找到您要查找的文檔,因此查詢的結果集越大,所需時間就越長。「 (這支持丹的回答。) –

2

您可以通過

  1. 爲此產生隨機偏移,這將進一步滿足挑下n 元素(不超過限制)
  2. 假設計數10,並且n是5
  3. 要做這個檢查給定n小於總計數
  4. 如果沒有將偏移量設置爲0,並且轉到步驟8
  5. 如果是,減去或總數的N,你會得到一個號碼5
  6. 使用此找到一個隨機數,這個數字肯定會從0到5(假設2)
  7. 使用隨機號碼2作爲抵消
  8. 現在你可以通過簡單地通過這個偏移量和n(5)作爲一個限制來接受隨機的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) 

希望這個他LPS

+0

謝謝。但這真的是隨機的。我想這將提供從cnt到cnt + n的隨機範圍,但不會創建一個條件。例如如果用戶5被選擇......那麼用戶6將很有可能在用戶11將是零的機率的同時? – GTDev

+0

對,這是從我的答案中折中。如果你可以從隨機點開始,只需選擇下一個n個連續記錄,那麼你可以在一個查詢中執行它,而不是在n個查詢中執行。然後,您可以將結果隨機混合,使其在該選擇內隨機化。但是,不,這不是隨機的。 –

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)並隨機化該範圍。

+0

保存此文件的地方和名稱?怎麼叫你這個文件?謝謝! – hyperrjas

+0

@hyperrjas你可以把這個文件放在應用程序的lib文件夾中。然後確保您的應用程序已配置爲自動加載該文件夾內的文件。文件的名稱並不重要。 – Moox

+0

謝謝。我在'/ app/lib'文件夾中添加了這個代碼的'random.rb'文件,但是例如,如果我在控制檯User.random(5)中運行''我得到錯誤'NoMethodError:undefined method'random '用戶:類'。我怎樣才能解決這個問題? – hyperrjas

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

就遇到了這樣的問題。試圖

Model.all.sample 

和它的作品對我來說

+9

很確定這會加載數據庫中的每一個模型,然後使用'Array#sample'方法來選擇一個隨機項目。我猜如果你只是在控制檯中探索,但不推薦用於生產應用程序。 – steve

+0

如果在模型中的物品數量更多,需要很多時間 – Sairam

+0

它可以工作,但是會有更多的20'000個文檔,正如我所說的那樣,需要花費很長時間,如前所述。 –

-2

我覺得這是更好地專注於隨機返回的結果集,所以我嘗試:

Model.all.to_a.shuffle 

希望這有助於。如果您想根據一些標準來尋找隨機模型

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

+3

如果數據庫中有一百萬個'Model'實例,那麼這將會非常可怕。 –

+0

這只是一個例子。您應該首先獲得您正在查找的結果集。 –

15

如果你只是想一個文檔,並且不希望定義一個新的標準方法,你可能只是這樣做

criteria = Model.scoped_whatever.where(conditions) # query example 
random_model = criteria.skip(rand(criteria.count)).first 
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 
2

MongoDB的3.2談到與$sample救援(link to doc

編輯:最近Mongoid已實現$樣本,這樣你就可以調用mongoid

Mongoid的YourCollection.all.sample(5)

以前的版本不支持sample直到Mongoid 6,所以你要運行這個與蒙戈司機彙總查詢:

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

您可以用

01做些什麼

我相信functionnality應該很快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)