web-dev-qa-db-fra.com

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

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

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);
65
iCodeLikeImDrunk

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

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

En vous référant à votre exemple de code spécifique, vous souhaiterez probablement lire toutes les lignes du fichier une fois puis sélectionner 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, il vous suffit de lire le fichier une seule fois, avant votre boucle. Il est beaucoup plus efficace de le faire que de revenir au début du fichier et d’appeler à nouveau f1.readlines() pour chaque itération de boucle.

109
Greg Hewgill

Vous pouvez d'abord créer une liste de nombres de a à b, où a et b sont respectivement les nombres les plus petits et les plus grands de votre liste, puis mélangez-les avec l'algorithme Fisher-Yates ou utilisez la méthode random.shuffle de Python .

9
ben

Vous pouvez utiliser la fonction shuffle du module random comme ceci:

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 aucune liste comme on peut s'y attendre, elle ne fait que mélanger la liste passée par référence.

8
Ricardo Murillo

La solution présentée dans cette réponse fonctionne, mais la mémoire risque de poser problème si la taille de l'échantillon est petite, mais la population est énorme (par exemple random.sample(insanelyLargeNumber, 10)). 

Pour résoudre ce problème, je voudrais aller avec 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
7
inspectorG4dget

Si vous devez échantillonner de très grands nombres, vous ne pouvez pas utiliser range 

random.sample(range(10000000000000000000000000000000), 10)

parce qu'il jette:

OverflowError: Python int too large to convert to C ssize_t

De plus, si random.sample ne peut pas produire le nombre d'éléments souhaité en raison d'une plage trop petite

 random.sample(range(2), 1000)

il jette:

 ValueError: Sample larger than population

Cette fonction résout les deux problèmes:

import random

def random_sample(count, start, stop, step=1):
    def gen_random():
        while True:
            yield random.randrange(start, stop, step)

    def gen_n_unique(source, n):
        seen = set()
        seenadd = seen.add
        for i in (i for i in source() if i not in seen and not seenadd(i)):
            yield i
            if len(seen) == n:
                break

    return [i for i in gen_n_unique(gen_random,
                                    min(count, int(abs(stop - start) / abs(step))))]

Utilisation avec des nombres extrêmement grands:

print('\n'.join(map(str, random_sample(10, 2, 10000000000000000000000000000000))))

Exemple de résultat:

7822019936001013053229712669368
6289033704329783896566642145909
2473484300603494430244265004275
5842266362922067540967510912174
6775107889200427514968714189847
9674137095837778645652621150351
9969632214348349234653730196586
1397846105816635294077965449171
3911263633583030536971422042360
9864578596169364050929858013943

Utilisation où la plage est inférieure au nombre d'éléments demandés:

print(', '.join(map(str, random_sample(100000, 0, 3))))

Exemple de résultat:

2, 0, 1

Il fonctionne également avec des plages et des étapes négatives:

print(', '.join(map(str, random_sample(10, 10, -10, -2))))
print(', '.join(map(str, random_sample(10, 5, -5, -2))))

Exemple de résultats:

2, -8, 6, -2, -4, 0, 4, 10, -6, 8
-3, 1, 5, -1, 3
3
Handcraftsman

Si la liste de N nombres de 1 à N est générée de manière aléatoire, alors oui, il est possible que certains nombres puissent être répétés.

Si vous voulez une liste de nombres de 1 à N dans un ordre aléatoire, remplissez un tableau avec des entiers de 1 à N, puis utilisez un Fisher-Yates shuffle ou Python random.shuffle() .

3
Mitch Wheat

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

O (1) mémoire

O (k) Opérations

Ce problème peut être résolu avec un simple générateur Linear Congruential . Cela nécessite une surcharge de mémoire constante (8 entiers) et au plus 2 calculs * (longueur de séquence).

Toutes les autres solutions utilisent plus de mémoire et plus de calculs! 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 dans l'ordre N unique k- séquences ou plus, je recommande la solution acceptée à l'aide des méthodes intégrées random.sample(range(N),k), car ce a été optimisé en vitesse de python.

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

Usage

L'utilisation de cette fonction "random_range" est la même que pour n'importe quel 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()

Exemples de résultats

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, ...]
2
Thomas Lux

Une fonction très simple qui résout également votre problème

from random import randint

data = []

def unique_Rand(inicial, limit, total):

        data = []

        i = 0

        while i < total:
            number = randint(inicial, limit)
            if number not in data:
                data.append(number)
                i += 1

        return data


data = unique_Rand(1, 60, 6)

print(data)


"""

prints something like 

[34, 45, 2, 36, 25, 32]

"""
0
Vinicius Torino

