web-dev-qa-db-fra.com

Comment générer un nombre aléatoire à 4 chiffres ne commençant pas par 0 et ayant des chiffres uniques?

Cela fonctionne presque bien mais le nombre commence parfois par 0:

import random
numbers = random.sample(range(10), 4)
print(''.join(map(str, numbers)))

J'ai trouvé beaucoup d'exemples mais aucun ne garantit que la séquence ne commencera pas par 0.

38
Menon A.

Nous générons le premier chiffre dans la plage 1 - 9, puis prenons les 3 suivants parmi les chiffres restants:

import random

# We create a set of digits: {0, 1, .... 9}
digits = set(range(10))
# We generate a random integer, 1 <= first <= 9
first = random.randint(1, 9)
# We remove it from our set, then take a sample of
# 3 distinct elements from the remaining values
last_3 = random.sample(digits - {first}, 3)
print(str(first) + ''.join(map(str, last_3)))

Les nombres générés sont équiprobables et nous obtenons un nombre valide en une seule étape.

65
Thierry Lathuille

Bouclez jusqu'à ce que vous ayez quelque chose que vous aimez:

import random

numbers = [0]
while numbers[0] == 0:
    numbers = random.sample(range(10), 4)

print(''.join(map(str, numbers)))
31
aghast

Ceci est très similaire aux autres réponses mais au lieu de sample ou shuffle, vous pouvez dessiner un entier aléatoire dans la plage 1000-9999 jusqu'à ce que vous en obteniez un qui ne contient que des chiffres uniques:

import random

val = 0  # initial value - so the while loop is entered.
while len(set(str(val))) != 4:  # check if it's duplicate free
    val = random.randint(1000, 9999)

print(val)

Comme @Claudio l'a souligné dans les commentaires, la plage ne doit en fait être que de 1023 à 9876 car les valeurs en dehors de cette plage contiennent des chiffres en double.

Généralement random.randint sera beaucoup plus rapide que random.shuffle ou random.choice donc même s'il est plus probable que l'on doive dessiner plusieurs fois (comme souligné par @karakfa) c'est jusqu'à 3 fois plus rapide que n'importe quelle approche shuffle, choice qui doit également join les chiffres uniques.

20
MSeifert

Je ne sais pas Python bien, mais quelque chose comme

digits=[1,2,3,4,5,6,7,8,9] <- no zero
random.shuffle(digits)
first=digits[0] <- first digit, obviously will not be zero
digits[0]=0 <- used digit can not occur again, zero can
random.shuffle(digits)
lastthree=digits[0:3] <- last three digits, no repeats, can contain zero, thanks @Dubu

Une itération plus utile, créant en fait un nombre:

digits=[1,2,3,4,5,6,7,8,9]   # no zero
random.shuffle(digits)
val=digits[0]                # value so far, not zero for sure
digits[0]=0                  # used digit can not occur again, zero becomes a valid pick
random.shuffle(digits)
for i in range(0,3):
  val=val*10+digits[i]       # update value with further digits
print(val)

Après avoir volé des morceaux d'autres solutions, plus appliqué la pointe de @DavidHammen:

val=random.randint(1,9)
digits=[1,2,3,4,5,6,7,8,9]
digits[val-1]=0
for i in random.sample(digits,3):
  val=val*10+i
print(val)
15
tevemadar

méthode d'échantillonnage de rejet. Créez une combinaison aléatoire à 4 chiffres à partir de 10 chiffres et rééchantillonnez si elle ne correspond pas aux critères.

r4=0    
while r4 < 1000:
    r4=int(''.join(map(str,random.sample(range(10),4))))

a remarqué que c'est essentiellement la même chose que @Austin Haskings réponse

11
karakfa

[Fixe] Décaler les quatre chiffres sur une position n'est pas correct. L'échange de zéro de tête avec une position fixe n'est pas non plus correct. Mais l'échange aléatoire du zéro de tête avec l'une des neuf positions est correct et donne une probabilité égale:

""" Solution: randomly shuffle all numbers. If 0 is on the 0th position,
              randomly swap it with any of nine positions in the list.

  Proof
    Lets count probability for 0 to be in position 7. It is equal to probability 1/10 
  after shuffle, plus probability to be randomly swapped in the 7th position if
  0 come to be on the 0th position: (1/10 * 1/9). In total: (1/10 + 1/10 * 1/9).
    Lets count probability for 3 to be in position 7. It is equal to probability 1/10
  after shuffle, minus probability to be randomly swapped in the 0th position (1/9)
  if 0 come to be on the 0th position (1/10) and if 3 come to be on the 7th position
  when 0 is on the 0th position (1/9). In total: (1/10 - 1/9 * 1/10 * 1/9).
    Total probability of all numbers [0-9] in position 7 is:
  9 * (1/10 - 1/9 * 1/10 * 1/9) + (1/10 + 1/10 * 1/9) = 1
    Continue to prove in the same way that total probability is equal to
  1 for all other positions.
    End of proof. """

