Générer des nombres aléatoires non répétitifs en Python

Ok c'est l'une de ces questions plus épineuses que ça n'en a l'air alors je me tourne vers le débordement de la pile parce que je ne peux pas trouver une bonne réponse. Voici ce que je veux: J'ai besoin de Python pour générer une simple liste de nombres de 0 à 1.000.000.000 dans l'ordre aléatoire pour être utilisé pour les numéros de série (en utilisant un nombre aléatoire de sorte que vous ne pouvez pas dire combien ont été assignés ou faire des attaques de timing aussi facilement, c.-à-d. deviner la prochaine qui viendra). Ces nombres sont stockés dans une table de base de données (indexés) avec les informations qui y sont liés. Le programme qui les génère ne fonctionne pas éternellement, donc il ne peut pas compter sur l'état interne.

C'est rien, hein? Tout simplement de générer une liste de nombres, de les pousser dans un tableau et d'utiliser Python "aléatoire.shuffle(big_number_array)" et nous avons fini. Le problème est que je voudrais éviter d'avoir à stocker une liste de numéros (et donc lire le fichier, pop un du dessus, enregistrer le fichier et le fermer). Je préfère les générer à la volée. Le problème est que l' les solutions que je pense avoir des problèmes:

1) générer un nombre aléatoire et vérifier s'il a déjà été utilisé. Si elle a été utilisée, générez un nouveau numéro, vérifiez, répétez au besoin jusqu'à ce que je trouve un numéro non utilisé. Le problème ici est que je peux obtenir malchance et de générer beaucoup de numéros utilisés avant d'obtenir un qui est non utilisé. Solution Possible: utiliser un très grand bassin de nombres pour réduire les chances de cela (mais alors je finis avec des nombres longs stupides).

2) Générez un nombre aléatoire et vérifiez s'il a déjà été utilisé. Si elle a été utilisée ajouter ou soustraire un du nombre et vérifier à nouveau, continuez à répéter jusqu'à ce que je frappe un nombre inutilisé. Le problème est que ce n'est plus un nombre aléatoire car j'ai introduit le biais (éventuellement, je vais obtenir des touffes de nombres et vous seriez en mesure de prédire le prochain nombre avec une meilleure chance de succès).

3) générer un nombre aléatoire et vérifier s'il a déjà été utilisé. Si il a été utilisé ajouter ou soustraire un autre nombre aléatoire généré au hasard et vérifier à nouveau, le problème est que nous sommes de retour à la production de nombres aléatoires et la vérification comme dans la solution 1.

4) aspirez-le et générez la liste aléatoire et enregistrez-la, Demandez à un démon de les mettre dans une file d'attente afin qu'il y ait des nombres disponibles (et évitez d'ouvrir et de fermer un fichier en permanence, en le compilant à la place).

5) générez des nombres aléatoires beaucoup plus grands et hachez-les (c.-à-d. en utilisant MD5) pour obtenir un plus petite valeur numérique, nous devrions rarement avoir des collisions, mais je me retrouve avec des nombres plus grands que nécessaire à nouveau.

6) Ajouter ou ajouter du temps en fonction de l'information pour le nombre aléatoire (c'est à dire le timestamp unix) afin de réduire la probabilité de collision, de nouveau je obtenir plus nombreux que ce que j'ai besoin.

N'importe qui a n'importe quelles idées intelligentes qui réduiront les chances d'une" collision " (c.-à-d. générant un nombre aléatoire qui est déjà pris) mais me permettra également de garder le nombre "petit" (c'est-à-dire moins d'un milliard (ou Mille millions pour vos européens =)).

réponse et pourquoi je l'ai accepté:

donc je vais simplement aller avec 1, et j'espère que ce n'est pas un problème, mais si c'est le cas, je vais aller avec la solution déterministe de générer tous les nombres et de les stocker afin qu'il y ait une garantie d'obtenir un nouveau nombre aléatoire, et je peux utiliser des" petits " nombres (i.e. 9 chiffres au lieu d'un MD5/etc.).

39
demandé sur bigredbob 2010-01-16 12:27:22

17 réponses

c'est un beau problème, et j'y ai pensé pendant un certain temps (avec des solutions similaires à Sjoerd ), mais à la fin, voici ce que je pense:

utilisez votre point 1) et arrêtez de vous inquiéter.

