Cách đơn giản để mã O (N + K * log (K))
Lấy một mẫu ngẫu nhiên mà không cần thay thế các chỉ số, sắp xếp các chỉ số và lấy chúng từ bản gốc.
indices = random.sample(range(len(myList)), K)
[myList[i] for i in sorted(indices)]
Hay ngắn gọn hơn:
[x[1] for x in sorted(random.sample(enumerate(myList),K))]
Tối ưu hóa O (N) - thời gian, O (1) - không gian phụ
Ngoài ra, bạn có thể sử dụng một mẹo toán học và lặp đi lặp lại myList
từ trái sang phải, chọn các số với xác suất thay đổi động (N-numbersPicked)/(total-numbersVisited)
. Ưu điểm của cách tiếp cận này là nó là một O(N)
thuật toán vì nó không liên quan đến việc sắp xếp!
from __future__ import division
def orderedSampleWithoutReplacement(seq, k):
if not 0<=k<=len(seq):
raise ValueError('Required that 0 <= sample_size <= population_size')
numbersPicked = 0
for i,number in enumerate(seq):
prob = (k-numbersPicked)/(len(seq)-i)
if random.random() < prob:
yield number
numbersPicked += 1
Chứng minh khái niệm và kiểm tra rằng các xác suất là đúng :
Được mô phỏng với 1 nghìn tỷ mẫu giả ngẫu nhiên trong suốt 5 giờ:
>>> Counter(
tuple(orderedSampleWithoutReplacement([0,1,2,3], 2))
for _ in range(10**9)
)
Counter({
(0, 3): 166680161,
(1, 2): 166672608,
(0, 2): 166669915,
(2, 3): 166667390,
(1, 3): 166660630,
(0, 1): 166649296
})
Xác suất phân biệt với xác suất thực nhỏ hơn 1.0001. Chạy thử nghiệm này một lần nữa dẫn đến một thứ tự khác có nghĩa là nó không thiên về một thứ tự. Chạy thử nghiệm với ít mẫu hơn [0,1,2,3,4], k=3
và [0,1,2,3,4,5], k=4
có kết quả tương tự.
chỉnh sửa: Không chắc tại sao mọi người lại bình chọn sai hoặc sợ ủng hộ ... KHÔNG, không có gì sai với phương pháp này. =)
(Cũng có một lưu ý hữu ích từ người dùng tegan trong phần nhận xét: Nếu đây là python2, bạn sẽ muốn sử dụng xrange, như thường lệ, nếu bạn thực sự quan tâm đến dung lượng bổ sung.)
chỉnh sửa : Chứng minh: Xét phân bố đồng đều (không có thay thế) của chọn một tập hợp con của k
trên một dân seq
quy mô len(seq)
, chúng ta có thể xem xét một phân vùng tại một điểm tùy ý i
vào 'trái' (0,1, ..., i-1) và 'right' (i, i + 1, ..., len (seq)). Giả sử rằng chúng tôi đã chọn numbersPicked
từ tập hợp con đã biết bên trái, phần còn lại phải đến từ cùng một phân phối đồng đều trên tập hợp con chưa biết bên phải, mặc dù các tham số bây giờ khác nhau. Cụ thể, xác suất seq[i]
có chứa một phần tử đã chọn là #remainingToChoose/#remainingToChooseFrom
, hoặc(k-numbersPicked)/(len(seq)-i)
, vì vậy chúng tôi mô phỏng điều đó và lặp lại kết quả. (Điều này phải chấm dứt vì nếu #remainingToChoose == #remainingToChooseFrom, thì tất cả các xác suất còn lại là 1.) Điều này tương tự như một cây xác suất xảy ra được tạo động. Về cơ bản, bạn có thể mô phỏng phân bố xác suất đồng nhất bằng cách điều chỉnh các lựa chọn trước (khi bạn trồng cây xác suất, bạn chọn xác suất của nhánh hiện tại sao cho nó là aposteriori giống với các lá trước đó, tức là được điều chỉnh trên các lựa chọn trước; điều này sẽ hoạt động vì xác suất này đồng nhất chính xác N / k).
sửa : Timothy Shields đề cập đến Lấy mẫu hồ chứa , đó là khái quát của phương pháp này khi len(seq)
chưa biết (chẳng hạn như với biểu thức máy phát). Cụ thể, thuật toán được lưu ý là "thuật toán R" là không gian O (N) và O (1) nếu được thực hiện tại chỗ; nó liên quan đến việc lấy N phần tử đầu tiên và từ từ thay thế chúng (một gợi ý về một bằng chứng quy nạp cũng được đưa ra). Ngoài ra còn có các biến thể phân tán hữu ích và các biến thể khác của lấy mẫu hồ chứa được tìm thấy trên trang wikipedia.
chỉnh sửa : Đây là một cách khác để viết mã bên dưới theo cách rõ ràng hơn về mặt ngữ nghĩa.
from __future__ import division
import random
def orderedSampleWithoutReplacement(seq, sampleSize):
totalElems = len(seq)
if not 0<=sampleSize<=totalElems:
raise ValueError('Required that 0 <= sample_size <= population_size')
picksRemaining = sampleSize
for elemsSeen,element in enumerate(seq):
elemsRemaining = totalElems - elemsSeen
prob = picksRemaining/elemsRemaining
if random.random() < prob:
yield element
picksRemaining -= 1
from collections import Counter
Counter(
tuple(orderedSampleWithoutReplacement([0,1,2,3], 2))
for _ in range(10**5)
)
random.sample
và sau đó sắp xếp?