Je suis en train d'implémenter l'algorithme d'Eller } en C++ et un petit détail me dérange au sujet du caractère aléatoire du labyrinthe.
Jusqu'à présent, j'ai utilisé le code suivant pour générer une bool
aléatoire:
bool randomBool()
{
return 0 + (Rand() % (1 - 0 + 1)) == 1;
}
// In main.cpp
time_t seconds;
time(&seconds);
srand((unsigned int) seconds);
Mais lors du débogage, je vois souvent des répétitions répétées de true
ou false
, parfois jusqu'à 30 fois de suite.
Cet algorithme est-il vraiment aléatoire ou existe-t-il une meilleure méthode en C++?
La STL dans C++ 11 intègre des méthodes de génération de nombres aléatoires supérieures à Rand()
. Vous pouvez simuler un booléen aléatoire via un entier aléatoire égal à 0 ou 1:
#include <iostream>
#include <random>
int main(int argc, char *argv[]) {
auto gen = std::bind(std::uniform_int_distribution<>(0,1),std::default_random_engine());
const unsigned int N = 100;
unsigned int numTrue = 0;
unsigned int numFalse = 0;
for (int i = 0; i < 100; ++i) {
bool b = gen();
if (b) ++ numTrue;
else ++numFalse;
}
std::cout << numTrue << " TRUE, " << numFalse << " FALSE" << std::endl;
}
Vous trouverez plus de détails sur cette bibliothèque dans les références C++ standard. Par exemple, si vous vouliez autre chose qu'un rapport 50/50 de valeurs "true" et "false", vous pouvez créer un nombre à virgule flottante aléatoire compris entre 0 et 1 et des valeurs d'appel inférieures à un seuil z true, sinon false.
Pourquoi tu vois de longues traînées, je pense
Je n'ai pas expliqué pourquoi vous obtenez 30 valeurs "true" ou "false" dans une ligne avec votre code. Bien que Rand()
ne doive plus être utilisé et que vous semblez avoir des additions et des soustractions inutiles de zéros ou de zéros dans votre code, il ne devrait pas y avoir de problème de ce type. Cependant, je réalise maintenant que le texte de votre question est ambigu. Si vous exécutez et quittez votre programme 30 fois de suite, attendez-vous à voir des valeurs répétées, même avec mon code. La plupart des générateurs de nombres aléatoires sont en réalité des générateurs de nombres pseudo-aléatoires. Chaque fois que vous exécuterez le programme, ils produiront la séquence même de nombres aléatoires; c'est important pour la cohérence des résultats. Cependant, lorsque le programme est en cours d'exécution (par exemple, en plaçant votre randomBool()
dans une boucle), vous ne devriez pas voir de traînées d'une telle longueur, car elles seraient hautement improbables.
Improbabilité des longues traînées
J'ai été surpris de recevoir des commentaires en désaccord avec mon affirmation selon laquelle une série de 30 "vrais" ou "faux" booléens aléatoires est improbable (lorsque vrai et faux sont tout aussi probables). Je me rends compte qu’un malentendu courant en matière de probabilité est que la «chance» essaie d’équilibrer les choses, et que si un tirage au sort a lieu plusieurs fois de suite, l’univers essaiera de corriger cela et de faire une queue plus longue. probable. En raison de cette incompréhension, les gens sous-estiment la probabilité d'obtenir des marques de tête et de queue, et je pense que la motivation des commentaires sur cette réponse et la question principale était de corriger cette erreur commune.
Cependant, il existe une raison réelle que de longues traînées (en particulier jusqu'à 30) sont de plus en plus improbables. En utilisant le langage des lancers de pièces aléatoires, chaque jet de pièce IID (indépendante et identiquement distribuée) n'a que 50% de chance d'être identique à la précédente. Ainsi, la probabilité d'une longue traînée diminue de manière exponentielle avec la longueur de la traînée. Pour une traînée de longueur L, la probabilité d'une traînée de toutes les têtes est de 1 sur 2 ^ L; la probabilité d'une série de l'un ou l'autre type est 2 sur 2 ^ L ou 1 sur 2 ^ (L-1). Voici du code à démontrer:
#include <iostream>
#include <random>
#include <map>
bool randomBool() {
static auto gen = std::bind(std::uniform_int_distribution<>(0,1),std::default_random_engine());
return gen();
}
int main(int argc, char *argv[]) {
const unsigned int N = 1e8;
std::map<unsigned int,unsigned int> histogram;
bool current = randomBool();
unsigned int currentLength = 1;
for (int i = 0; i < N; ++i) {
bool b = randomBool();
if (b == current) {
++currentLength;
} else {
auto it = histogram.find(currentLength);
if (it != histogram.end())
it->second += 1;
else
histogram.insert(std::make_pair(currentLength,1));
currentLength = 1;
}
current = b;
}
for (auto pair : histogram)
std::cout << "STREAK LENGTH " << pair.first << " OCCURS " << pair.second << " TIMES" << std::endl;
}
L'histogramme de sortie est:
STREAK LENGTH 1 OCCURS 25011106 TIMES
STREAK LENGTH 2 OCCURS 12503578 TIMES
STREAK LENGTH 3 OCCURS 6249056 TIMES
STREAK LENGTH 4 OCCURS 3125508 TIMES
STREAK LENGTH 5 OCCURS 1560812 TIMES
STREAK LENGTH 6 OCCURS 781206 TIMES
STREAK LENGTH 7 OCCURS 390143 TIMES
STREAK LENGTH 8 OCCURS 194748 TIMES
STREAK LENGTH 9 OCCURS 97816 TIMES
STREAK LENGTH 10 OCCURS 48685 TIMES
STREAK LENGTH 11 OCCURS 24327 TIMES
STREAK LENGTH 12 OCCURS 12176 TIMES
STREAK LENGTH 13 OCCURS 6149 TIMES
STREAK LENGTH 14 OCCURS 3028 TIMES
STREAK LENGTH 15 OCCURS 1489 TIMES
STREAK LENGTH 16 OCCURS 811 TIMES
STREAK LENGTH 17 OCCURS 383 TIMES
STREAK LENGTH 18 OCCURS 193 TIMES
STREAK LENGTH 19 OCCURS 104 TIMES
STREAK LENGTH 20 OCCURS 43 TIMES
STREAK LENGTH 21 OCCURS 20 TIMES
STREAK LENGTH 22 OCCURS 14 TIMES
STREAK LENGTH 23 OCCURS 4 TIMES
STREAK LENGTH 24 OCCURS 3 TIMES
Il est difficile de calculer le nombre attendu de traînées de longueur L sur un nombre de retournements N, car il existe de nombreuses étendues de longueur L qui se chevauchent et où une telle traînée pourrait exister. Cependant, notez que cet histogramme suit une distribution approximativement exponentielle, avec chaque entrée environ la moitié de l'entrée précédente.
La traînée maximale est de 24 [note: un bogue dans la version précédente comptait cela comme 23]. La probabilité d’une traînée de cette longueur dans une chaîne indépendante de 24 lancers est de 1 sur 2 ^ (24-1), soit environ 1 sur 8 millions. Étant donné que dans 1e8 lancers, il y a environ 1e8/24 ~ 4,3 millions de tronçons séparés, nous nous attendons à un petit nombre de stries de ce type, donc il semble à peu près correct [avec ma mise en garde ci-dessus qu'il est difficile de calculer l'attente exacte]. Une traînée de longueur 30, quant à elle, a une probabilité de 1 sur 537 millions sur tout tronçon indépendant de 30 retournements, et est beaucoup moins probable que même une traînée de longueur 24.
Les bits de poids faible des générateurs de nombres pseudo-aléatoires ont tendance à être moins aléatoires. Cela est particulièrement vrai pour la fonction intégrée Rand()
, qui est généralement implémentée sous la forme d'un LCG . Le meilleur moyen de générer une variable aléatoire bool
consiste à utiliser le bit MSB. Il s'agit en fait d'une norme distribution de Bernoulli avec une probabilité 1/2
.
#include <cmath>
#include <cstdlib>
inline bool random_bool()
{
static const int shift = static_cast<int>(std::log2(Rand_MAX));
return (Rand() >> shift) & 1;
}
C’est vraiment pseudo-aléatoire, si Rand()
est vraiment pseudo-aléatoire, bien que la distribution puisse être très légèrement non uniforme si Rand_MAX
est pair (c’est-à-dire qu’il existe un nombre pair de plus qu’un nombre impair). Mais généralement, Rand_MAX
est suffisamment important pour que la différence soit négligeable.
bool randomBool() {
return 0 + (Rand() % (1 - 0 + 1)) == 1;
}
C'est peut-être le pire moyen possible de convertir la sortie de Rand()
en un booléen. Sur de nombreuses implémentations, les bits d’ordre inférieur sont beaucoup moins aléatoires que les bits d’ordre supérieur.
Idéalement, vous utiliseriez autre chose, mais si vous devez utiliser Rand()
, essayez:
bool randomBool() {
return Rand() > (Rand_MAX / 2);
}