198 votes

Comment créer une liste de nombres aléatoires sans doublons ?

J'ai essayé d'utiliser random.randint(0, 100) mais certains numéros sont les mêmes. Existe-t-il une méthode/module pour créer une liste de nombres aléatoires uniques ?

Note : Le code suivant est sur la base d'une réponse et a été ajouté après que la réponse ait été postée. Il ne fait pas partie de la question ; il constitue la solution.

def getScores():
    # open files to read and write
    f1 = open("page.txt", "r");
    p1 = open("pgRes.txt", "a");

    gScores = [];
    bScores = [];
    yScores = [];

    # run 50 tests of 40 random queries to implement "bootstrapping" method 
    for i in range(50):
        # get 40 random queries from the 50
        lines = random.sample(f1.readlines(), 40);

2 votes

S'ils sont uniques, ils peuvent être véritablement aléatoires dans le bon contexte. Comme un échantillon aléatoire d'index sans remplacement peut encore être complètement aléatoire.

312voto

Greg Hewgill Points 356191

Cela renvoie une liste de 10 numéros sélectionnés dans la plage de 0 à 99, sans doublons.

import random
random.sample(range(100), 10)

En référence à votre exemple de code spécifique, vous voulez probablement lire toutes les lignes du fichier une fois puis sélectionnez des lignes aléatoires dans la liste enregistrée en mémoire. Par exemple :

all_lines = f1.readlines()
for i in range(50):
    lines = random.sample(all_lines, 40)

De cette façon, vous n'avez besoin de lire le fichier qu'une seule fois, avant votre boucle. Il est beaucoup plus efficace de procéder ainsi que de revenir au début du fichier et d'appeler f1.readlines() à nouveau pour chaque itération de la boucle.

3 votes

Cette technique gaspille de la mémoire, surtout pour les grands échantillons. J'ai posté ci-dessous le code d'une solution beaucoup plus efficace en termes de mémoire et de calcul, qui utilise un générateur contructif linéaire.

0 votes

On m'a fait remarquer que la méthode LCG est moins "aléatoire" cependant, donc si vous voulez générer beaucoup de séquences aléatoires uniques, la variété sera moindre que cette solution. Si vous n'avez besoin que d'une poignée de séquences aléatoires, LCG est la solution idéale !

4 votes

numpy au lieu de random semble plus rapide. import numpy as np; np.random.permutation(100)[:10] génère également 10 numéros sélectionnés de 0 à 99, sans doublons. Le benchmarking en IPython, donne 103 µs ± 513 ns pour %timeit random.sample(range(1000), 100) et de 17 µs ± 1,24 µs pour les %timeit np.random.permutation(1000)[:100] .

33voto

Ricardo Murillo Points 634

Vous pouvez utiliser le remue-ménage de la fonction au hasard module comme celui-ci :

import random

my_list = list(xrange(1,100)) # list of integers from 1 to 99
                              # adjust this boundaries to fit your needs
random.shuffle(my_list)
print my_list # <- List of unique random numbers

Notez ici que la méthode shuffle ne renvoie pas de liste comme on pourrait s'y attendre, elle ne fait que mélanger la liste passée par référence.

1 votes

Il est bon de mentionner ici que xrange ne fonctionne que dans Python 2 et non dans Python 3.

0 votes

@ShayanShafiq : Cela dit, sur Python 3, range est xrange (mais un peu mieux), donc il suffit de laisser tomber l'option x fait que cela fonctionne de la même manière (si vous ajoutez les parenthèses print exige aussi).

12voto

ben Points 108

Vous pouvez d'abord créer une liste de numéros à partir de a a b , donde a y b sont respectivement le plus petit et le plus grand nombre dans votre liste, puis mélangez-la avec Fisher-Yates ou en utilisant l'algorithme de Python. random.shuffle méthode.

2 votes

La génération d'une liste complète d'indices est un gaspillage de mémoire, surtout pour les grands échantillons. J'ai posté ci-dessous le code d'une solution beaucoup plus efficace en termes de mémoire et de calcul, qui utilise un générateur contructif linéaire.

0 votes

L'objet n'a pas d'attribut 'sample'.

12voto

Thomas Lux Points 638

Générateur de nombres pseudo-aléatoires congruentiels linéaires

Mémoire O(1)

Opérations O(k)

Ce problème peut être résolu par une simple Générateur linéaire de congruence . Cela nécessite une charge mémoire constante (8 entiers) et au plus 2*(longueur de la séquence) de calculs.

Toutes les autres solutions utilisent plus de mémoire et plus de calcul ! Si vous n'avez besoin que de quelques séquences aléatoires, cette méthode sera nettement moins chère. Pour les plages de taille N si vous souhaitez générer un chiffre d'affaires de l'ordre de 1,5 milliard d'euros. N unique k -ou plus, je recommande la solution acceptée en utilisant les méthodes intégrées. random.sample(range(N),k) comme ceci a été optimisé en python pour plus de rapidité.

Code

