web-dev-qa-db-fra.com

Moyen le plus rapide de générer une chaîne unique de type aléatoire avec une longueur aléatoire dans Python 3

Je sais comment créer des chaînes aléatoires, comme:

''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(N))

Cependant, il ne devrait pas y avoir de doublons, donc ce que je vérifie actuellement, c'est que la clé existe déjà dans une liste, comme le montre le code suivant:

import secrets
import string
import numpy as np


amount_of_keys = 40000

keys = []

for i in range(0,amount_of_keys):
    N = np.random.randint(12,20)
    n_key = ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(N))
    if not n_key in keys:
        keys.append(n_key)

Ce qui convient pour un petit nombre de clés comme 40000. Cependant, plus le nombre de clés est important, plus le problème n'est pas à l'échelle. Je me demande donc s’il existe un moyen plus rapide d’obtenir le résultat pour encore plus de clés, comme 999999

23
Kev1n91

Améliorations de base, ensembles et noms locaux

Utilisez un ensemble, pas une liste, et le test de l'unicité est beaucoup plus rapide; Les tests d'appartenance à set prennent un temps constant indépendamment de la taille définie, tandis que les listes prennent un temps linéaire O(N). Utilisez un ensemble de compréhension pour produire une série de clés à la fois afin d’éviter de rechercher et d’appeler la méthode set.add() dans une boucle; correctement aléatoires, les grandes touches ont de toute façon très peu de chances de produire des doublons.

Comme cela se fait dans une boucle serrée, cela vaut la peine d'optimiser au maximum toutes les recherches de noms:

import secrets
import numpy as np
from functools import partial

def produce_amount_keys(amount_of_keys, _randint=np.random.randint):
    keys = set()
    pickchar = partial(secrets.choice, string.ascii_uppercase + string.digits)
    while len(keys) < amount_of_keys:
        keys |= {''.join([pickchar() for _ in range(_randint(12, 20))]) for _ in range(amount_of_keys - len(keys))}
    return keys

L'argument _randint lie le nom np.random.randint à un élément local de la fonction, qui est plus rapide à référencer que les éléments globaux, en particulier lorsque des recherches d'attribut sont impliquées.

Le pickchar() partial évite de rechercher des attributs sur des modules ou plusieurs sections locales; c'est un simple appelable qui a toutes les références en place, donc il est plus rapide à exécuter, surtout lorsqu'il est exécuté en boucle.

La boucle while continue à itérer uniquement si des doublons ont été générés. Nous produisons suffisamment de clés dans un ensemble de compréhension pour remplir le reste s'il n'y a pas de doublons.

Horaires pour cette première amélioration

Pour 100 articles, la différence n'est pas si grande:

>>> timeit('p(100)', 'from __main__ import produce_amount_keys_list as p', number=1000)
8.720592894009314
>>> timeit('p(100)', 'from __main__ import produce_amount_keys_set as p', number=1000)
7.680242831003852

mais lorsque vous commencez à augmenter cette taille, vous remarquerez que le coût du test d'adhésion O(N) par rapport à une liste ralentit réellement votre version:

>>> timeit('p(10000)', 'from __main__ import produce_amount_keys_list as p', number=10)
15.46253142200294
>>> timeit('p(10000)', 'from __main__ import produce_amount_keys_set as p', number=10)
8.047800761007238

Ma version est déjà presque deux fois plus rapide que 10 000 éléments; 40 000 éléments peuvent être exécutés 10 fois en environ 32 secondes:

>>> timeit('p(40000)', 'from __main__ import produce_amount_keys_list as p', number=10)
138.84072386901244
>>> timeit('p(40000)', 'from __main__ import produce_amount_keys_set as p', number=10)
32.40720253501786

La version de liste prenait plus de 2 minutes, plus de dix fois plus longtemps.

Fonction random.choice de Numpy, non cryptographiquement puissante

Vous pouvez encore accélérer le processus en renonçant au module secrets et en utilisant np.random.choice() à la place; cela ne produira cependant pas un niveau aléatoire de niveau cryptographique, mais la sélection d'un caractère aléatoire est deux fois plus rapide:

def produce_amount_keys(amount_of_keys, _randint=np.random.randint):
    keys = set()
    pickchar = partial(
        np.random.choice,
        np.array(list(string.ascii_uppercase + string.digits)))
    while len(keys) < amount_of_keys:
        keys |= {''.join([pickchar() for _ in range(_randint(12, 20))]) for _ in range(amount_of_keys - len(keys))}
    return keys