dans l'hypothèse d'un aléatoire réel, la probabilité qu'un nombre aléatoire ait déjà été choisi est le nombre de nombres préalablement choisis divisé par la taille de votre pool, c'est-à-dire le nombre maximal.

si vous dites que vous n'avez besoin que d'un milliard de numéros, c'est-à – dire neuf chiffres: Offrez-vous trois chiffres de plus, de sorte que vous avez des numéros de série de 12 chiffres (c'est-à-dire trois groupes de quatre chiffres-agréable et lisible).

même si vous êtes proche d'avoir choisi un milliard de nombres auparavant, la probabilité que votre nouveau nombre est déjà pris est encore que 0,1%.

Faire l'étape 1 et de tirer à nouveau. Vous pouvez toujours vérifier pour une boucle "infinie", dites ne pas essayer plus de 1000 fois ou ainsi, et puis revenir à ajouter 1 (ou quelque chose d'autre).

vous gagnerez à la loterie avant que cette retombée ne soit utilisée.

25
répondu balpha 2017-05-23 11:52:55

vous pouvez utiliser format-préserver le chiffrement pour chiffrer un compteur. Votre compteur va juste de 0 vers le haut, et le cryptage utilise une clé de votre choix pour le transformer en une valeur apparemment aléatoire de quelque radix et largeur que vous voulez.

Les blocs de chiffrement

ont normalement une taille de bloc fixe de 64 ou 128 bits, par exemple. Mais le cryptage de préservation de Format vous permet de prendre un chiffre standard comme AES et de faire un chiffre de plus petite largeur, de n'importe quel radix et la largeur que vous voulez (par exemple radix 10, Largeur 9 pour les paramètres de la question), avec un algorithme qui est encore cryptographiquement robuste.

il est garanti de ne jamais avoir de collisions (parce que les algorithmes cryptographiques créent une cartographie 1:1). Il est également réversible (une cartographie bidirectionnelle), de sorte que vous pouvez prendre le nombre résultant et revenir à la valeur de compteur que vous avez commencé avec.

AES-FFX est un projet de norme sur la méthode à atteindre cet objectif.

j'ai expérimenté un code Python de base pour AES-FFX-- voir le code Python ici (mais notez qu'il n'est pas entièrement conforme à la spécification AES-FFX). Il peut par exemple chiffrer un compteur à un nombre décimal aléatoire de 7 chiffres. Par exemple:

0000000   0731134
0000001   6161064
0000002   8899846
0000003   9575678
0000004   3030773
0000005   2748859
0000006   5127539
0000007   1372978
0000008   3830458
0000009   7628602
0000010   6643859
0000011   2563651
0000012   9522955
0000013   9286113
0000014   5543492
0000015   3230955
...       ...

pour un autre exemple en Python, en utilisant une autre méthode non-AES-FFX (je pense), voir ce billet de blog "comment générer un numéro de Compte" qui fait FPE en utilisant un cryptage Feistel. Il génère des nombres de 0 à 2^32-1.

12
répondu Craig McQueen 2016-11-30 01:05:55

avec quelques nombres premiers et arithmétiques modulaires, vous pouvez créer tous les nombres entre 0 et un grand premier, hors de l'ordre. si vous choisissez vos numéros avec soin, le numéro suivant est difficile à deviner.

modulo = 87178291199 # prime
incrementor = 17180131327 # relative prime

current = 433494437 # some start value
for i in xrange(1, 100):
    print current
    current = (current + incrementor) % modulo
8
répondu Sjoerd 2010-01-16 13:13:47

S'ils ne doivent pas être aléatoires, mais pas forcément linéaires (1, 2, 3, 4, ...), alors voici un algorithme simple:

choisissez deux nombres premiers. L'un d'eux sera le plus grand nombre que vous pouvez générer, donc il devrait être d'environ un milliard. L'autre devrait être assez grand.

max_value = 795028841
step = 360287471
previous_serial = 0
for i in xrange(0, max_value):
    previous_serial += step
    previous_serial %= max_value
    print "Serial: %09i" % previous_serial

il suffit de stocker la série précédente à chaque fois pour que vous sachiez où vous vous êtes arrêté. Je ne peux pas prouver mathématiquement que cela fonctionne (a été trop longtemps des classes particulières), mais il est manifestement correct avec des nombres premiers plus petits:

