Получать случайный образец из списка при сохранении порядка элементов?

У меня есть отсортированный список, скажем: (его не просто цифры, его список объектов, отсортированных по сложному алгоритму, требующему много времени)

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]