Le problème avec les approches basées sur les ensembles ("si une valeur aléatoire dans les valeurs de retour, essayez à nouveau") est que leur temps d'exécution est indéterminé en raison de collisions (qui nécessitent une autre itération "essayer à nouveau"), en particulier lorsqu'un grand nombre de valeurs aléatoires sont renvoyées. de la gamme.

Une alternative qui n'est pas sujette à ce runtime non déterministe est la suivante:

import bisect
import random

def fast_sample(low, high, num):
    """ Samples :param num: integer numbers in range of
        [:param low:, :param high:) without replacement
        by maintaining a list of ranges of values that
        are permitted.

        This list of ranges is used to map a random number
        of a contiguous a range (`r_n`) to a permissible
        number `r` (from `ranges`).
    """
    ranges = [high]
    high_ = high - 1
    while len(ranges) - 1 < num:
        # generate a random number from an ever decreasing
        # contiguous range (which we'll map to the true
        # random number).
        # consider an example with low=0, high=10,
        # part way through this loop with:
        #
        # ranges = [0, 2, 3, 7, 9, 10]
        #
        # r_n :-> r
        #   0 :-> 1
        #   1 :-> 4
        #   2 :-> 5
        #   3 :-> 6
        #   4 :-> 8
        r_n = random.randint(low, high_)
        range_index = bisect.bisect_left(ranges, r_n)
        r = r_n + range_index
        for i in xrange(range_index, len(ranges)):
            if ranges[i] <= r:
                # as many "gaps" we iterate over, as much
                # is the true random value (`r`) shifted.
                r = r_n + i + 1
            Elif ranges[i] > r_n:
                break
        # mark `r` as another "gap" of the original
        # [low, high) range.
        ranges.insert(i, r)
        # Fewer values possible.
        high_ -= 1
    # `ranges` happens to contain the result.
    return ranges[:-1]
0
orange

Vous pouvez utiliser la bibliothèque Numpy pour une réponse rapide, comme indiqué ci-dessous -

Un extrait de code donné répertorie 6 numéros uniques compris entre 0 et 5. Vous pouvez régler les paramètres pour votre confort.

import numpy as np
import random
a = np.linspace( 0, 5, 6 )
random.shuffle(a)
print(a)

Sortie

[ 2.  1.  5.  3.  4.  0.]

Cela n’impose aucune contrainte comme nous le voyons dans random.sample en tant que référencé ici .

J'espère que ça aide un peu.

0
dataLeo

Si vous souhaitez vous assurer que les numéros ajoutés sont uniques, vous pouvez utiliser un objet Set

si vous utilisez 2.7 ou supérieur, ou importez le module sets sinon.

Comme d'autres l'ont mentionné, cela signifie que les chiffres ne sont pas vraiment aléatoires.

0
Recaiden

La réponse fournie ici fonctionne très bien en ce qui concerne le temps ainsi que la mémoire mais un peu plus compliquée car elle utilise des constructions python avancées. La réponse plus simple fonctionne bien dans la pratique, mais le problème avec cette réponse Est qu’elle peut générer de nombreux entiers parasites avant de construire réellement Essayez-le avec populationSize = 1000, sampleSize = 999 . En théorie, il se peut que cela ne se termine pas.

La réponse ci-dessous aborde les deux questions, car elle est déterministe et quelque peu efficace Bien qu’elle ne soit pas aussi efficace que les deux autres.

def randomSample(populationSize, sampleSize):
  populationStr = str(populationSize)
  dTree, samples = {}, []
  for i in range(sampleSize):
    val, dTree = getElem(populationStr, dTree, '')
    samples.append(int(val))
  return samples, dTree

où les fonctions getElem, percolateUp sont telles que définies ci-dessous

import random

def getElem(populationStr, dTree, key):
  msd  = int(populationStr[0])
  if not key in dTree.keys():
    dTree[key] = range(msd + 1)
  idx = random.randint(0, len(dTree[key]) - 1)
  key = key +  str(dTree[key][idx])
  if len(populationStr) == 1:
    dTree[key[:-1]].pop(idx)
    return key, (percolateUp(dTree, key[:-1]))
  newPopulation = populationStr[1:]
  if int(key[-1]) != msd:
    newPopulation = str(10**(len(newPopulation)) - 1)
  return getElem(newPopulation, dTree, key)

def percolateUp(dTree, key):
  while (dTree[key] == []):
    dTree[key[:-1]].remove( int(key[-1]) )
    key = key[:-1]
  return dTree

Enfin, le temps moyen était d’environ 15 ms pour une valeur élevée de n, comme indiqué ci-dessous,

In [3]: n = 10000000000000000000000000000000

In [4]: %time l,t = randomSample(n, 5)
Wall time: 15 ms

In [5]: l
Out[5]:
[10000000000000000000000000000000L,
 5731058186417515132221063394952L,
 85813091721736310254927217189L,
 6349042316505875821781301073204L,
 2356846126709988590164624736328L]
0
aak318