s = set()
with open("test.txt", "w+") as f:
    previous_serial = 0
    for i in xrange(0, 2711):
        previous_serial += 1811
        previous_serial %= 2711
        assert previous_serial not in s
        s.add(previous_serial)

vous pourriez également le prouver empiriquement avec des nombres premiers de 9 chiffres, il faudrait juste un peu plus de travail (ou beaucoup plus de mémoire).

cela signifie qu'avec quelques numéros de série, il serait possible de déterminer quelles sont vos valeurs--mais avec seulement neuf chiffres, il est peu probable que vous optiez pour des numéros imprécis de toute façon.

6
répondu Glenn Maynard 2010-01-16 10:35:13

si vous n'avez pas besoin de quelque chose de cryptographiquement sécurisé, mais juste "suffisamment brouillé"...

Champs De Galois

vous pouvez essayer des opérations dans Galois Fields , p.ex. GF(2) 32 , pour faire correspondre un simple compteur incrémentant x à un numéro de série apparemment aléatoire y :

x = counter_value
y = some_galois_function(x)
  • multiplier par une constante
    • Inverse est de multiplier par la réciproque de la constante
  • Élever à une puissance : x n
  • réciproque x -1 1519130920"
    • cas spécial de montée au pouvoir n
    • Il est son propre inverse
  • Exponentiation d'un élément primitif: a x

beaucoup de ces opérations ont une inverse, ce qui signifie, compte tenu de votre numéro de série, vous pouvez calculer la valeur originale du compteur à partir de laquelle il a été dérivé.

comme pour trouver une bibliothèque pour le champ Galois pour Python... bonne question. Si vous n'avez pas besoin de vitesse (ce que vous ne feriez pas pour cela) alors vous pouvez faire votre propre. Je n'ai pas essayé ceux-ci:

multiplication matricielle en GF(2)

choisir une matrice inversible 32×32 appropriée dans GF(2), et multiplier un compteur d'entrée 32 bits par celui-ci. C'est sur le plan conceptuel, lié à L'EFT, tel que décrit dans réponse de S. Lott .

CRC

une possibilité connexe est d'utiliser un CRC calcul. Basé sur le reste de la division longue avec un polynôme irréductible dans GF(2). Le code Python est facilement disponible pour les CRCs ( crcmod , pycrc ), bien que vous puissiez choisir un autre polynôme irréductible que celui qui est normalement utilisé, pour vos besoins. Je suis un peu confus sur la théorie, mais je pense qu'un CRC 32 bits devrait générer une valeur unique pour chaque combinaison possible d'entrées 4 octets. Cochez cette. Il est assez facile de vérifier expérimentalement ceci, en réalimentant la sortie dans l'entrée, et en vérifiant qu'il produit un cycle complet de longueur 2 32 -1 (zéro correspond à zéro). Vous pouvez avoir besoin de se débarrasser de n'importe quel xors initial/final dans L'algorithme de CRC pour ce chèque pour travailler.

6
répondu Craig McQueen 2017-05-23 12:00:17

je pense que vous surestimez les problèmes avec l'approche 1). À moins que vous n'ayez des exigences en temps réel juste en vérifiant par choix aléatoire se termine assez rapidement. La probabilité d'avoir besoin de plus d'un nombre d'itérations décroît exponentiellement. Avec 100M nombres outputted (10% fillfactor) vous aurez une chance sur milliard d'exiger plus de 9 itérations. Même avec 50% des nombres pris vous aurez en moyenne besoin de 2 itérations et avoir une chance sur un milliard d'exiger plus de 30 vérifier. Ou même le cas extrême où 99% des nombres sont déjà pris pourrait encore être raisonnable - vous aurez une moyenne de 100 itérations et avoir 1 sur un milliard de changement de exiger 2062 itérations

5
répondu Ants Aasma 2010-01-16 12:26:55

la séquence de graines du générateur de nombres aléatoires congruents linéaires standard ne peut pas être répétée tant que l'ensemble complet de nombres à partir de la valeur de départ n'a pas été généré. Alors il doit répéter précisément.

la graine interne est souvent Grande (48 ou 64 bits). Les nombres générés sont plus petits (32 bits habituellement) parce que l'ensemble des bits ne sont pas aléatoires. Si vous suivez les valeurs de graine elles formeront une séquence non-répétitive distincte.

