2009-06-08 40 views
11

Giả sử tôi có một danh sách, được gọi là elements, mỗi phần tử có hoặc không thỏa mãn một số thuộc tính boolean p. Tôi muốn chọn một trong các yếu tố thỏa mãn p một cách ngẫu nhiên với phân bố đồng đều. Tôi không biết trước bao nhiêu mặt hàng thỏa mãn tài sản này p.Chọn phần tử mảng ngẫu nhiên thỏa mãn thuộc tính nhất định

đoạn mã sau sẽ làm được điều này ?:

pickRandElement(elements, p) 
    randElement = null 
    count = 0 
    foreach element in elements 
      if (p(element)) 
       count = count + 1 
       if (randInt(count) == 0) 
        randElement = element 

    return randElement 

(randInt(n) lợi nhuận một ngẫu nhiên int k với 0 <= k < n.)

+0

Tôi đã có thể nghĩ "bởi ngẫu nhiên" và "với phân phối bình đẳng" là loại trừ lẫn nhau, tôi thiếu gì? –

+0

@Binary: anh ta đơn giản có nghĩa là nó phải là một số ngẫu nhiên hợp lý. Tất cả các yếu tố thỏa mãn p phải có cơ hội bình đẳng được chọn ngẫu nhiên mỗi lần. Nếu điều này là đúng, thì chúng sẽ được vẽ với sự phân bố bằng nhau đủ thời gian. – JoeCool

+1

Các bản phân phối ngẫu nhiên có thể có tất cả các loại hình dạng có thể được đặt trọng số theo một nhóm các phần tử khác. Tại đây, Phao-lô hỏi về một sự phân bố đồng đều (hoặc thống nhất) trong đó mỗi phần tử có cùng xác suất được chọn. –

Trả lời

13

Nó hoạt động bằng toán học. Có thể được chứng minh bằng cảm ứng.

Làm việc rõ ràng cho yếu tố n = 1 thỏa mãn p.

Đối với các yếu tố n + 1, chúng tôi sẽ chọn phần tử n + 1 với xác suất 1/(n + 1), vì vậy xác suất của nó là OK.Nhưng làm thế nào mà có hiệu lực xác suất cuối cùng của việc lựa chọn một trong những yếu tố n trước?

Mỗi n trước đó có cơ hội được chọn với xác suất 1/n, cho đến khi chúng tôi tìm thấy phần tử n + 1. Bây giờ, sau khi tìm n + 1, có 1/(n + 1) cơ hội mà phần tử n + 1 được chọn, do đó, có một n/(n + 1) cơ hội mà phần tử đã chọn trước đó vẫn được chọn. Có nghĩa là xác suất cuối cùng của nó là được chọn sau khi tìm thấy n + 1 là 1/n * (n/n + 1) = 1/n + 1 - đó là xác suất chúng ta muốn cho tất cả các phần tử n + 1 để phân bố đều.

Nếu nó hoạt động cho n = 1, và nó hoạt động cho n + 1 cho n, sau đó nó hoạt động cho tất cả n.

+0

Cảm ứng đã lưu mông của tôi quá nhiều lần trong các bằng chứng tính toán! – JoeCool

+0

Có một cách đơn giản để chứng minh điều này. Đối với các phần tử n, chúng ta sẽ chọn phần tử n với xác suất 1/n. Nhưng những yếu tố n-1 trước đây thì sao? Vâng, theo cảm ứng, chúng ta biết rằng tất cả các phần tử n-1 này đều có cùng xác suất. Vì vậy, xác suất cho mỗi _must_ là 1/n, vì 1/n là số duy nhất là 1 khi nhân với n. qed :) – FeepingCreature

6

Vâng, tôi tin như vậy.

Lần đầu tiên bạn gặp phải một yếu tố phù hợp, bạn chắc chắn chọn nó. Lần sau, bạn chọn giá trị mới với xác suất là 1/2, do đó, mỗi một trong hai phần tử đều có cơ hội như nhau. Lần sau, bạn chọn giá trị mới với xác suất 1/3, để mỗi phần tử khác có xác suất bằng 1/2 * 2/3 = 1/3.

