J'essaie d'écrire un algorithme qui sélectionnerait aléatoirement N éléments distincts d'une séquence, sans connaître la taille de la séquence à l'avance, et où il est coûteux de parcourir la séquence plusieurs fois. Par exemple, les éléments de la séquence peuvent être les lignes d’un fichier volumineux.
J'ai trouvé une solution lorsque N = 1 (c'est-à-dire lorsque vous essayez de choisir exactement un élément au hasard dans une séquence énorme):
import random
items = range(1, 10) # Imagine this is a huge sequence of unknown length
count = 1
selected = None
for item in items:
if random.random() * count < 1:
selected = item
count += 1
Mais comment puis-je obtenir la même chose pour d'autres valeurs de N (disons, N = 3)?
Utiliser échantillonnage de réservoir . C'est un algorithme très simple qui fonctionne pour tout N
.
Si votre séquence est suffisamment courte pour que la lecture en mémoire et le tri aléatoire soient acceptables, une approche simple consisterait simplement à utiliser random.shuffle
:
import random
arr=[1,2,3,4]
# In-place shuffle
random.shuffle(arr)
# Take the first 2 elements of the now randomized array
print arr[0:2]
[1, 3]
Selon le type de votre séquence, vous devrez peut-être la convertir en liste en appelant list(your_sequence)
, mais cela fonctionnera quels que soient les types d'objets de votre séquence.
Naturellement, si vous ne pouvez pas adapter votre séquence à la mémoire ou si les besoins en mémoire ou en CPU de cette approche sont trop élevés pour vous, vous devrez utiliser une solution différente.
Le plus simple que j'ai trouvé est this answer in SO:
import random
my_list = [1, 2, 3, 4, 5]
num_selections = 2
new_list = random.sample(my_list, num_selections)
# To preserve the order of the list, you could do:
randIndex = random.sample(range(len(my_list)), n_selections)
randIndex.sort()
new_list = [my_list[i] for i in randIndex]
Si vous avez la version 3.6 ou ultérieure de Python, vous pouvez choisir entre
from random import choices
items = range(1, 10)
new_items = choices(items, k = 3)
print(new_items)
[6, 3, 1]
@NPE est correct, mais les implémentations qui sont liées sont sous-optimales et peu "pythoniques". Voici une meilleure implémentation:
def sample(iterator, k):
"""
Samples k elements from an iterable object.
:param iterator: an object that is iterable
:param k: the number of items to sample
"""
# fill the reservoir to start
result = [next(iterator) for _ in range(k)]
n = k - 1
for item in iterator:
n += 1
s = random.randint(0, n)
if s < k:
result[s] = item
return result
Edit Comme @ panda-34 a fait remarquer que la version originale était défectueuse, mais pas parce que j'utilisais randint
vs randrange
. Le problème est que ma valeur initiale pour n
ne tenait pas compte du fait que randint
est inclusif aux deux extrémités de la plage. Prendre cela en compte corrige le problème. (Remarque: vous pouvez également utiliser randrange
car il est inclus pour la valeur minimale et exclusif pour la valeur maximale.)
La suite vous donnera N éléments aléatoires d'un tableau X
import random
list(map(lambda _: random.choice(X), range(N)))
Cela devrait suffire d'accepter ou de rejeter chaque nouvel élément une seule fois et, si vous l'acceptez, de jeter un ancien article choisi au hasard.
Supposons que vous avez sélectionné N éléments de K au hasard et que vous voyez un (K + 1) ème élément. Acceptez-le avec une probabilité N/(K + 1) et ses probabilités sont acceptables. Les items actuels sont entrés avec probabilité N/K, et sont jetés avec probabilité (N/(K + 1)) (1/N) = 1/(K + 1) afin de survivre avec probabilité (N/K ) (K/(K + 1)) = N/(K + 1), donc leurs probabilités sont bien aussi.
Et oui, je vois que quelqu'un vous a demandé de faire un échantillonnage de réservoir - ceci explique en partie comment cela fonctionne.
Comme mentionné, Aix a mentionné que les travaux d’échantillonnage dans les réservoirs étaient efficaces. Une autre option consiste à générer un nombre aléatoire pour chaque nombre affiché et à sélectionner les k premiers chiffres.
Pour le faire de manière itérative, maintenez un tas de k paires (nombre aléatoire) et chaque fois que vous voyez un nouveau nombre insérer dans le tas s'il est supérieur à la plus petite valeur du tas.
C'était ma réponse à une question en double (fermée avant que je puisse poster) qui était quelque peu liée ("générer des nombres aléatoires sans aucun doublon"). Comme c'est une approche différente des autres réponses, je vais le laisser ici au cas où cela fournirait des informations supplémentaires.
from random import randint
random_nums = []
N = # whatever number of random numbers you want
r = # lower bound of number range
R = # upper bound of number range
x = 0
while x < N:
random_num = randint(r, R) # inclusive range
if random_num in random_nums:
continue
else:
random_nums.append(random_num)
x += 1
La raison de la boucle while sur la boucle for est qu’elle permet une implémentation plus aisée de la génération aléatoire (par exemple, si vous obtenez 3 doublons, vous n’obtiendrez pas de nombres N-3).