Existe-t-il un moyen en .NET de générer une séquence de todos les entiers de 32 bits ( Int32
) dans un ordre aléatoire, sans répétitions, et d'une manière efficace du point de vue de la mémoire ? Une mémoire efficace signifie qu'il faut utiliser au maximum quelques centaines de méga-octets de mémoire principale.
Idéalement, la séquence devrait être quelque chose comme IEnumerable<int>
et il renvoie paresseusement le numéro suivant dans la séquence, uniquement lorsque cela est demandé.
J'ai fait quelques recherches rapides et j'ai trouvé quelques solutions partielles à ce problème :
- Utilisation d'un registre à décalage à rétroaction linéaire maximale - si j'ai bien compris, il ne génère que les numéros en séquence croissante et ne couvre pas la totalité de la plage.
- Utilisation de la Fisher-Yates ou d'autres algorithmes de brassage sur les collections - cela violerait les restrictions de mémoire étant donné la grande gamme
-
Maintenir une collection de type ensemble et continuer à générer un entier aléatoire (en utilisant peut-être
Random
) jusqu'à ce qu'il ne se répète plus, c'est-à-dire qu'il ne fasse plus partie de l'ensemble - en plus de ne pas satisfaire aux exigences de mémoire, cela deviendrait ridiculement lent lors de la génération des derniers chiffres de la séquence. - Permutations aléatoires sur 32 bits, mais je n'arrive pas à trouver un moyen de garantir la non-répétitivité.
Existe-t-il une autre façon d'aborder ce problème - peut-être en tirant parti de la plage fixe de valeurs - qui permettrait de satisfaire aux exigences en matière de mémoire ? Peut-être les bibliothèques de classes .NET contiennent-elles quelque chose d'utile ?
MISE À JOUR 1
Merci à tous pour vos idées et vos suggestions créatives pour trouver une solution. J'essaierai d'implémenter et de tester bientôt (à la fois pour l'exactitude et l'efficacité de la mémoire) les 2 ou 3 solutions les plus prometteuses proposées ici, de publier les résultats et de choisir un "gagnant".
MISE À JOUR 2
J'ai essayé de mettre en œuvre la suggestion de hvd dans la section commentaire ci-dessous . J'ai essayé d'utiliser à la fois le BitArray
dans .NET et mon implémentation personnalisée, puisque celle de .NET est limitée à int.MaxValue
entrées, donc pas assez pour couvrir toute la gamme des nombres entiers.
J'aimais la simplicité de l'idée et j'étais prêt à "sacrifier" ces 512 Mo de mémoire si cela fonctionnait bien. Malheureusement, le temps d'exécution est assez lent, passant jusqu'à des dizaines de secondes pour générer le prochain nombre aléatoire sur ma machine, qui a un CPU Core i7 de 3,5 GHz. Donc, malheureusement, ce n'est pas acceptable si vous demandez à ce que de très nombreux nombres aléatoires soient générés. Je suppose que c'est prévisible cependant, c'est un algorithme O(M x N) si je ne me trompe pas, où N est 2^32 et M est le nombre d'entiers demandés, donc toutes ces itérations prennent leur péage.
Idéalement, j'aimerais générer le prochain nombre aléatoire en O(1) temps, tout en respectant les exigences de mémoire, peut-être que les prochains algorithmes suggérés ici sont adaptés pour cela. Je les essaierai dès que je le pourrai.
MISE À JOUR 3
Je viens de tester le Générateur linéaire de congruence et je peux dire que je suis assez satisfait des résultats. Il s'agit d'un candidat solide pour la position de gagnant dans ce fil.
Correctness tous les entiers sont générés exactement une fois (j'ai utilisé un vecteur de bits pour vérifier cela).
Randomness : assez bon.
Utilisation de la mémoire : Excellent, juste quelques octets.
Temps de fonctionnement : Génère le prochain entier aléatoire très rapidement, comme vous pouvez l'attendre d'un algorithme O(1). La génération de chaque entier a pris un total d'environ 11 secondes sur ma machine.
Dans l'ensemble, je dirais que c'est une technique très appropriée si vous ne recherchez pas des séquences hautement aléatoires.
UPDATE 4
Le système modulaire technique inverse multiplicative décrite ci-dessous se comporte de manière assez similaire à la technique LCG - ce qui n'est pas surprenant, puisque les deux sont basées sur l'arithmétique modulaire -, bien que j'ai trouvé qu'elle était un peu moins simple à mettre en œuvre pour produire des séquences aléatoires satisfaisantes.
Une différence intéressante que j'ai trouvée est que cette technique semble plus rapide que LCG : générer la séquence entière a pris environ 8 secondes, contre 11 secondes pour LCG. A part cela, toutes les autres remarques concernant l'efficacité de la mémoire, la correction et le caractère aléatoire sont les mêmes.
MISE À JOUR 5
On dirait que l'utilisateur TomTom a supprimé sa réponse avec le Mersenne Twister sans préavis après que j'ai signalé dans un commentaire que j'avais découvert qu'il générait des numéros en double plus tôt que prévu. Donc je suppose que cela exclut complètement le Mersenne Twister.
MISE À JOUR 6
J'ai testé une autre technique suggérée qui semble prometteuse, Skip32 et, bien que la qualité des nombres aléatoires m'ait beaucoup plu, l'algorithme ne permet pas de générer toute la gamme des nombres entiers en un temps acceptable. Malheureusement, il n'est pas à la hauteur des autres techniques qui ont permis d'achever le processus. J'ai utilisé l'implémentation en C# de aquí J'ai modifié le code pour réduire le nombre de tours à 1, mais il ne peut toujours pas se terminer dans les temps.
Après tout, si l'on en juge par les résultats décrits ci-dessus, mon choix personnel pour la solution va aux inverses multiplicatifs modulaires suivie de très près par la technique générateur de congruence linéaire . Certains diront peut-être que cette technique est inférieure à d'autres sur certains points, mais compte tenu de mes contraintes initiales, je dirais qu'elle est la plus adaptée.
5 votes
Je pense que vous avez la réponse à votre question, mais que vous ne la voyez pas. Maintenir une collection de type ensemble est possible avec un tableau de bits. Cela nécessite 2**32 bits, soit 512 Mo, ce qui semble un peu élevé, mais reste dans les limites de votre exigence de quelques centaines de méga-octets. La génération d'un nombre aléatoire qui n'est pas dans cet ensemble n'a pas besoin d'être un essai et une erreur : si vous avez N nombres restants, générez un nombre aléatoire de 0 à N-1, puis choisissez le N-ième nombre restant.
0 votes
@hvd : Je savais que cela prendrait moins d'espace que les tableaux/collections d'entiers, mais je n'avais pas pensé à la façon dont je pourrais remplir ce tableau, merci pour cette idée ! Quant à la limite inférieure de 512 Mo, je dirais qu'elle est encore discutable - pensez aux anciens PC, comme ceux des années 90, où une telle quantité de mémoire était un luxe.
0 votes
@Bjørn-RogerKringsjå : oui, tous, dans toute la gamme.
1 votes
Cela semble être un cas particulier de cette question : stackoverflow.com/questions/10054732/
0 votes
@usr : Vous avez raison, il s'agit essentiellement de la même question mais posée d'une manière plus mathématique ; avez-vous finalement réussi à trier les détails et à l'implémenter ? Je suppose que vous avez essayé la technique LFSR.
0 votes
Je n'ai rien fait de tout ça. Si je me souviens bien, j'ai opté pour le "fisher yates shuffle" et j'ai accepté le gaspillage de mémoire parce que ce n'était pas du code de production.
0 votes
Doit-il être aléatoire à chaque fois, ou le même résultat qui satisfait à vos conditions à chaque fois est-il autorisé ?
0 votes
@WaiHaLee : Nous ne parlons pas d'une situation de sécurité/crypto ici, donc je dirais que le même résultat aléatoire est autorisé à chaque fois s'il satisfait mieux les exigences de la mémoire. Mais idéalement, cela devrait donner une nouvelle séquence aléatoire à chaque fois.
0 votes
@GabrielS. - si vous ne parlez pas de sécurité/crypto, autoriseriez-vous la possibilité de calculer de façon déterministe un nombre à partir des valeurs précédentes ?
0 votes
@WaiHaLee : Je dirais oui, tant que le résultat final n'est pas une séquence de nombres manifestement croissante ou décroissante (voir ma remarque sur le registre à décalage).
0 votes
Que signifie RANDOM ? Aléatoire selon quelle norme ? Pseudeo-Random - implémentez ou téléchargez un mersenne-twister. C'est suffisant pour 99,9% des applications.
0 votes
@TomTom : Le pseudo-aléatoire est très bien ; je ne veux simplement pas de relation évidente entre les nombres, pas de séquences ordonnées, etc.
0 votes
@GabrielS. Voir ma réponse - un mersenne twister fonctionne parfaitement tant que vous ne demandez pas trop de nombres (pour 8 bits, il se répétera après 255 ou 256 nombres - parce qu'il le doit). Il est petit et efficace.
0 votes
@GabrielS. - une dernière chose - autoriserez-vous la répétition (de la séquence entière ) une fois que tous les numéros sont épuisés ?
0 votes
@WaiHaLee : peut-être que je ne comprends pas bien votre question, mais cela ne voudrait-il pas dire que les nombres aléatoires générés sont répétés ? Ce qui violerait mon exigence de non-répétition.
0 votes
@GabrielS. - Ce que je veux dire, c'est qu'il y a 2^32 ~= 4x10^9 valeurs distinctes. Une fois que toutes ces valeurs ont été retournées, vous doit par définition, commencent à renvoyer des doublons. Êtes-vous heureux que la séquence se répète avec une période de 2^32 ?
0 votes
@WaiHaLee : Oui, bien sûr, j'ai d'abord pensé que vous parlez d'une période beaucoup plus basse et que les doublons commencent à apparaître plus tôt qu'ils ne le devraient.
0 votes
Question connexe : Comment obfusquer un nombre entier ?
0 votes
"cela deviendrait ridiculement lent lors de la génération des derniers nombres de la séquence". Bien que cela soit vrai, le temps d'exécution total n'est toujours que de 10 minutes.
n log(n)
. Le suivi des doublons à l'aide d'un champ de bits nécessiterait 2^32 bits = 512 MiB, ce qui pourrait ne pas répondre à vos exigences en matière de mémoire.