2011-10-13 38 views
9

Cho phép nói rằng tôi có một Bộ sưu tập người dùng. Có cách nào để sử dụng mongoid để tìm người dùng ngẫu nhiên trong bộ sưu tập mà nó không trả lại cùng một người dùng hai lần không? Bây giờ, hãy cho phép bộ sưu tập của người dùng trông giống như sau:Tài liệu ngẫu nhiên Mongoid

class User 
    include Mongoid::Document 
    field :name 
end 

Đơn giản huh?

Cảm ơn

+1

Điều này đang được xem xét bởi nhóm MongoDB. Họ ưu tiên các vấn đề dựa trên nhu cầu; vì vậy nếu bạn muốn tính năng này, hãy kiểm tra [Ticket # 533: Nhận ngẫu nhiên (các) mục từ Bộ sưu tập] (https://jira.mongodb.org/browse/SERVER-533), đọc và bỏ phiếu cho phù hợp. –

+0

Vé đã bị đóng và hiện tại có một toán tử '$ sample' cho MongoDB. Dường như không được tích hợp với Mongoid, truy vấn phải được thực hiện thủ công. Bạn cũng có thể muốn có một cái nhìn tại 'snapshot' để thực sự tránh trùng lặp từ concurrency. –

Trả lời

13

Giải pháp tốt nhất là sẽ phụ thuộc vào kích thước dự kiến ​​của bộ sưu tập.

Đối với các bộ sưu tập nhỏ, chỉ cần lấy tất cả chúng và .shuffle.slice!

Đối với kích thước nhỏ của n, bạn có thể nhận được ngay với một cái gì đó như thế này:

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

Đối với kích thước lớn của n, tôi sẽ khuyên bạn nên tạo một cột "ngẫu nhiên" để sắp xếp theo. Xem ở đây để biết chi tiết: http://cookbook.mongodb.org/patterns/random-attribute/ https://github.com/mongodb/cookbook/blob/master/content/patterns/random-attribute.txt

+1

Cảm ơn ... điều này có thể là quá mức cần thiết nhưng đã tự hỏi nếu có một cách đơn giản để chuyển đổi trở lại thành một Mongoid :: Tiêu chí – GTDev

+0

SQL có ORDER BY RAND() nhưng theo như tôi biết không có tương đương với điều đó trong mongodb . Vì vậy, bạn có thể tạo cột "Ngẫu nhiên" và sau đó là User.order_by, đó sẽ là một truy vấn đơn lẻ. –

+1

Theo [SO: MongoDB tìm hiệu suất tập dữ liệu ngẫu nhiên] (http://stackoverflow.com/questions/9434969/mongodb-find-random-dataset-performance), 'skip' không hiệu quả lắm đối với các giá trị lớn:" Bỏ qua buộc Mongo phải đi qua bộ kết quả cho đến khi nó đến tài liệu bạn đang tìm kiếm, do đó tập hợp kết quả của truy vấn đó càng lớn thì thời gian càng dài. " (Điều này hỗ trợ câu trả lời của Dan.) –

2

Bạn có thể làm điều này bằng

  1. tạo ngẫu nhiên bù đắp mà sẽ tiếp tục đáp ứng để chọn n tiếp theo yếu tố (không vượt quá giới hạn)
  2. Giả đếm là 10, và n là 5
  3. để thực hiện việc này, hãy kiểm tra n cho trước nhỏ hơn tổng số
  4. nếu không đặt chênh lệch thành 0 và chuyển đến bước 8
  5. nếu có, trừ đi n từ tổng số, và bạn sẽ nhận được một số 5
  6. Sử dụng này để tìm một số ngẫu nhiên, số lượng chắc chắn sẽ là 0-5 (Giả sử 2)
  7. Sử dụng ngẫu nhiên số 2 là bù đắp
  8. bây giờ bạn có thể lấy ngẫu nhiên 5 người dùng bằng cách đơn giản chuyển giá trị chênh lệch này và n (5) làm giới hạn.
  9. bây giờ bạn có được người dùng từ 3 để 7

đang

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

và bạn có thể đặt điều này trong một phương pháp

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

và gọi nó là như

rand_users = get_random_users(5) 

hy vọng điều này anh LPS

+0

cảm ơn. Nhưng điều này thực sự sẽ là ngẫu nhiên. Tôi đoán điều này sẽ cung cấp một phạm vi ngẫu nhiên từ cnt để cnt + n nhưng wont này tạo ra một điều kiện. chẳng hạn như nếu người dùng 5 được chọn ... sẽ có cơ hội cao mà người dùng 6 sẽ ở trong khi cơ hội bằng không mà người dùng 11 sẽ là? – GTDev

+0

Phải, đây là một sự đánh đổi khỏi câu trả lời của tôi. Nếu bạn có thể bắt đầu với một vị trí ngẫu nhiên và chỉ cần chọn các bản ghi tuần tự n tiếp theo, thì bạn có thể thực hiện nó trong một truy vấn thay vì truy vấn n. Sau đó bạn có thể xáo trộn kết quả để có được ngẫu nhiên trong lựa chọn đó. Nhưng không, điều này không thực sự ngẫu nhiên. –

3

Nếu bạn thực sự muốn đơn giản bạn có thể sử dụng thay vì:

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 

Bạn chỉ cần gọi User.random(5) và bạn sẽ nhận được 5 người ngẫu nhiên. Nó cũng sẽ hoạt động với tính năng lọc, vì vậy nếu bạn chỉ muốn những người dùng đã đăng ký, bạn có thể thực hiện User.where(:registered => true).random(5).Điều này sẽ mất một thời gian cho các bộ sưu tập lớn vì vậy tôi khuyên bạn nên sử dụng một phương pháp thay thế, trong đó bạn sẽ phân chia ngẫu nhiên số lượng (ví dụ: 25 000 đến 30 000) và ngẫu nhiên phạm vi đó.

+0

Trên địa điểm nào và với tên nào lưu tệp này ?. Làm thế nào gọi cho bạn tập tin này? Cảm ơn bạn! – hyperrjas

+0

@hyperrjas Bạn có thể đặt tệp này vào thư mục lib của ứng dụng của bạn. Sau đó, hãy đảm bảo rằng ứng dụng của bạn được định cấu hình để tự động tải các tệp trong thư mục đó. Tên của tệp không quan trọng. – Moox

+0

Cảm ơn bạn. Tôi đã thêm vào trong thư mục '/ app/lib' tệp' random.rb' với mã này, nhưng ví dụ, nếu tôi chạy trong giao diện điều khiển 'User.random (5)', tôi nhận được lỗi 'NoMethodError: undefined method' random 'cho Người dùng: Lớp'. Làm thế nào tôi có thể sửa lỗi này? – hyperrjas

0

Vì tôi muốn giữ một tiêu chí, tôi làm:

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

Chỉ cần gặp phải một vấn đề như vậy. Cố gắng

Model.all.sample 

và nó làm việc cho tôi

+9

Khá chắc chắn điều này sẽ tải mọi mô hình đơn lẻ từ cơ sở dữ liệu và sau đó sử dụng phương thức 'Array # sample' để chọn một mục ngẫu nhiên. Tôi đoán OK nếu bạn chỉ cần poking xung quanh trong giao diện điều khiển, nhưng không được khuyến khích cho các ứng dụng sản xuất. – steve

+0

Mất nhiều thời gian nếu số lượng các mục có nhiều hơn trong mô hình – Sairam

+0

Nó hoạt động nhưng với hơn 20'000 tài liệu như trong trường hợp của tôi phải mất nhiều thời gian, như đã đề cập. –

-2

tôi nghĩ rằng nó là tốt hơn để tập trung vào ngẫu nhiên kết quả được trả về vì vậy tôi cố gắng:

Model.all.to_a.shuffle 

Hope this helps.

+3

Điều đó sẽ rất kinh khủng nếu bạn có một triệu trường hợp 'Mô hình' trong cơ sở dữ liệu của bạn. –

+0

Nó chỉ là một ví dụ. Bạn chắc chắn sẽ nhận được bộ kết quả mà bạn đang tìm kiếm đầu tiên. –

15

Nếu bạn chỉ muốn một tài liệu, và không muốn để xác định một phương pháp tiêu chuẩn mới, bạn chỉ có thể làm điều này:

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

Nếu bạn muốn tìm một mô hình ngẫu nhiên dựa trên một số tiêu chí:

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

Cách tiếp cận từ @moox thực sự thú vị nhưng tôi nghi ngờ rằng việc bắt cóc toàn bộ Mongoid là một ý tưởng hay ở đây. Vì vậy, cách tiếp cận của tôi chỉ là viết một mối quan tâm Randomizable có thể bao gồm trong mỗi mô hình bạn sử dụng tính năng này. Điều này đi đến 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 

Sau đó, mô hình User của bạn sẽ trông như thế này:

class User 
    include Mongoid::Document 
    include Randomizable 

    field :name 
end 

Và các bài kiểm tra ....

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 đến để giải thoát với $sample (link to doc)

EDIT: Gần đây nhất của Mongoid đã thực hiện $ mẫu, vì vậy bạn có thể gọi YourCollection.all.sample(5)

Các phiên bản trước của mongoid

Mongoid không hỗ trợ sample cho đến khi Mongoid 6, vì vậy bạn phải chạy này truy vấn tổng hợp với người lái xe Mongo:

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

gì bạn có thể làm với điều đó

Tôi tin rằng chức năng sẽ sớm được tiến hành thành Mongoid, nhưng trong thời gian chờ đợi

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) 
Các vấn đề liên quan