import random
l = [0,1,2,3,4,5,6,7,8,9]
random.shuffle(l)
if l[0] == 0:
    pos = random.choice(range(1, len(l)))
    l[0], l[pos] = l[pos], l[0]
print(''.join(map(str, l[0:4])))
11
foo bar

Vous pouvez utiliser la gamme complète pour 3 numéros, puis choisissez le numéro de tête parmi les numéros restants:

import random
numbers = random.sample(range(0,10), 3)
first_number = random.choice(list(set(range(1,10))-set(numbers)))
print(''.join(map(str, [first_number]+numbers)))

Une autre façon, si le choix doit être répété (et si vous restez raisonnable sur le nombre de chiffres), est de pré-calculer la liste des sorties possibles en utilisant itertools.permutations, en filtrant ceux avec un zéro de tête et en construisant une liste d'entiers à partir de celui-ci:

import itertools,random

l = [int(''.join(map(str,x))) for x in itertools.permutations(range(10),4) if x[0]]

C'est un peu de temps de calcul, mais après cela, vous pouvez appeler:

random.choice(l)

autant de fois que vous le souhaitez. C'est très rapide et fournit un aléatoire uniformément réparti.

6

Je ne sais pas Python donc je vais poster une solution pseudo-code-ish pour ce problème spécifique:

  • Créez une variable de recherche contenant une liste de chiffres basée sur 0:

    lu = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  • Générez quatre nombres aléatoires basés sur 0 comme suit:

    r1 = random number between 0 and 8
    r2 = random number between 0 and 8
    r3 = random number between 0 and 7
    r4 = random number between 0 and 6
    
  • Utilisez la variable de recherche pour convertir des nombres aléatoires en chiffres un par un. Après chaque recherche, mutez la variable de recherche en supprimant le chiffre qui a été utilisé:

    d1 = lu[r1]
    lu.remove(d1)
    lu.insert(0)
    
    d2 = lu[r2]
    lu.remove(d2)
    
    d3 = lu[r3]
    lu.remove(d3)
    
    d4 = lu[r4]
    lu.remove(d4)
    
  • Imprimez le résultat:

    print concatenate(d1, d2, d3, d4)
    

Il est possible de généraliser un peu cette idée. Par exemple, vous pouvez créer une fonction qui accepte une liste (de chiffres) et un nombre (longueur souhaitée du résultat); la fonction renvoie le numéro et mute la liste en supprimant les chiffres utilisés. Voici une implémentation JavaScript de cette solution:

function randomCombination(list, length) {
    var i, Rand, result = "";
    for (i = 0; i < length; i++) {
        Rand = Math.floor(Math.random() * list.length);
        result += list[Rand];
        list.splice(Rand, 1);
    }
    return result;
}

function desiredNumber() {
    var list = [1, 2, 3, 4, 5, 6, 7, 8, 9],
        result;
    result = randomCombination(list, 1);
    list.Push(0);
    result += randomCombination(list, 3);
    return result;
}

var i;
for (i = 0; i < 10; i++) {
    console.log(desiredNumber());
}
4
Salman A

Voici comment je le ferais

while True:
    n = random.randrange(1000, 10000)
    if len(set(str(n))) == 4: # unique digits
        return n

Plus généralement, étant donné un générateur, vous pouvez utiliser les commandes intégrées filter et next pour prendre le premier élément qui satisfait certains tests une fonction.

numbers = iter(lambda: random.randrange(1000, 10000), None) # infinite generator
test = lambda n: len(set(str(n))) == 4
return next(filter(test, numbers))
2
Colonel Panic