Tôi đang cố gắng để tìm một bài viết trên Wikipedia về chiến lược này, nhưng thất bại cho đến nay ...

Lưu ý rằng tổng quát hơn, bạn chỉ cần chọn một mẫu ngẫu nhiên ra khỏi một chuỗi có độ dài chưa biết. Trình tự của bạn xảy ra được tạo ra bằng cách lấy một chuỗi ban đầu và lọc nó, nhưng thuật toán không yêu cầu phần đó chút nào.

tôi nghĩ rằng tôi đã có một nhà điều hành LINQ trong MoreLINQ để làm điều này, nhưng tôi không thể tìm thấy nó trong kho ... EDIT: May mắn thay, nó vẫn còn tồn tại từ this answer:

public static T RandomElement<T>(this IEnumerable<T> source, 
           Random rng) 
{ 
    T current = default(T); 
    int count = 0; 
    foreach (T element in source) 
    { 
     count++; 
     if (rng.Next(count) == 0) 
     { 
      current = element; 
     }    
    } 
    if (count == 0) 
    { 
     throw new InvalidOperationException("Sequence was empty"); 
    } 
    return current; 
} 
+1

Jon, theo như tôi thấy, thuật toán này sẽ luôn luôn chọn yếu tố đầu tiên đáp ứng p, những gì tôi đang thiếu? – tekBlues

+0

@tekBlues: nó tiếp tục sau khi nó được chọn đầu tiên. – AakashM

+0

Tôi khá chắc chắn thuật toán này hoạt động, nếu trình tạo ngẫu nhiên thực hiện công việc của nó đúng cách. –

0

Đối vì lợi ích rõ ràng, tôi sẽ cố gắng:

pickRandElement(elements, p) 
    OrderedCollection coll = new OrderedCollection 
    foreach element in elements 
      if (p(element)) 
       coll.add(element) 
    if (coll.size == 0) return null 
    else return coll.get(randInt(coll.size)) 

Đối với tôi, mà làm cho nó rõ ràng hơn NHIÊU những gì bạn đang cố gắng làm và là tự tài liệu. Ngày đầu đó, nó đơn giản và thanh lịch hơn, và bây giờ rõ ràng rằng mỗi người sẽ được chọn với một bản phân phối đồng đều.

+0

Đó là mã chúng tôi hiện có (và tôi thừa nhận nó rõ ràng hơn). Tôi đang hy vọng điều gì đó hiệu quả hơn. Tạo một danh sách và thêm các yếu tố vào nó, tôi đoán, có phần không hiệu quả và lãng phí, nếu giải pháp thay thế được đề xuất sẽ hoạt động. –

+0

Khi bạn thấy thuật toán được đề xuất là gì, IMO cực kỳ thanh lịch. –

+0

Có, bạn nói đúng, nếu hiệu quả là ưu tiên hàng đầu. Tôi sẽ nói rằng các giải pháp rõ ràng hơn mà tôi cung cấp là dễ đọc hơn mặc dù. – JoeCool

3

Trong Thực hành lập trình, trg. 70, (The Markov Chain Algorithm) có một thuật toán tương tự cho rằng:.

[...] 
    nmatch = 0; 
    for (/* iterate list */) 
    if (rand() % ++nmatch == 0) /* prob = 1/nmatch */ 
     w = suf->word; 
[...] 

"Chú ý các thuật toán để lựa chọn một mục một cách ngẫu nhiên khi chúng ta không biết làm thế nào nhiều mặt hàng có Các biến nkhớp đếm số lượng các trận đấu như danh sách được scan. Khái niệm

rand() % ++nmatch == 0 

increments nkhớp và sau đó đúng với xác suất 1/nkhớp."

1

decowboy có một bằng chứng tốt đẹp mà này hoạt động trên TopCoder

Các vấn đề liên quan