Metodo O (N + K * log (K)) semplice da codificare
Prendi un campione casuale senza sostituire gli indici, ordina gli indici e prendili dall'originale.
indices = random.sample(range(len(myList)), K)
[myList[i] for i in sorted(indices)]
O più concisamente:
[x[1] for x in sorted(random.sample(enumerate(myList),K))]
Tempo O (N) ottimizzato, modo O (1) -spazio ausiliario
In alternativa, puoi utilizzare un trucco matematico e scorrere iterativamente myList
da sinistra a destra, scegliendo numeri con probabilità che cambiano dinamicamente (N-numbersPicked)/(total-numbersVisited)
. Il vantaggio di questo approccio è che è un O(N)
algoritmo poiché non comporta l'ordinamento!
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
Prova di concetto e verifica che le probabilità siano corrette :
Simulato con 1 trilione di campioni pseudocasuali nel corso di 5 ore:
>>> 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
})
Le probabilità divergono dalle probabilità vere di meno di un fattore 1.0001. L'esecuzione di questo test di nuovo ha prodotto un ordine diverso, il che significa che non è sbilanciato verso un ordine. L'esecuzione del test con un minor numero di campioni [0,1,2,3,4], k=3
e ha [0,1,2,3,4,5], k=4
avuto risultati simili.
modifica: Non sono sicuro del motivo per cui le persone votano commenti sbagliati o hanno paura di votare a favore ... NO, non c'è niente di sbagliato in questo metodo. =)
(Anche una nota utile dell'utente tegan nei commenti: se questo è python2, vorrai usare xrange, come al solito, se ti interessa davvero lo spazio extra.)
modifica : Dimostrazione: Considerando la distribuzione uniforme (senza sostituzione) di scegliere un sottoinsieme k
da una popolazione seq
di dimensioni len(seq)
, possiamo considerare una partizione in un punto arbitrario i
in 'sinistra' (0,1, ..., i-1) e 'right' (i, i + 1, ..., len (seq)). Dato che abbiamo scelto numbersPicked
dal sottoinsieme sinistro noto, il resto deve provenire dalla stessa distribuzione uniforme sul sottoinsieme sconosciuto destro, sebbene i parametri siano ora diversi. In particolare, la probabilità che seq[i]
contiene un elemento scelto è #remainingToChoose/#remainingToChooseFrom
, o(k-numbersPicked)/(len(seq)-i)
, quindi lo simuliamo e ricorreremo al risultato. (Questo deve terminare poiché se #remainingToChoose == #remainingToChooseFrom, tutte le probabilità rimanenti sono 1.) Questo è simile a un albero di probabilità che sembra essere generato dinamicamente. Fondamentalmente puoi simulare una distribuzione di probabilità uniforme condizionando le scelte precedenti (man mano che aumenti l'albero delle probabilità, scegli la probabilità del ramo corrente in modo che sia aposteriori uguale alle foglie precedenti, cioè condizionata alle scelte precedenti; questa probabilità è uniformemente esattamente N / k).
modifica : Timothy Shields menziona il campionamento del serbatoio , che è la generalizzazione di questo metodo quando len(seq)
è sconosciuto (come con un'espressione di generatore). In particolare, quello indicato come "algoritmo R" è lo spazio O (N) e O (1) se eseguito sul posto; implica prendere il primo elemento N e sostituirli lentamente (viene fornito anche un accenno a una dimostrazione induttiva). Ci sono anche utili varianti distribuite e varie varianti del campionamento del giacimento che si trovano sulla pagina di wikipedia.
modifica : ecco un altro modo per codificarlo di seguito in un modo semanticamente più ovvio.
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
e poi smista?