Получать случайный образец из списка при сохранении порядка элементов?
У меня есть отсортированный список, скажем: (его не просто цифры, его список объектов, отсортированных по сложному алгоритму, требующему много времени)
mylist = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ,9 , 10 ]
Есть ли какая-нибудь функция python, которая даст мне N элементов, но сохранит порядок?
Пример:
randomList = getRandom(mylist,4)
# randomList = [ 3 , 6 ,7 , 9 ]
randomList = getRandom(mylist,4)
# randomList = [ 1 , 2 , 4 , 8 ]
и т.д...
Ответы
Ответ 1
Следующий код сгенерирует случайную выборку размером 4:
import random
sample_size = 4
sorted_sample = [
mylist[i] for i in sorted(random.sample(range(len(mylist)), sample_size))
]
(примечание: в Python 2 лучше использовать xrange
вместо range
)
объяснение
random.sample(range(len(mylist)), sample_size)
генерирует случайную выборку из индексов исходного списка.
Затем эти индексы сортируются, чтобы сохранить порядок элементов в исходном списке.
Наконец, понимание списка вытягивает фактические элементы из исходного списка, учитывая выбранные индексы.
Ответ 2
Простой код 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) -time, 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
на " left '(0,1,..., i-1) и' right '(i, я + 1,..., len (seq)). Учитывая, что мы выбрали numbersPicked
из левого известного подмножества, остальные должны исходить из одного и того же равномерного распределения на неизвестном справа подмножестве, хотя параметры теперь разные. В частности, вероятность того, что seq[i]
содержит выбранный элемент, равна #remainingToChoose/#remainingToChooseFrom
, или (k-numbersPicked)/(len(seq)-i)
, поэтому мы имитируем это и возвращаем результат. (Это должно завершиться, так как если #remainingToChoose == #remainingToChooseFrom, то все остальные вероятности равны 1.) Это похоже на дерево вероятностей, которое, как оказалось, динамически генерируется. В принципе, вы можете моделировать равномерное распределение вероятности, обусловливая предварительные выборы (по мере роста дерева вероятности вы выбираете вероятность текущей ветки, чтобы она была апостериорной по сравнению с предыдущими листами, то есть была обусловлена предыдущими выборами, это будет работать, потому что эта вероятность равномерно равна N/k).
edit: Timothy Shields упоминает Reservoir Sampling, который является обобщением этого метода, когда len(seq)
неизвестен (например, с выражением генератора). В частности, тот, который обозначен как "алгоритм R", представляет собой O (N) и O (1) пространство, если это делается на месте; он включает в себя прием первого элемента N и его медленную замену (также дается подсказка об индуктивном доказательстве). Существуют также полезные распределенные варианты и различные варианты выборки коллектора, которые можно найти на странице wikipedia.
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)
)
Ответ 3
Возможно, вы можете просто сгенерировать образец индексов, а затем собрать элементы из своего списка.
randIndex = random.sample(range(len(mylist)), sample_size)
randIndex.sort()
rand = [mylist[i] for i in randIndex]
Ответ 4
По-видимому random.sample
был введен в python 2.3
поэтому для версии под этим мы можем использовать shuffle (пример для 4 элементов):
myRange = range(0,len(mylist))
shuffle(myRange)
coupons = [ bestCoupons[i] for i in sorted(myRange[:4]) ]
Ответ 5
random.sample реализовать его.
>>> random.sample([1, 2, 3, 4, 5], 3) # Three samples without replacement
[4, 1, 5]