Для этого есть очень хороший и эффективный алгоритм, использующий метод, называемый отбор проб из коллектора .
Позвольте мне начать с истории :
Кнут вызывает этот алгоритм R на с. 144 его издания 1997 года Seminumeric Algorithms (том 2 «Искусство компьютерного программирования»), и предоставляет некоторый код для него там. Кнут приписывает алгоритм Алану Уотерману. Несмотря на долгий поиск, мне не удалось найти оригинальный документ Уотермана, если он существует, возможно, поэтому вы чаще всего будете видеть цитату Кнута как источника этого алгоритма.
McLeod and Bellhouse, 1983 (1) обеспечивают более подробное обсуждение, чем Кнут, а также первое опубликованное доказательство (которое мне известно) о том, что алгоритм работает.
Vitter 1985 (2) рассматривает алгоритм R, а затем представляет три дополнительных алгоритма, которые обеспечивают тот же результат, но с изюминкой. Вместо того чтобы делать выбор включать или пропускать каждый входящий элемент, его алгоритм предопределяет количество входящих элементов, которые должны быть пропущены. В его тестах (которые, по общему признанию, сейчас устарели) это значительно сократило время выполнения, избегая генерации случайных чисел и сравнений для каждого входящего числа.
В псевдокод алгоритм:
Let R be the result array of size s
Let I be an input queue
> Fill the reservoir array
for j in the range [1,s]:
R[j]=I.pop()
elements_seen=s
while I is not empty:
elements_seen+=1
j=random(1,elements_seen) > This is inclusive
if j<=s:
R[j]=I.pop()
else:
I.pop()
Обратите внимание, что я специально написал код, чтобы не указывать размер ввода. Это одно из замечательных свойств этого алгоритма: вы можете запустить его без необходимости заранее знать размер ввода, и он все равно гарантирует, что каждый элемент, с которым вы столкнетесь, имеет равную вероятность попадания в R
(то есть нет предвзятости). Кроме того, R
содержит справедливую и репрезентативную выборку элементов, которые алгоритм всегда рассматривал. Это означает, что вы можете использовать это как онлайн алгоритм .
Почему это работает?
Маклеод и Беллхаус (1983) приводят доказательства, используя математику комбинаций. Это симпатично, но было бы немного трудно восстановить это здесь. Поэтому я создал альтернативное доказательство, которое легче объяснить.
Мы проводим доказательство по индукции.
Скажем, мы хотим сгенерировать набор s
элементов и что мы уже видели n>s
элементов.
Предположим, что наши текущие s
элементы уже были выбраны с вероятностью s/n
.
По определению алгоритма выбираем элемент n+1
с вероятностью s/(n+1)
.
Каждый элемент, уже входящий в наш набор результатов, имеет вероятность 1/s
замены.
Вероятность того, что элемент из набора результатов n
будет заменен в наборе результатов n+1
, равна (1/s)*s/(n+1)=1/(n+1)
. И наоборот, вероятность того, что элемент не будет заменен, равна 1-1/(n+1)=n/(n+1)
.
Таким образом, n+1
-видимый набор результатов содержит элемент, либо если он был частью n
-видимого набора результатов и не был заменен --- эта вероятность равна (s/n)*n/(n+1)=s/(n+1)
--- или если элемент был выбран --- с вероятностью s/(n+1)
.
Определение алгоритма говорит нам, что первые s
элементы автоматически включаются в качестве первых n=s
членов результирующего набора. Поэтому набор результатов n-seen
включает каждый элемент с вероятностью s/n
(= 1), что дает нам необходимый базовый случай для индукции.
Ссылки
Маклеод, А. Ян и Дэвид Р. Беллхаус. «Удобный алгоритм рисования простой случайной выборки». Журнал Королевского статистического общества. Серия C (Прикладная статистика) 32,2 (1983): 182-184. ( Ссылка )
Виттер, Джеффри С. "Случайная выборка с резервуара". Транзакции ACM по математическому программному обеспечению (TOMS) 11.1 (1985): 37-57. ( Link )