J'ai une liste triée, disons: (ce n'est pas vraiment que des nombres, c'est une liste d'objets qui sont triés avec un algorithme compliqué prenant beaucoup de temps)
mylist = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ,9 , 10 ]
Y at-il une fonction python qui me donnera N des éléments, mais gardera l'ordre?
Exemple:
randomList = getRandom(mylist,4)
# randomList = [ 3 , 6 ,7 , 9 ]
randomList = getRandom(mylist,4)
# randomList = [ 1 , 2 , 4 , 8 ]
etc...
Le code suivant générera un échantillon aléatoire de taille 4:
import random
sample_size = 4
sorted_sample = [
mylist[i] for i in sorted(random.sample(range(len(mylist)), sample_size))
]
(note: avec Python 2, mieux vaut utiliser xrange
au lieu de range
)
Explication
random.sample(range(len(mylist)), sample_size)
génère un échantillon aléatoire de indices de la liste d'origine.
Ces index sont ensuite triés pour préserver l'ordre des éléments dans la liste d'origine.
Enfin, la compréhension de la liste extrait les éléments réels de la liste d'origine, en fonction des indices échantillonnés.
Prélevez un échantillon au hasard sans remplacer les index, triez-les et prenez-les à partir de l'original.
indices = random.sample(range(len(myList)), K)
[myList[i] for i in sorted(indices)]
Ou plus concement:
[x[1] for x in sorted(random.sample(enumerate(myList),K))]
Vous pouvez également utiliser une astuce mathématique et parcourir de manière itérative myList
de gauche à droite, en choisissant des nombres avec une probabilité de changement dynamique (N-numbersPicked)/(total-numbersVisited)
. L'avantage de cette approche est qu'il s'agit d'un algorithme O(N)
puisqu'il ne nécessite pas de tri!
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
Preuve de concept et test que les probabilités sont correctes:
Simulé avec 1 billion d’échantillons pseudo-aléatoires sur une période de 5 heures:
>>> 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
})
Les probabilités s'écartent des probabilités réelles d'un facteur moins égal à 1.0001. Lancer à nouveau ce test a abouti à un ordre différent, ce qui signifie qu'il n'est pas biaisé en faveur d'un ordre. L'exécution du test avec moins d'échantillons pour [0,1,2,3,4], k=3
et [0,1,2,3,4,5], k=4
a donné des résultats similaires.
edit: Vous ne savez pas pourquoi les gens votent pour de faux commentaires ou craignent de passer à un vote supérieur ... NON, il n'y a rien de mal à cette méthode. =)
(Également une note utile de l’utilisateur tegan dans les commentaires: s’il s’agit de python2, vous voudrez utiliser xrange, comme d’habitude, si vous vous souciez vraiment de l’espace supplémentaire.)
edit: Preuve: Compte tenu de la distribution uniforme (sans remplacement) du choix d'un sous-ensemble de k
sur une population seq
de taille len(seq)
, nous pouvons considérer une partition à un point arbitraire i
dans 'gauche' (0, 1, ..., i-1) et 'right' (i, i + 1, ..., len (seq)). Étant donné que nous avons sélectionné numbersPicked
dans le sous-ensemble connu de gauche, le reste doit provenir de la même distribution uniforme dans le sous-ensemble inconnu de droite, bien que les paramètres soient maintenant différents. En particulier, la probabilité que seq[i]
contienne un élément choisi est #remainingToChoose/#remainingToChooseFrom
ou (k-numbersPicked)/(len(seq)-i)
, aussi nous simulons cela et recurse sur le résultat. (Cela doit prendre fin puisque si #remainingToChoose == #remainingToChooseFrom, toutes les probabilités restantes sont égales à 1.) Ceci est similaire à un arbre de probabilités généré dynamiquement. Fondamentalement, vous pouvez simuler une distribution de probabilité uniforme en conditionnant des choix antérieurs (lorsque vous développez l’arbre de probabilité, vous choisissez la probabilité de la branche actuelle de telle sorte qu’elle soit aposteriori identique aux congés précédents, c’est-à-dire conditionnée à des choix antérieurs; cette probabilité est uniformément exactement N/k).
edit: Timothy Shields mentionne Échantillonnage de réservoir , qui est la généralisation de cette méthode lorsque len(seq)
est inconnu (comme avec une expression génératrice). Spécifiquement, celui noté comme "algorithme R" est O(N) et O(1) espace si cela est fait sur place; cela implique de prendre le premier élément N et de le remplacer lentement (un indice de preuve inductive est également donné). Il existe également des variantes distribuées utiles et diverses variantes d'échantillonnage de réservoir disponibles sur la page wikipedia.
edit: Voici un autre moyen de le coder ci-dessous d'une manière plus sémantique.
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)
)
Peut-être pouvez-vous simplement générer un échantillon d'index, puis collecter les éléments de votre liste.
randIndex = random.sample(range(len(mylist)), sample_size)
randIndex.sort()
Rand = [mylist[i] for i in randIndex]
Apparemment, random.sample
a été introduit dans Python 2.3
donc pour la version en dessous, on peut utiliser shuffle (exemple pour 4 éléments):
myRange = range(0,len(mylist))
shuffle(myRange)
coupons = [ bestCoupons[i] for i in sorted(myRange[:4]) ]
random.sample l'implémente.
>>> random.sample([1, 2, 3, 4, 5], 3) # Three samples without replacement
[4, 1, 5]