# Return a randomized "range" using a Linear Congruential Generator
# to produce the number sequence. Parameters are the same as for 
# python builtin "range".
#   Memory  -- storage for 8 integers, regardless of parameters.
#   Compute -- at most 2*"maximum" steps required to generate sequence.
#
def random_range(start, stop=None, step=None):
    import random, math
    # Set a default values the same way "range" does.
    if (stop == None): start, stop = 0, start
    if (step == None): step = 1
    # Use a mapping to convert a standard range into the desired range.
    mapping = lambda i: (i*step) + start
    # Compute the number of numbers in this range.
    maximum = (stop - start) // step
    # Seed range with a random integer.
    value = random.randint(0,maximum)
    # 
    # Construct an offset, multiplier, and modulus for a linear
    # congruential generator. These generators are cyclic and
    # non-repeating when they maintain the properties:
    # 
    #   1) "modulus" and "offset" are relatively prime.
    #   2) ["multiplier" - 1] is divisible by all prime factors of "modulus".
    #   3) ["multiplier" - 1] is divisible by 4 if "modulus" is divisible by 4.
    # 
    offset = random.randint(0,maximum) * 2 + 1      # Pick a random odd-valued offset.
    multiplier = 4*(maximum//4) + 1                 # Pick a multiplier 1 greater than a multiple of 4.
    modulus = int(2**math.ceil(math.log2(maximum))) # Pick a modulus just big enough to generate all numbers (power of 2).
    # Track how many random numbers have been returned.
    found = 0
    while found < maximum:
        # If this is a valid value, yield it in generator fashion.
        if value < maximum:
            found += 1
            yield mapping(value)
        # Calculate the next value in the sequence.
        value = (value*multiplier + offset) % modulus

Utilisation

L'utilisation de cette fonction "random_range" est la même que pour tout générateur (comme "range"). Un exemple :

# Show off random range.
print()
for v in range(3,6):
    v = 2**v
    l = list(random_range(v))
    print("Need",v,"found",len(set(l)),"(min,max)",(min(l),max(l)))
    print("",l)
    print()

Résultats de l'échantillon

Required 8 cycles to generate a sequence of 8 values.
Need 8 found 8 (min,max) (0, 7)
 [1, 0, 7, 6, 5, 4, 3, 2]

Required 16 cycles to generate a sequence of 9 values.
Need 9 found 9 (min,max) (0, 8)
 [3, 5, 8, 7, 2, 6, 0, 1, 4]

Required 16 cycles to generate a sequence of 16 values.
Need 16 found 16 (min,max) (0, 15)
 [5, 14, 11, 8, 3, 2, 13, 1, 0, 6, 9, 4, 7, 12, 10, 15]

Required 32 cycles to generate a sequence of 17 values.
Need 17 found 17 (min,max) (0, 16)
 [12, 6, 16, 15, 10, 3, 14, 5, 11, 13, 0, 1, 4, 8, 7, 2, ...]

Required 32 cycles to generate a sequence of 32 values.
Need 32 found 32 (min,max) (0, 31)
 [19, 15, 1, 6, 10, 7, 0, 28, 23, 24, 31, 17, 22, 20, 9, ...]

Required 64 cycles to generate a sequence of 33 values.
Need 33 found 33 (min,max) (0, 32)
 [11, 13, 0, 8, 2, 9, 27, 6, 29, 16, 15, 10, 3, 14, 5, 24, ...]

1 votes

C'est très cool ! Mais je ne suis pas sûr que cela réponde vraiment à la question ; disons que je veux échantillonner 2 valeurs de 0 à 4. Sans générer mon propre système d'échantillonnage, je n'ai pas le temps de le faire. prime la fonction ne me renverra que 4 réponses possibles, car value est la seule chose choisie au hasard avec 4 valeurs possibles, alors que nous avons besoin d'au moins (4 choisir 2) = 6, (permettant un ordre non aléatoire). random_range(2,4) retournera les valeurs {(1, 0), (3, 2), (2, 1), (0, 3)}, mais jamais la paire (3,1) (ou (1,3)). Attendez-vous à ce que de nouveaux grands nombres premiers générés aléatoirement soient générés à chaque appel de fonction ?

1 votes

(Je suppose également que vous vous attendez à ce que les gens mélangent la séquence après que votre fonction l'ait retournée s'ils veulent un ordre aléatoire, puisque random_range(v) retourne jusqu'à v des séquences uniques au lieu de v! )

0 votes

C'est tout à fait vrai ! Il est difficile de trouver un équilibre entre éviter le dépassement des nombres entiers et générer suffisamment de séquences aléatoires. J'ai mis à jour la fonction pour incorporer un peu plus d'aléatoire, mais elle n'est toujours pas aussi aléatoire que v ! Cela dépend si vous voulez utiliser la fonction plusieurs fois. Cette solution est mieux utilisée lorsque vous générez à partir d'une large gamme de valeurs (lorsque la consommation mémoire des autres serait beaucoup plus élevée). Je vais y réfléchir davantage, merci !

9voto

inspectorG4dget Points 25092

La solution présentée dans cette réponse fonctionne, mais il pourrait devenir problématique avec la mémoire si la taille de l'échantillon est petite, mais que la population est énorme (par ex. random.sample(insanelyLargeNumber, 10) ).

Pour réparer ça, je ferais ceci :

answer = set()
sampleSize = 10
answerSize = 0

while answerSize < sampleSize:
    r = random.randint(0,100)
    if r not in answer:
        answerSize += 1
        answer.add(r)

# answer now contains 10 unique, random integers from 0.. 100

0 votes

Maintenant random.sample utilise cette approche pour un petit nombre d'échantillons provenant d'une grande population, de sorte que ce problème de mémoire n'existe plus vraiment. Bien que, au moment où cette réponse a été écrite, l'implémentation de random.shuffle aurait pu être différente.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X