Cela fait une énorme différence: vous pouvez désormais produire 10 fois 40 000 clés en seulement 16 secondes:

>>> timeit('p(40000)', 'from __main__ import produce_amount_keys_npchoice as p', number=10)
15.632006907981122

Autres ajustements avec le module itertools et un générateur

Nous pouvons également utiliser la fonction unique_everseen() du module itertoolsRecipes pour qu'elle prenne soin de l'unicité, puis utiliser un générateur infini et la fonction itertools.islice() pour limiter les résultats à juste le nombre que nous voulons:

# additional imports
from itertools import islice, repeat

# assumption: unique_everseen defined or imported

def produce_amount_keys(amount_of_keys):
    pickchar = partial(
        np.random.choice,
        np.array(list(string.ascii_uppercase + string.digits)))
    def gen_keys(_range=range, _randint=np.random.randint):
        while True:
            yield ''.join([pickchar() for _ in _range(_randint(12, 20))])
    return list(islice(unique_everseen(gen_keys()), amount_of_keys))

C'est un peu plus rapide encore, mais seulement de manière marginale:

>>> timeit('p(40000)', 'from __main__ import produce_amount_keys_itertools as p', number=10)
14.698191125993617

os.urandom () octets et une méthode différente de production de chaînes

Ensuite, nous pourrons suivre les idées de Adam Barnes pour l’utilisation de UUID4 (qui est en fait juste une enveloppe autour de os.urandom() ) et de Base64. Mais en repliant la casse Base64 et en remplaçant 2 caractères par des caractères choisis au hasard, sa méthode limite sévèrement l'entropie dans ces chaînes (vous ne produirez pas la plage complète de valeurs uniques possibles, une chaîne de 20 caractères utilisant uniquement (256 ** 15) / (36 ** 20) == 1 in chaque 99437 bits d'entropie!).

L'encodage Base64 utilise à la fois des majuscules et des minuscules et des chiffres, mais aussi ajoute les caractères - et / (ou + et _ pour la variante sécurisée contre les URL). Pour les lettres majuscules et les chiffres, vous devez mettre la sortie en majuscule et mapper ces deux caractères supplémentaires sur d'autres caractères aléatoires, processus qui élimine une grande quantité d'entropie des données aléatoires fournies par os.urandom(). Au lieu d'utiliser Base64, vous pouvez également utiliser le codage Base32, qui utilise des lettres majuscules et les chiffres de 2 à 8, génère ainsi des chaînes avec 32 ** n possibilités contre 36 ** n. Cependant, cela peut accélérer les choses plus loin des tentatives ci-dessus:

import os
import base64
import math

def produce_amount_keys(amount_of_keys):
    def gen_keys(_urandom=os.urandom, _encode=base64.b32encode, _randint=np.random.randint):
        # (count / math.log(256, 32)), rounded up, gives us the number of bytes
        # needed to produce *at least* count encoded characters
        factor = math.log(256, 32)
        input_length = [None] * 12 + [math.ceil(l / factor) for l in range(12, 20)]
        while True:
            count = _randint(12, 20)
            yield _encode(_urandom(input_length[count]))[:count].decode('ascii')
    return list(islice(unique_everseen(gen_keys()), amount_of_keys))

C'est vraiment rapide:

>>> timeit('p(40000)', 'from __main__ import produce_amount_keys_b32 as p', number=10)
4.572628145979252

Touches 40k, 10 fois, en un peu plus de 4 secondes. Donc environ 75 fois plus vite; la vitesse d'utilisation de os.urandom() comme source est indéniable.

Ceci est, à nouveau cryptographiquement fort; os.urandom() produit des octets pour une utilisation cryptographique. D'autre part, nous avons réduit le nombre de chaînes possibles produites de plus de 90% (((36 ** 20) - (32 ** 20)) / (36 ** 20) * 100 étant 90.5), nous n'utilisons plus les chiffres 0, 1, 8 et 9 dans les sorties.

Alors peut-être devrions-nous utiliser l’astuce urandom() pour produire un encodage Base36 approprié; nous devrons produire notre propre fonction b36encode():

import string
import math

