Простий у кодуванні 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)
)
random.sample
а потім сортуєте?