Простой код O (N + K * log (K)) способ
Возьмите случайную выборку без замены индексов, отсортируйте индексы и возьмите их из оригинала.
indices = random.sample(range(len(myList)), K)
[myList[i] for i in sorted(indices)]
Или более кратко:
[x[1] for x in sorted(random.sample(enumerate(myList),K))]
Оптимизированный O (N) -время, O (1) -пространственный способ
Вы также можете использовать математический трюк и итеративно проходить myList
слева направо, выбирая числа с динамически изменяющейся вероятностью (N-numbersPicked)/(total-numbersVisited)
. Преимущество этого подхода в том, что это O(N)
алгоритм, поскольку он не требует сортировки!
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
Подтверждение концепции и проверка верности вероятностей :
Смоделировано с использованием 1 триллиона псевдослучайных выборок в течение 5 часов:
>>> 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
})
Вероятности отклоняются от истинных вероятностей менее чем в 1.0001 раз. Повторный запуск этого теста привел к другому порядку, что означает, что он не смещен в сторону одного порядка. Выполнение теста с меньшим количеством образцов [0,1,2,3,4], k=3
и [0,1,2,3,4,5], k=4
дало аналогичные результаты.
edit: Не уверен, почему люди голосуют за неправильные комментарии или боятся голосовать за ... НЕТ, в этом методе нет ничего плохого. знак равно
(Также полезное примечание от пользователя tegan в комментариях: если это python2, вы, как обычно, захотите использовать xrange, если вам действительно нужно дополнительное пространство.)
edit : Доказательство: учитывая равномерное распределение (без замены) выбора подмножества k
из совокупности seq
размера len(seq)
, мы можем рассмотреть разделение в произвольной точке i
на «левый» (0,1, ..., i-1) и 'right' (i, i + 1, ..., len (seq)). Учитывая, что мы выбрали numbersPicked
из левого известного подмножества, оставшееся должно происходить из того же равномерного распределения в правом неизвестном подмножестве, хотя теперь параметры другие. В частности, вероятность seq[i]
наличия выбранного элемента равна #remainingToChoose/#remainingToChooseFrom
, или(k-numbersPicked)/(len(seq)-i)
, поэтому мы моделируем это и возвращаемся к результату. (Это должно прекратиться, поскольку если #remainingToChoose == #remainingToChooseFrom, то все оставшиеся вероятности равны 1.) Это похоже на дерево вероятностей, которое случайно создается динамически. По сути, вы можете смоделировать равномерное распределение вероятностей, обусловив предыдущие выборы (по мере роста дерева вероятностей вы выбираете вероятность текущей ветви так, чтобы она была апостериорной, такой же, как предыдущие листья, то есть обусловлена предыдущими выборами; это будет работать, потому что эта вероятность равномерно равна N / k).
edit : Тимоти Шилдс упоминает отбор проб коллектора , который является обобщением этого метода, когда len(seq)
он неизвестен (например, с выражением генератора). В частности, тот, который отмечен как «алгоритм R», занимает O (N) и O (1) пространство, если выполняется на месте; он включает в себя выбор первых N элементов и их медленную замену (также дается намек на индуктивное доказательство). Также на странице википедии можно найти полезные распределенные варианты и различные варианты отбора проб из коллектора.
edit : Вот еще один способ закодировать его более семантически очевидным образом.
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
а потом сортировать?