la question est essentiellement celle de localiser une bonne graine qui génère" assez " de nombres. Vous pouvez choisir une graine, et générer des nombres jusqu'à ce que vous obtenez de nouveau à la graine de départ. C'est la longueur de la séquence. Il peut s'agir de millions ou de milliards de nombres.

il y a quelques lignes directrices à Knuth pour cueillir des graines convenables qui produiront de très longues séquences de nombres uniques.

4
répondu S.Lott 2010-01-16 13:09:50

vous pouvez exécuter 1) sans courir dans le problème de trop de nombres aléatoires erronés si vous diminuez juste l'intervalle aléatoire d'un chaque fois.

pour que cette méthode fonctionne, vous devrez sauvegarder les nombres déjà donnés (ce que vous voulez faire de toute façon) et aussi sauvegarder la quantité de nombres pris.

il est assez évident que, après avoir recueilli 10 nombres, votre pool de nombres aléatoires possibles aura été diminué de 10. Donc, vous ne devez pas choisir un nombre entre 1 et 1.000.000 mais entre 1 et 999.990. Bien sûr, ce nombre n'est pas le nombre réel mais seulement un indice (à moins que les 10 nombres collectés aient été 999.991, 999.992, ...); il faudrait compter maintenant à partir de 1 en omettant tous les nombres déjà collectés.

bien sûr, votre algorithme devrait être plus intelligent que de compter de 1 à 1.000.000 mais j'espère que vous comprenez la méthode.

Je n'aime pas dessiner des nombres aléatoires jusqu'à Je en avoir un qui s'adapte à chaque. Il se sent juste mal.

1
répondu Debilski 2010-01-18 01:18:16

My solution https://github.com/glushchenko/python-unique-id , je pense que vous devriez étendre la matrice pour 1.000.000.000 de variations et avoir du plaisir.

1
répondu fluder 2012-08-27 00:26:24

Je repenserais au problème lui-même... Vous ne semblez pas faire quelque chose de séquentiel avec les nombres... et vous avez un index sur la colonne. Ont-ils réellement besoin pour être numéros ?

considère un SHA hash... vous n'avez pas réellement besoin de toute chose. Faites ce que git ou d'autres services de raccourcissement d'url font, et prenez les premiers 3/4/5 caractères du hachage. Étant donné que chaque caractère a maintenant 36 valeurs possibles au lieu de 10, vous avez 2 176 782 336 combinaisons au lieu de 999 999 combinaisons (pour six chiffres). Combinez cela avec un contrôle rapide sur si la combinaison existe (une requête d'index pure) et une graine comme un timestamp + nombre aléatoire et il devrait faire pour presque n'importe quelle situation.

0
répondu Sudhir Jonathan 2010-01-16 09:51:22

avez-vous besoin que ce soit cryptographiquement sécurisé ou juste difficile à deviner? Quelles sont les collisions? Parce que si elle doit être cryptographiquement forte et avoir zéro collisions, il est, malheureusement, impossible.

0
répondu Andrew McGregor 2010-01-16 10:35:44

j'ai commencé à essayer d'écrire une explication de l'approche utilisée ci-dessous, mais juste la mettre en œuvre était plus facile et plus précis. Cette approche a le comportement étrange qu'il obtient plus vite les nombres que vous avez générés. Mais cela fonctionne, et cela ne vous oblige pas à générer tous les nombres à l'avance.