def b36encode(b, 
        _range=range, _ceil=math.ceil, _log=math.log, _fb=int.from_bytes, _len=len, _b=bytes,
        _c=(string.ascii_uppercase + string.digits).encode()):
    """Encode a bytes value to Base36 (uppercase ASCII and digits)

    This isn't too friendly on memory because we convert the whole bytes
    object to an int, but for smaller inputs this should be fine.
    """
    b_int = _fb(b, 'big')
    length = _len(b) and _ceil(_log((256 ** _len(b)) - 1, 36))
    return _b(_c[(b_int // 36 ** i) % 36] for i in _range(length - 1, -1, -1))

et utiliser cela:

def produce_amount_keys(amount_of_keys):
    def gen_keys(_urandom=os.urandom, _encode=b36encode, _randint=np.random.randint):
        # (count / math.log(256, 36)), rounded up, gives us the number of bytes
        # needed to produce *at least* count encoded characters
        factor = math.log(256, 36)
        input_length = [None] * 12 + [math.ceil(l / factor) for l in range(12, 20)]
        while True:
            count = _randint(12, 20)
            yield _encode(_urandom(input_length[count]))[-count:].decode('ascii')
    return list(islice(unique_everseen(gen_keys()), amount_of_keys))

Ceci est relativement rapide et produit surtout toute la gamme des 36 lettres majuscules et chiffres:

>>> timeit('p(40000)', 'from __main__ import produce_amount_keys_b36 as p', number=10)
8.099918447987875

Certes, la version base32 est presque deux fois plus rapide que celle-ci (grâce à une implémentation efficace de Python utilisant une table), mais l'utilisation d'un encodeur Base36 personnalisé est toujours deux fois plus rapide que la version numpy.random.choice() non sécurisée du point de vue cryptographique.Cependant, en utilisant os.urandom()produit un biais encore; nous devons produire plus d'entropie qu'il n'en faut pour 12 à 19 bases36 chiffres. Par exemple, pour 17 chiffres, nous ne pouvons pas produire 36 ** 17 valeurs différentes en utilisant des octets, mais seulement l'équivalent le plus proche de 256 ** 11 octets, ce qui est environ 1,08 fois trop élevé. Nous allons donc nous retrouver avec un biais. vers A, B, et dans une moindre mesure, C (merci Stefan Pochmann pour l’avoir signalé).

Choisir un entier inférieur à (36 ** length) et mapper des entiers à base36.

Nous devons donc recourir à une méthode aléatoire sécurisée qui peut nous donner des valeurs réparties de manière égale entre 0 (inclus) et 36 ** (desired length) (exclusif). Nous pouvons ensuite mapper le numéro directement sur la chaîne souhaitée.

Tout d'abord, mapper l'entier sur une chaîne; ce qui suit a été modifié pour produire la chaîne de sortie la plus rapide:.

def b36number(n, length, _range=range, _c=string.ascii_uppercase + string.digits): """Convert an integer to Base36 (uppercase ASCII and digits)""" chars = [_c[0]] * length while n: length -= 1 chars[length] = _c[n % 36] n //= 36 return ''.join(chars)

En utilisant secrets.randbelow() la fonction devient:.

import secrets def produce_amount_keys(amount_of_keys): def gen_keys(_below=secrets.randbelow, _encode=b36number, _randint=np.random.randint): limit = [None] * 12 + [36 ** l for l in range(12, 20)] while True: count = _randint(12, 20) yield _encode(_below(limit[count]), count) return list(islice(unique_everseen(gen_keys()), amount_of_keys))

>>> timeit('p(40000)', 'from __main__ import produce_amount_keys_below as p', number=10)
5.135716405988205

This is almost as fast as the Base32 approach, but produces the full range of keys!

45
Martijn Pieters

C'est donc une course de vitesse, c'est ça?

S'appuyant sur le travail de Martijn Pieters, j'ai une solution qui exploite intelligemment une autre bibliothèque pour générer des chaînes aléatoires: uuid.

Ma solution est de générer un uuid4, base64 le coder et le majuscule, pour obtenir uniquement les caractères que nous recherchons, puis le découper à une longueur aléatoire.

Cela fonctionne dans ce cas car la longueur des sorties que nous recherchons, (12-20), est plus courte que le codage base64 le plus court d'un uuid4. C'est aussi très rapide, parce que uuid est très rapide.

J'en ai également fait un générateur au lieu d'une fonction régulière, car ils peuvent être plus efficaces.

Fait intéressant, l'utilisation de la fonction randint de la bibliothèque standard était plus rapide que celle de numpy.

Voici la sortie de test:

Timing 40k keys 10 times with produce_amount_keys
20.899942063027993
Timing 40k keys 10 times with produce_amount_keys, stdlib randint
20.85920040300698
Timing 40k keys 10 times with uuidgen
3.852462349983398
Timing 40k keys 10 times with uuidgen, stdlib randint
3.136272903997451

Voici le code pour uuidgen():

def uuidgen(count, _randint=np.random.randint):
    generated = set()

    while True:
        if len(generated) == count:
            return

        candidate = b64encode(uuid4().hex.encode()).upper()[:_randint(12, 20)]
        if candidate not in generated:
            generated.add(candidate)
            yield candidate

Et ici est l’ensemble du projet. (Au commit d9925d au moment de la rédaction).


Grâce aux commentaires de Martijn Pieters, j'ai quelque peu amélioré la méthode, en augmentant l'entropie et en l'accélérant d'un facteur d'environ 1/6.

Il y a encore beaucoup d'entropie perdue lors de la conversion de toutes les lettres minuscules en majuscules. Si cela est important, il est peut-être préférable d'utiliser b32encode(), qui contient les caractères souhaités, moins 0, 1, 8 et 9.

La nouvelle solution se lit comme suit:

def urandomgen(count):
    generated = set()

    while True:
        if len(generated) == count:
            return

        desired_length = randint(12, 20)

        # # Faster than math.ceil
        # urandom_bytes = urandom(((desired_length + 1) * 3) // 4)
        #
        # candidate = b64encode(urandom_bytes, b'//').upper()
        #
        # The above is rolled into one line to cut down on execution
        # time stemming from locals() dictionary access.

        candidate = b64encode(
            urandom(((desired_length + 1) * 3) // 4),
            b'//',
        ).upper()[:desired_length]

        while b'/' in candidate:
            candidate = candidate.replace(b'/', choice(ALLOWED_CHARS), 1)

        if candidate not in generated:
            generated.add(candidate)
            yield candidate.decode()

Et la sortie de test:

Timing 40k keys 10 times with produce_amount_keys, stdlib randint
19.64966493297834
Timing 40k keys 10 times with uuidgen, stdlib randint
4.063803717988776
Timing 40k keys 10 times with urandomgen, stdlib randint
2.4056471119984053

Le nouveau commit dans mon référentiel est 5625fd .


Les commentaires de Martijn sur l'entropie m'ont fait réfléchir. La méthode que j'ai utilisée avec base64 et .upper() rend les lettres SO beaucoup plus courantes que les nombres. J'ai revisité le problème avec un esprit plus binaire.

L'idée était de prendre la sortie de os.urandom(), de l'interpréter comme une longue chaîne de nombres non signés à 6 bits et d'utiliser ces chiffres comme index pour un tableau défilant des caractères autorisés. Le premier nombre à 6 bits sélectionnerait un caractère de la plage A..Z0..9A..Z01, le deuxième nombre à 6 bits sélectionnerait un caractère de la plage 2..9A..Z0..9A..T, etc.

Ceci a un léger écrasement d'entropie en ce que le premier caractère sera légèrement moins susceptible de contenir 2..9, le deuxième caractère moins susceptible de contenir U..Z0, etc., mais il est tellement meilleur qu'avant.

Il est légèrement plus rapide que uuidgen() et légèrement plus lent que urandomgen(), comme indiqué ci-dessous:

Timing 40k keys 10 times with produce_amount_keys, stdlib randint
20.440480664998177
Timing 40k keys 10 times with uuidgen, stdlib randint
3.430628580001212
Timing 40k keys 10 times with urandomgen, stdlib randint
2.0875444510020316
Timing 40k keys 10 times with bytegen, stdlib randint
2.8740892770001665

Je ne suis pas tout à fait sûr de savoir comment éliminer le dernier écrasement d'entropie; décaler le point de départ pour les personnages ne fera que déplacer le motif le long d'un motif, la randomisation du décalage sera lente, le brassage de la carte aura encore une période ... Je suis ouvert aux idées.

Le nouveau code est le suivant:

from os import urandom
from random import randint
from string import ascii_uppercase, digits

# Masks for extracting the numbers we want from the maximum possible
# length of `urandom_bytes`.
bitmasks = [(0b111111 << (i * 6), i) for i in range(20)]
allowed_chars = (ascii_uppercase + digits) * 16  # 576 chars long


def bytegen(count):
    generated = set()

    while True:
        if len(generated) == count:
            return

        # Generate 9 characters from 9x6 bits
        desired_length = randint(12, 20)
        bytes_needed = (((desired_length * 6) - 1) // 8) + 1

        # Endianness doesn't matter.
        urandom_bytes = int.from_bytes(urandom(bytes_needed), 'big')

        chars = [
            allowed_chars[
                (((urandom_bytes & bitmask) >> (i * 6)) + (0b111111 * i)) % 576
            ]
            for bitmask, i in bitmasks
        ][:desired_length]

        candidate = ''.join(chars)

        if candidate not in generated:
            generated.add(candidate)
            yield candidate

Et le code complet, avec un plus en profondeur README sur l'implémentation, est terminé à de0db8 .

J'ai essayé plusieurs choses pour accélérer la mise en œuvre, comme visible dans le repo. Un codage de caractères où les chiffres et ASCII = les lettres majuscules sont séquentielles serait certainement utile.

8
Adam Barnes

Un simple et rapide:

def b36(n, N, chars=string.ascii_uppercase + string.digits):
    s = ''
    for _ in range(N):
        s += chars[n % 36]
        n //= 36
    return s

def produce_amount_keys(amount_of_keys):
    keys = set()
    while len(keys) < amount_of_keys:
        N = np.random.randint(12, 20)
        keys.add(b36(secrets.randbelow(36**N), N))
    return keys

- Edit: Ce qui suit fait référence à une précédente révision de la réponse de Martijn. Après notre discussion, il a ajouté une autre solution, qui est essentiellement la même que la mienne mais avec quelques optimisations. Ils n’aident pas beaucoup, cependant, c’est seulement environ 3,4% plus rapide que les miens lors de mes tests, donc à mon avis, ils ne font que compliquer les choses. -

Comparée à la solution finale de Martijn dans sa réponse acceptée la mienne est beaucoup plus simple, le facteur 1,7 est plus rapide et non biaisée:

Stefan
8.246490597876106 seconds.
8 different lengths from 12 to 19
  Least common length 19 appeared 124357 times.
  Most common length 16 appeared 125424 times.
36 different characters from 0 to Z
  Least common character Q appeared 429324 times.
  Most common character Y appeared 431433 times.
36 different first characters from 0 to Z
  Least common first character C appeared 27381 times.
  Most common first character Q appeared 28139 times.
36 different last characters from 0 to Z
  Least common last character Q appeared 27301 times.
  Most common last character E appeared 28109 times.

Martijn
14.253227412021943 seconds.
8 different lengths from 12 to 19
  Least common length 13 appeared 124753 times.
  Most common length 15 appeared 125339 times.
36 different characters from 0 to Z
  Least common character 9 appeared 428176 times.
  Most common character C appeared 434029 times.
36 different first characters from 0 to Z
  Least common first character 8 appeared 25774 times.
  Most common first character A appeared 31620 times.
36 different last characters from 0 to Z
  Least common last character Y appeared 27440 times.
  Most common last character X appeared 28168 times.

Le caractère de Martijn est biaisé dans le premier caractère, A apparaît beaucoup trop souvent et 8 trop rarement. J'ai fait mon test dix fois, son premier caractère le plus commun était toujours A ou B (cinq fois chacun) et son plus petit caractère commun était toujours 7, 8 ou 9 (respectivement deux, trois et cinq fois). J'ai également vérifié les longueurs séparément, la longueur 17 étant particulièrement mauvaise, son premier caractère le plus commun est toujours apparu environ 51500 fois, tandis que son premier caractère le moins commun est apparu environ 25400 fois.

Note latérale amusante: J'utilise le module secrets que Martijn a rejeté :-)

Mon script entier:

import string
import secrets
import numpy as np
import os
from itertools import islice, filterfalse
import math

#------------------------------------------------------------------------------------
#   Stefan
#------------------------------------------------------------------------------------

def b36(n, N, chars=string.ascii_uppercase + string.digits):
    s = ''
    for _ in range(N):
        s += chars[n % 36]
        n //= 36
    return s

def produce_amount_keys_stefan(amount_of_keys):
    keys = set()
    while len(keys) < amount_of_keys:
        N = np.random.randint(12, 20)
        keys.add(b36(secrets.randbelow(36**N), N))
    return keys

#------------------------------------------------------------------------------------
#   Martijn
#------------------------------------------------------------------------------------

def b36encode(b, 
        _range=range, _ceil=math.ceil, _log=math.log, _fb=int.from_bytes, _len=len, _b=bytes,
        _c=(string.ascii_uppercase + string.digits).encode()):
    b_int = _fb(b, 'big')
    length = _len(b) and _ceil(_log((256 ** _len(b)) - 1, 36))
    return _b(_c[(b_int // 36 ** i) % 36] for i in _range(length - 1, -1, -1))

def produce_amount_keys_martijn(amount_of_keys):
    def gen_keys(_urandom=os.urandom, _encode=b36encode, _randint=np.random.randint, _factor=math.log(256, 36)):
        while True:
            count = _randint(12, 20)
            yield _encode(_urandom(math.ceil(count / _factor)))[-count:].decode('ascii')
    return list(islice(unique_everseen(gen_keys()), amount_of_keys))

#------------------------------------------------------------------------------------
#   Needed for Martijn
#------------------------------------------------------------------------------------

def unique_everseen(iterable, key=None):
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

#------------------------------------------------------------------------------------
#   Benchmark and quality check
#------------------------------------------------------------------------------------

from timeit import timeit
from collections import Counter

def check(name, func):
    print()
    print(name)

    # Get 999999 keys and report the time.
    keys = None
    def getkeys():
        nonlocal keys
        keys = func(999999)
    t = timeit(getkeys, number=1)
    print(t, 'seconds.')

    # Report statistics about lengths and characters
    def statistics(label, values):
        ctr = Counter(values)
        least = min(ctr, key=ctr.get)
        most = max(ctr, key=ctr.get)
        print(len(ctr), f'different {label}s from', min(ctr), 'to', max(ctr))
        print(f'  Least common {label}', least, 'appeared', ctr[least], 'times.')
        print(f'  Most common {label}', most, 'appeared', ctr[most], 'times.')
    statistics('length', map(len, keys))
    statistics('character', ''.join(keys))
    statistics('first character', (k[0] for k in keys))
    statistics('last character', (k[-1] for k in keys))

for _ in range(2):
    check('Stefan', produce_amount_keys_stefan)
    check('Martijn', produce_amount_keys_martijn)
3
Stefan Pochmann

Approche alternative: Unicité dans la création plutôt que par test

L'approche évidente de votre question serait de générer une sortie aléatoire, puis de vérifier si elle est unique. Bien que je ne propose pas d'implémentation, voici une approche alternative:

  1. Générer une sortie aussi aléatoire que possible
  2. Génère une sortie qui est garantie d'être unique et qui a l'air quelque peu aléatoire
  3. Combinez-les

Vous avez maintenant une sortie qui est garantie d'être unique et qui semble être aléatoire.

Exemple

Supposons que vous souhaitiez générer 999999 chaînes de 12 à 20 longueurs. L'approche fonctionnera bien sûr pour tous les jeux de caractères, mais restons simple et supposons que vous souhaitiez utiliser uniquement les valeurs 0 à 9.

  1. Générer une sortie aléatoire de 6 à 14 longueurs
  2. Permuter au hasard les numéros 000000 à 999999 (oui, 6 chiffres, c’est beaucoup de choses à "sacrifier" dans un caractère apparemment aléatoire, mais avec un jeu de caractères plus grand, vous n’avez pas besoin de autant de caractères)
  3. Maintenant, combinez-les de manière à ce que l'unicité soit préservée. Le moyen le plus trivial serait une simple concaténation des entités, mais vous pouvez bien sûr penser à des solutions moins évidentes. 

Exemple à petite échelle

  1. Générer du hasard:

    sdfdsf xxer ver 

  2. Générer l'unicité

    xdaebd

  3. Combiner

    xdsdfdsf aexxer bdver

Notez que cette méthode suppose que vous avez un nombre minimum de caractères par entrée, ce qui semble être le cas dans votre question.

0
Dennis Jaheruddin