Modo simple de codificar O (N + K * log (K))
Tome una muestra aleatoria sin reemplazar los índices, ordene los índices y sáquelos del original.
indices = random.sample(range(len(myList)), K)
[myList[i] for i in sorted(indices)]
O más concisamente:
[x[1] for x in sorted(random.sample(enumerate(myList),K))]
Optimizado O (N) -time, O (1) -auxiliary-space way
Alternativamente, puede usar un truco matemático y pasar iterativamente myList
de izquierda a derecha, seleccionando números con probabilidad que cambia dinámicamente (N-numbersPicked)/(total-numbersVisited)
. La ventaja de este enfoque es que es un O(N)
algoritmo, ya que no implica ordenar.
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
Prueba de concepto y prueba de que las probabilidades son correctas :
Simulado con 1 billón de muestras pseudoaleatorias en el transcurso de 5 horas:
>>> 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
})
Las probabilidades difieren de las probabilidades verdaderas en menos de un factor de 1.0001. La ejecución de esta prueba nuevamente resultó en un orden diferente, lo que significa que no está sesgado hacia un pedido. Ejecutar la prueba con menos muestras [0,1,2,3,4], k=3
y [0,1,2,3,4,5], k=4
tuvo resultados similares.
editar: No estoy seguro de por qué la gente vota comentarios incorrectos o tiene miedo de votar ... NO, no hay nada de malo en este método. =)
(También una nota útil del usuario tegan en los comentarios: si esto es python2, querrá usar xrange, como de costumbre, si realmente le importa el espacio extra).
editar : Prueba: Considerando la distribución uniforme (sin reemplazo) de elegir un subconjunto de k
una población seq
de tamaño len(seq)
, podemos considerar una partición en un punto arbitrario i
en 'izquierda' (0,1, ..., i-1) y 'derecha' (i, i + 1, ..., len (seq)). Dado que elegimos numbersPicked
del subconjunto conocido de la izquierda, el resto debe provenir de la misma distribución uniforme en el subconjunto desconocido de la derecha, aunque los parámetros ahora son diferentes. En particular, la probabilidad de que seq[i]
contenga un elemento elegido es #remainingToChoose/#remainingToChooseFrom
, o(k-numbersPicked)/(len(seq)-i)
, así que simulamos eso y recurrimos al resultado. (Esto debe terminar ya que si #remainingToChoose == #remainingToChooseFrom, entonces todas las probabilidades restantes son 1.) Esto es similar a un árbol de probabilidad que se genera dinámicamente. Básicamente, puede simular una distribución de probabilidad uniforme condicionando las elecciones anteriores (a medida que aumenta el árbol de probabilidad, elige la probabilidad de la rama actual de modo que sea aposteriori igual que las hojas anteriores, es decir, condicionada a las elecciones anteriores; esto funcionará porque esta probabilidad es uniformemente exactamente N / k).
editar : Timothy Shields menciona Reservoir Sampling , que es la generalización de este método cuando len(seq)
se desconoce (como con una expresión generadora). Específicamente, el que se indica como "algoritmo R" es el espacio O (N) y O (1) si se realiza en el lugar; implica tomar el primer elemento N y reemplazarlo lentamente (también se da una pista sobre una prueba inductiva). También hay variantes distribuidas útiles y variantes diversas de muestreo de yacimientos que se encuentran en la página de wikipedia.
editar : Aquí hay otra forma de codificarlo a continuación de una manera más obvia semánticamente.
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
y luego ordenar?