Comme une simple optimisation, vous pouvez facilement faire de cette classe utilisent un algorithme probabiliste (générer un nombre aléatoire, et si ce n'est pas dans l'ensemble des numéros utilisés ajoutez - le à l'ensemble et retournez-le) dans un premier temps, gardez le cap sur le taux de collision, et passez à l'approche déterministe utilisée ici une fois que le taux de collision devient mauvais.

import random

class NonRepeatingRandom(object):

    def __init__(self, maxvalue):
        self.maxvalue = maxvalue
        self.used = set()

    def next(self):
        if len(self.used) >= self.maxvalue:
            raise StopIteration
        r = random.randrange(0, self.maxvalue - len(self.used))
        result = 0
        for i in range(1, r+1):
            result += 1
            while result in self.used:
                 result += 1
        self.used.add(result)
        return result

    def __iter__(self):
        return self

    def __getitem__(self):
        raise NotImplemented

    def get_all(self):
        return [i for i in self]

>>> n = NonRepeatingRandom(20)
>>> n.get_all()
[12, 14, 13, 2, 20, 4, 15, 16, 19, 1, 8, 6, 7, 9, 5, 11, 10, 3, 18, 17]
0
répondu Robert Rossney 2010-01-16 20:06:54

S'il vous suffit qu'un observateur occasionnel ne puisse pas deviner la valeur suivante, vous pouvez utiliser des choses comme un générateur de congruence linéaire ou même un simple registre de décalage de rétroaction linéaire pour générer les valeurs et garder l'état dans la base de données dans le cas où vous avez besoin de plus de valeurs. Si vous utilisez ces droits, les valeurs ne se répèteront pas avant la fin de l'univers. Vous trouverez plus d'idées dans la liste des générateurs de nombres aléatoires .

si vous pensez qu'il pourrait y avoir quelqu'un qui aurait un intérêt sérieux à deviner les valeurs suivantes, vous pouvez utiliser une séquence de base de données pour compter les valeurs que vous générez et les chiffrer avec un algorithme de cryptage ou un autre cryptographiquement fort perfect a fonction. Cependant, vous devez prendre soin que l'algorithme de cryptage n'est pas facilement cassable si l'on peut mettre la main sur une séquence de numéros successifs que vous avez généré - un simple RSA par exemple, ne le fera pas à cause de L'attaque de message liée à Franklin-Reiter .

0
répondu Hans-Peter Störr 2011-12-09 09:52:28

réponse un peu tardive, mais je n'ai vu cela suggéré nulle part.

pourquoi ne pas utiliser le module uuid pour créer identificateurs globaux uniques

"
0
répondu Mew 2012-08-24 11:58:31

pour générer une liste de nombres totalement aléatoires à l'intérieur d'un seuil défini, comme suit:

plist=list()
length_of_list=100
upbound=1000
lowbound=0
while len(pList)<(length_of_list):
     pList.append(rnd.randint(lowbound,upbound))
     pList=list(set(pList))
0
répondu David moreno 2014-05-31 18:17:04

je suis tombé sur le même problème et j'ai ouvert une question avec un titre différent avant d'arriver à celle-ci. Ma solution est un échantillon aléatoire générateur d'index (c'est à dire la non-répétition des nombres) dans l'intervalle [0,maximal) , appelé itersample . Voici quelques exemples d'utilisation:

import random
generator=itersample(maximal)
another_number=generator.next() # pick the next non-repeating random number

ou

import random
generator=itersample(maximal)
for random_number in generator:
    # do something with random_number
    if some_condition: # exit loop when needed
        break

itersample génère des entiers aléatoires non répétitifs, besoin de stockage est limité aux numéros choisis, et le temps nécessaire pour choisir n numéros doivent être (comme certains tests le confirment) O(n log(n)) , quel que soit maximal .

voici le code de itersample :

import random
def itersample(c): # c = upper bound of generated integers
    sampled=[]
    def fsb(a,b): # free spaces before middle of interval a,b
        fsb.idx=a+(b+1-a)/2
        fsb.last=sampled[fsb.idx]-fsb.idx if len(sampled)>0 else 0
        return fsb.last
    while len(sampled)<c:
        sample_index=random.randrange(c-len(sampled))
        a,b=0,len(sampled)-1
        if fsb(a,a)>sample_index:
            yielding=sample_index
            sampled.insert(0,yielding)
            yield yielding
        elif fsb(b,b)<sample_index+1:
            yielding=len(sampled)+sample_index
            sampled.insert(len(sampled),yielding)
            yield yielding
        else: # sample_index falls inside sampled list
            while a+1<b:
                if fsb(a,b)<sample_index+1:
                    a=fsb.idx
                else:
                    b=fsb.idx
            yielding=a+1+sample_index
            sampled.insert(a+1,yielding)
            yield yielding
0
répondu mmj 2017-05-23 12:16:44

vous déclarez que vous stockez les numéros dans une base de données.

ne serait-il pas alors plus facile d'y stocker tous les numéros et de demander à la base de données un numéro aléatoire non utilisé? La plupart des bases de données soutiennent une telle demande.

exemples

MySQL:

SELECT column FROM table
ORDER BY RAND()
LIMIT 1

PostgreSQL:

SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1
-2
répondu Johan 2010-01-16 10:26:54