Отримати випадкову вибірку зі списку, зберігаючи порядок товарів?


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а потім сортуєте?
Даніель Любаров

Це сортується за допомогою нетривіального алгоритму ... насправді це не просто цифри
Йочай Таймер

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 * журнал (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
})

Імовірності відрізняються від справжніх ймовірностей меншим коефіцієнтом 10001. Повторний запуск цього тесту призвів до іншого порядку, що означає, що він не упереджений до одного замовлення. Запуск тесту з меншою кількістю зразків для [0,1,2,3,4], k=3та [0,1,2,3,4,5], k=4мав подібні результати.

редагувати: Не впевнені, чому люди голосують за неправильні коментарі або бояться голосувати за них ... НІ, в цьому методі немає нічого поганого. =)

(Також корисна примітка від користувача tegan у коментарях: якщо це python2, ви захочете використовувати xrange, як зазвичай, якщо вам дійсно потрібен зайвий простір.)

редагувати : Доказ: Беручи до уваги рівномірний розподіл (без заміни) вибору підмножини з kсукупності seqрозміру len(seq), ми можемо розглянути розділ у довільній точці iна 'ліворуч (0,1, ..., i-1) та 'праворуч' (i, i + 1, ..., len (seq)). Враховуючи те, що ми вибрали numbersPickedз лівого відомого підмножини, решта має походити з однакового рівномірного розподілу в правому невідомому підмножині, хоча параметри зараз різні. Зокрема, ймовірність, що seq[i]містить вибраний елемент, становить #remainingToChoose/#remainingToChooseFrom, або(k-numbersPicked)/(len(seq)-i), тому ми імітуємо це і повторюємо результат. (Це має закінчитися, оскільки якщо #remainingToChoose == #remainingToChooseFrom, тоді всі решта ймовірностей дорівнюють 1.) Це схоже на дерево ймовірностей, яке, як правило, динамічно генерується. В основному ви можете імітувати рівномірний розподіл ймовірностей, обумовлюючи попередній вибір (під час вирощування дерева ймовірностей ви вибираєте ймовірність поточної гілки таким чином, щоб вона була апостеріорі такою ж, як попереднє листя, тобто зумовлена ​​попереднім вибором; це спрацює, оскільки ця ймовірність рівномірно точно N / k).

редагувати : Тімоті Шилдс згадує про вибірку пласта , що є узагальненням цього методу приlen(seq) він невідомий (наприклад, із виразом генератора). Зокрема, зазначений як "алгоритм R" - це простір O (N) та O (1), якщо він виконується на місці; це передбачає взяття першого N-елемента та повільну заміну їх (також дається підказка щодо індуктивного доказу). Також є корисні розподілені варіанти та різні варіанти відбору проб пласта, які можна знайти на сторінці Вікіпедії.

редагувати : Ось інший спосіб кодувати його нижче більш семантично очевидно.

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))
пришвидшення

1
Дуже приємно, мені було цікаво, як зробити цей лінійний підхід теж. Чи має ця формула сторінку Вікіпедії? :)
Jochen Ritzel

2
Я здивований, що ця відповідь не має більше голосів, вона фактично пояснює, як працює рішення (і надає інше рішення!), На відміну від першої відповіді, яка є лише однорядковим фрагментом - не даючи мені уявлення, чому або як це працювало.
crazy2be

1
Приємне рішення ninjagecko. Існує приємний індуктивний доказ вашого рішення, якщо хтось зацікавлений написати його.
Neil G

3
Гарне рішення! Не забудьте додати from __future__ import divisionдля тих, хто працює на Python 2.
xApple

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 ?! Вам слід оновити ... це вже застарілий термін.
Катріель

1
ну, це те, що ми маємо на серверах .. робити загальносистемне оновлення занадто багато Бюрократія
Yochai Timmer

-2

random.sample реалізує це.

>>> random.sample([1, 2, 3, 4, 5],  3)   # Three samples without replacement
[4, 1, 5]

9
Це не замовлено.
Астрід
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.