Простой код 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а потом сортировать?