Avertissement: il s'agit d'une terrible approche anti-Python, strictement pour la partie benchmarking (voir les commentaires de @ DavidHammen, et http://ideone.com/qyopLF ) L'idée est de générer les numéros de séquence de les chiffres en une seule étape, puis corrigez les collisions:

rnd=random.randint(0,4535)
(rnd,d1)=divmod(rnd,9)
(rnd,d2)=divmod(rnd,9)
#(rnd,d3)=divmod(rnd,8)
#(rnd,d4)=divmod(rnd,7)
(d4,d3)=divmod(rnd,8) # miracle found: 1 divmod happens to run faster than 2

Maintenant, nous avons d1 = 0..8, d2 = 0..8, d3 = 0..7, d4 = 0..6, il peut être testé en exécutant l'extrait de code avec rnd = 4535 (4535 = 9 * 9 * 8 * 7-1, soit dit en passant)

Tout d'abord, d1 doit être corrigé

d1=d1+1 # now d1 = 1..9

Alors d2 doit "sauter" d1 si nécessaire

if d2>=d1
  d2=d2+1 # now d2 = 0..9 "-" d1

Ensuite, la même chose doit être faite avec les chiffres restants, devenant très vite laid:

if d3>=d1:
  d3=d3+1    # now d3 = 0..8 "-" d1
  if d3>=d2:
    d3=d3+1  # now d3 = 0..9 "-" {d1,d2}
Elif d3>=d2: # this branch prepares for the other variant
  d3=d3+1
  if d3>=d1: # ">=" is preserved for consistency, here "==" may occur only
    d3=d3+1

Et la dernière partie est catastrophique:

if d4>=d1:
  d4=d4+1
  if d4>=d2:
    d4=d4+1
    if d4>=d3:
      d4=d4+1
  Elif d4>=d3:
    d4=d4+1
    if d4>=d2:
      d4=d4+1
Elif d4>=d2:
  d4=d4+1
  if d4>=d1:
    d4=d4+1
    if d4>=d3:
      d4=d4+1
  Elif d4>=d3:
    d4=d4+1
    if d4>=d1:
      d4=d4+1
Elif d4>=d3:
  d4=d4+1
  if d4>=d2:
    d4=d4+1
    if d4>=d1:
      d4=d4+1
  Elif d4>=d1:
    d4=d4+1
    if d4>=d2:
      d4=d4+1

Pour les nombres plus longs, cela pourrait fonctionner plus rapidement avec les champs de bits, mais je ne vois pas de manière triviale. (Vérifier une fois les relations> = ne suffit pas, car la collision peut facilement se produire après avoir effectué une incrémentation. Par exemple, d1 = 1, d2 = 2, d3 = 1: d3 entre en collision avec d1, mais ne se heurte pas initialement à d2. Cependant, après avoir "percé le trou" à 1, d3 devient 2 et maintenant il entre en collision avec d2. Il n'y a aucun moyen trivial de repérer cette collision à l'avance)

Comme le code pue l'enfer, je mets une étape de vérification à la fin

val = d1*1000 + d2*100 + d3*10 + d4
#if len(set(str(val))) != 4: print(str(val)+" "+str(o1)+","+str(o2)+","+str(o3)+","+str(o4))
if len(set(str(val))) != 4: print(val)

Il est déjà plus rapide que les autres codes vraiment rapides (la vérification commentée affiche les chiffres originaux conservés après les divmod-s, à des fins de débogage. Ce n'est pas le genre de code qui fonctionne immédiatement ...). Commenter les deux vérifications le rend encore plus rapide.

EDIT: à propos de vérifier ceci et cela

Il s'agit d'une approche qui maintient une relation 1: 1 entre l'ensemble minimal d'entrées valides (0 ... 4535) et les sorties valides (les 9 * 9 * 8 * 7 nombres possibles à 4 chiffres avec des chiffres distincts, sans commencer par -0). Ainsi, une simple boucle peut et doit générer tous les nombres, ils peuvent être vérifiés un par un et ils peuvent être collectés dans un ensemble par exemple afin de voir s'ils sont tous des résultats distincts

Pratiquement:

collect=set()
for rnd in range(0,4536):
    (rnd,d1)=divmod(rnd,9)
    ... rest of the code, also the verification step kept active ...
    collect.add(val)
print(len(collect))

1) Il n'imprimera rien dans la boucle (tous les résultats sont des nombres à 4 chiffres avec des chiffres distincts)

2) Il imprimera 4536 à la fin (tous les résultats sont distincts)

On peut ajouter une vérification pour le premier chiffre (d1), ici et maintenant je suppose simplement que
"(quelque chose de mod 9) +1" ne sera pas 0.

1
tevemadar

Combinez les générateurs avec next

Une façon d'écrire en Pythonic serait d'utiliser 2 générateurs imbriqués et next:

from random import randint
from itertools import count

print(next(i for i in (randint(1023, 9876) for _ in count()) if len(set(str(i))) == 4))
# 8756

Il s'agit essentiellement d'une variante à une ligne de @ MSeifert's answer

Prétraitez tous les nombres acceptables

Si vous avez besoin de nombreux nombres aléatoires, vous pouvez investir du temps et de la mémoire pour prétraiter tous les nombres acceptables:

import random    
possible_numbers = [i for i in range(1023, 9877) if len(set(str(i))) == 4]

1023 et 9877 sont utilisés comme limites car aucun int inférieur à 1023 ou supérieur à 9876 ne peut avoir 4 nombres de distince uniques.

Ensuite, il vous suffirait de random.choice pour une génération très rapide:

print(random.choice(possible_numbers))
# 7234
1
Eric Duminil