Получить случайную выборку из списка, сохранив порядок товаров?

84

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

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
Почему не хотите, random.sampleа потом сортировать?
Daniel Lubarov
Он сортируется с помощью нетривиального алгоритма ... на самом деле это не просто числа,
Йохай Тиммер
4
Очень небольшое изменение в комментарии Дэниела: выборка из диапазона [0,count), сортировка выборки (числа в диапазоне имеют естественный порядок), затем извлечение значений mylistна основе индексов. Использование zipможет достичь того же эффекта с немного другой механикой.
1
хорошо, могу я получить ответ + пример, чтобы мне было что принять? :)
Yochai Timmer

Ответы:

121

Следующий код сгенерирует случайную выборку размером 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)

генерирует случайную выборку индексов исходного списка.

Затем эти индексы сортируются, чтобы сохранить порядок элементов в исходном списке.

Наконец, понимание списка извлекает фактические элементы из исходного списка с учетом выбранных индексов.

Мифриц
источник
89

Простой код 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)

)

ниндзягеко
источник
1
@pst: никаких недостатков, O(N)скорее , просто ускорениеO(N log(N))
ninjagecko
1
Очень хорошо, мне тоже было интересно, как сделать этот линейный подход. Есть ли у этой формулы страница в Википедии? :)
Йохен Ритцель
2
Я удивлен, что в этом ответе нет большего количества голосов, он фактически объясняет, как работает решение (и предоставляет другое решение!), В отличие от первого ответа, который представляет собой просто однострочный фрагмент - не давая мне понять, почему или как это работало.
crazy2be
1
Хорошее решение ninjagecko. У вашего решения есть хорошее индуктивное доказательство, если кому-то интересно его написать.
Neil G
3
Хорошее решение! Не забудьте добавить from __future__ import divisionдля тех, кто использует Python 2.
xApple, 04
7

Может быть, вы можете просто создать образец индексов, а затем собрать элементы из своего списка.

randIndex = random.sample(range(len(mylist)), sample_size)
randIndex.sort()
rand = [mylist[i] for i in randIndex]
Говард
источник
4

По-видимому, random.sampleбыл введен в Python 2.3

поэтому для версии ниже мы можем использовать перемешивание (например, для 4 элементов):

myRange =  range(0,len(mylist)) 
shuffle(myRange)
coupons = [ bestCoupons[i] for i in sorted(myRange[:4]) ]
Йохай Тиммер
источник
4
Вы используете Python 2.2 ?! Вам следует обновить ... это уже устарело.
Katriel
1
ну, это то, что у нас есть на серверах ... создание общесистемного обновления - это слишком бюрократия
Йохай Тиммер
-2

random.sample реализовать это.

>>> random.sample([1, 2, 3, 4, 5],  3)   # Three samples without replacement
[4, 1, 5]
сяо
источник
9
Это не заказано.
Астрид