Maintenant, on passe à la vitesse supérieure, on va construire une machine qui sort un nombre infini de cartes (Titi s'est vite lassé des dés) ! Pour utiliser le vocabulaire que Titi m'a appris, cela signifie que
l'univers des possibles sera infini. Vous allez voir que ce n'est pas une mince affaire.
Un problème épineux
Comme le dit Titi, "il faut réutiliser ce qu'on sait déjà faire" : dans la partie précédente, on a vu que si on connaît une loi, on a alors une méthode pour simuler une machine qui suit cette loi. Je pensais être sorti d'affaire : "Il me suffit de déterminer cette loi et le tour est joué !".
Mais si l'univers des possibles est infini, comment stockeras-tu ta loi ? Tu ne pourras pas utiliser un tableau !
Il est énervant à toujours avoir raison ce Titi ! Ma solution est de remplacer notre tableau par une gentille fonction qui prend un entier en paramètre et qui renvoie sa probabilité d'apparaître. Par exemple, j'ai eu l'idée d'utiliser le même type de loi que pour la machine équilibrée : toutes les cartes ont la même probabilité, et je choisis cette proba à 0,05 ! Ce qui nous fait une fonction du type :
Code : C | double loi(int n){
if(n>0)
return 0.05;
else
return 0; /* La numérotation des cartes commence à 1, donc
la probabilité de tirer un nombre inférieur
ou égal à 0 est nulle ! */
}
|
C'est un peu mieux ... Mais cette fonction ne représente absolument pas une loi de probabilité : en additionnant les 20 premières probabilités élémentaires, on obtient 1, et si on continue, on dépasse le allégrement ; c'est même pire que ça, vu qu'il y en a un nombre infini, la somme de toutes les probabilités sera ... infinie !
Et prendre une probabilité plus petite ne changera rien, on mettra juste "plus de temps à atteindre l'infini".
Gare à celui qui me propose d'alterner des nombres positifs et négatifs pour faire en sorte que certains termes en annulent d'autres : une probabilité négative n'a aucun sens !
J'ai alors pensé à ne mettre une probabilité que sur un nombre fini de cartes, le reste des cartes ayant une probabilité nulle. Ce qui correspondrait à :
Code : C | double loi(int n){
if(n>0 && n<=20)
return 0.05;
else
return 0;
}
|
C'est bien une loi, mais on n'a rien gagné par rapport à la première partie ! En effet, on simule ici une machine qui ne tire qu'un nombre fini de carte ... Retour à la case départ !
Au lieu de chercher à construire une loi à tâtons, on va plutôt tenter de modéliser une situation "réelle" ; on s'assurera ensuite que ce qu'on a trouvé est bien une loi.
Un jeu de pile ou face
Le jeu
Titi m'a proposé le jeu suivant : je lance une pièce jusqu'à ce que je tombe sur pile. Mon score est alors le nombre de lancers que j'ai effectués. Par exemple, si je tire face, face puis pile, mon score est de 3 ; si je tire pile du premier coup, mon score est de 1.
Bien sûr, ce qu'on va essayer de faire, c'est simuler ce jeu par une machine : on lance la machine, elle calcule notre score puis l'inscrit sur une carte.
L'algorithme le plus naïf serait de tirer au hasard 0 (pour pile) ou 1 (pour face), compter le nombre de fois qu'on le fait et de s'arrêter lorsqu'on tire un face. De façon plus concise, cela donne :
Code : Autre1
2
3
4
5
6
| compteur=0
faire
nombre = tirer 0 ou 1 au hasard
compteur ++
tant que nombre = 0
afficher compteur |
Mais je me suis vite rendu compte que cela n'allait pas du tout : on tire potentiellement pleins de nombres aléatoires, ce qui n'est pas du tout économique ! Cherchons maintenant à trouver la loi de cette machine pour pouvoir appliquer l'algo de la première partie, et être plus efficace.
La probabilité que je tire pile du premier coup est de 1/2 (Titi n'est pas encore assez âgé pour avoir la fourberie de me donner une pièce déséquilibrée

). On connaît donc déjà une probabilité élémentaire : P({1})=1/2 (c'est la traduction de la phrase d'avant : la probabilité que mon score soit 1 est de 1/2).
Pour que je tire pile au deuxième coup (sans l'avoir fait au premier), il faut que je tire:
- face au premier coup, ce qui a une probabilité de 1/2;
- ET pile au deuxième, ce qui a aussi une probabilité de 1/2.
Croyez moi sur parole, nous avons : P({face au premier lancer} ET {pile au second lancer}) = P({face au premier lancer})*P({pile au second}) = 1/2 * 1/2 = 1/4.
Et donc P({2}) = 1/2² = 1/4. De la même manière, vous pouvez trouver que P({3}) = 1/2 * 1/2 * 1/2 = 1/2
3=1/8, P({4})=1/2
4, ... On peut alors généraliser : P({n}) = 1/2
n. Nous connaissons donc bien notre loi vu que pour chaque événement élémentaire, on a une probabilité.
Mais avons nous bien une loi ? Autrement dit, la somme de toutes les probabilités élémentaires fait-elle bien 1 ?
La loi du gâteau idéal
Déjà, quel est l'univers des possibles ? Le score du jeu peut être 1, 2, 3, 42,512, ... en fait, n'importe quel entier positif non nul. Donc on a bien un univers infini.
Et on veut vérifier que la somme des probabilités élémentaires soit égale à 1. Autrement dit que :
Vous remarquerez que j'ai utilisé cette fois "..." sans rien mettre derrière ; cela signifie qu'on continue la somme "à l'infini". Si j'avais mis "0.1+ 0.2 +0.3 +... + 0.9" cela signifierait que j'ai une somme finie.
Pour m'expliquer ce problème, Titi m'a raconté ce qu'il a fait à son dernier anniversaire. Comme à tous les anniversaires, il y avait un beau gros gâteau, comme on les aime de par chez nous, un gâteau "idéal" : on peut en couper des parts aussi fines qu'on veut, sans en perdre une miette.
Et Titi a beaucoup de chance, il a beaucoup d'amis. Quand je dis beaucoup, ce n'est pas 10, 100 ou 100 000. Non, beaucoup. Une infinité !
Et comme Titi est à moitié généreux, à chaque nouvel arrivant, il offrait la moitié restante du gâteau. Donc pour le premier invité, le gâteau était entier, il a donné la moitié du gâteau ; donc 1/2. Il restait alors la moitié du gâteau ; le deuxième invité a donc reçu la moitié de la moitié, autrement dit un quart (1/4). Et ainsi de suite, le troisième a reçu 1/8, le quatrième 1/16 ...
La question est : quand il aura servi tous ses amis, restera-t-il du gâteau pour Titi ?
Pour déjà faire le lien avec notre problème, on peut remarquer que la quantité de gâteau donnée par Titi correspond à la somme qui nous intéresse.
On remarque immédiatement que cette somme ne dépassera jamais 1 : Titi ne peut pas donner plus de gâteau que le gâteau lui même ! Et en regardant les images ci-dessus, on voit bien qu'à la fin, Titi aura distribué tout le gâteau et qu'il n'en aura pas ; pas de chance, il fallait se servir avant ! Tout ça pour dire que la somme qui nous intéresse fait bien 1.
Pourquoi n'a-t'il pas la moitié de la dernière part ?
Il y a une infinité d'ami... Du coup, pas de dernière part !
Nous sommes maintenant sûr d'avoir affaire à une loi de probabilité, on peut maintenant essayer de la simuler !
Une machine rapide !
Maintenant qu'on a une loi, on peut réadapter le programme qu'on avait fait pour simuler ce jeu dans la première partie. Je vous rappelle la méthode : on tire un nombre x compris entre 0 et 1, puis on regarde dans quelle "zone" il se situe. Ici le découpage des zones se fait ainsi :
Pour "regarder dans quelle zone x se situe", je vous rappelle (n'hésitez pas à relire la première partie) qu'en gros, on teste si x est dans la zone "1", puis s'il est dans la zone "2", et ainsi de suite.
Pour appliquer le même algo, il faut alors définir une fonction qui représentera notre loi :
Code : C | #include <math.h>
double loi(int n){
if(n<=0) return 0;
else return pow(0.5,n); // = (1/2)^n
}
|
Ce qui au final donnerait un programme de ce type :
Secret (cliquez pour afficher)
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 | #include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
/* Renvoie un nombre aléatoire entre 0 et 1 */
double rand_0_1(){ return rand()/(double)RAND_MAX; }
double loi(int n){
if(n<=0) return 0;
else return pow(0.5,n); // = (1/2)^n
}
/*Génère un entier suivant la loi modélisée par la fonction "loi" */
int alea_perso(){
int i=0;
double x=rand_0_1();
double somme=0;
do{
somme += loi(i);
i++;
}while( somme < x );
return i-1;
}
int main(){
int nb_tirages=0;
int i=0;
int carte=0;
srand(time(NULL));
do{
printf("Combien de tirages voulez-vous effectuer ? ");
scanf("%d",&nb_tirages);
}while(nb_tirages <= 0);
printf("Voici la liste des tirages : \n");
for(i=0; i<nb_tirages;i++){
carte=alea_perso();
printf("%d ",carte);
}
printf("\n");
return 0;
}
|
Mais en fait, ce n'est pas beaucoup mieux que la première version. Je vous rappelle que dans la première version, on tirait un nombre 0 ou 1 tant que ce nombre n'était pas 1 ; donc si le score valait 5, on avait donc 5 "tours de boucles". Ici, si on regarde de la même façon le "nombre de tours", on se rend compte très vite qu'il est aussi égal au score ; donc niveau efficacité, c'est la loose

.
On va donc essayer d'être plus efficace : on va réfléchir pour trouver directement dans quelle zone se situe x sans faire aucune boucle. Autrement dit, on va chercher une formule mathématique qui nous donnera la zone directement à partir de x.
Pour certaines valeurs, c'est très facile de dire "à l'œil nu" dans quelle région est x. Par exemple, si x=0.2, il est clairement dans la région "1" ; si x=0.6, il est dans la région "2". Mais si x=0.9876, vous serez bien en peine de me dire - sans calculs ! - dans quelle région est x ! C'est cette "recherche de zone" que l'on va automatiser dans ce qui suit.
La partie qui suit, même si expliquée en détail, est relativement technique ; savoir ce qu'est une inéquation est fortement recommandé. Si vous voyez que vous ne comprenez rien à mon charabia, sautez à la formule finale sans états d'âmes.
Pour bien comprendre, mettons que x est dans la zone "3". Regardez le schéma au-dessus, cela veut dire que
x est compris entre P({1})+P({2}) et P({1})+P({2})+P({3}). Autrement dit :
On connaît très bien P({1}),P({2}) et P({3}), on peut donc écrire :
Mais la zone dans laquelle est x, c'est pas ce qu'on cherche à trouver ? On se mord la queue là, non ?
Oui, c'était un exemple pour bien comprendre la formule que je vais vous balancer. Maintenant, mettons que x est dans la zone "n" (que nous ne connaissons pas); on peut donc écrire :

.
Nous avons donc une superbe inéquation, et le but du jeu,
c'est de trouver n (nous connaissons déjà la valeur de x)

!
Dans un premier temps, nous allons trouver une forme plus sympathique aux grosses sommes de part et d'autre des inégalités. Vous remarquerez que :
...
Donc notre grosse inégalité se re-écrit :

. Ce qui me fait déjà beaucoup moins peur. En enlevant 1 à chaque bout, puis en multipliant par (-1) on a (multiplier par un nombre négatif change le sens d'une inégalité) :
Gloups ... et comment je fais "sauter" la puissance ?
Alors pour faire ça, rappelez vous de la façon dont vous faites "sauter" un carré : vous prenez la racine carré !
Mais qu'est-ce que la racine carré ? Vous connaissez surement très bien la fonction "carré" f(x)=x² qui a pour effet de multiplier un nombre par lui même. Eh bien la racine carré est la fonction qui fait le boulot inverse de la fonction carré.
Dans notre cas, nous avons la fonction "puissance de 1/2"

; eh bien il existe une fonction qui fait le boulot inverse la fonction "puissance de 1/2". Cette fonction s'appelle le
logarithme de base 1/2 (évidement, si on considère la fonction "puissance de a", on aura affaire au "logarithme de base a"). Je le noterai log
1/2. Ainsi, on aura :
Vous pouvez remarquer que log1/2 est décroissante, donc quand je l'applique, les inégalités "changent de sens".
Revenons à notre calcul, j'applique le log
1/2 
:
Donc il n'y a plus qu'une seule possibilité pour n : c'est l'entier qui est juste au-dessus (fonction
ceil en C) de log
1/2(1-x).
Une dernière petite optimisation (c'est vraiment du bout de chandelles ...) : x est un nombre aléatoire compris entre 0 et 1 ... c'est aussi le cas de 1-x, donc dans l'argument du log
1/2, on peut se contenter de prendre x au lieu de 1-x.
Dernière note technique : pour calculer log
1/2(x) concrètement en C, vous devrez faire le calcul suivant :
log(x)/log(0.5) (en ayant inclus
math.h évidemment).
Pfiou, j'ai enfin fini ! Bah ... pourquoi tout le monde est parti ?
La formule finale
Tout ceci nous mène à ce superbe résultat ; pour savoir dans quelle zone se situe x, il suffit de faire le calcul :
ceil( log(x) / log(0.5) );
Cela correspond au score que j'ai obtenu au jeu de pile ou face. Il ne me reste plus qu'à l'écrire sur une carte !
Tout ce patacaisse pour une pauvre petite malheureuse formule ? Mais qu'est-ce qu'on a gagné ?
Vous avez gagné sur plusieurs points :
- efficacité du code : le calcul du log est très rapide, optimisé par des grosses brutes des mathématiques, beaucoup plus efficace que notre boucle !
- simplicité du code : nous n'avons plus besoin de définir une fonction "loi" auxiliaire, la simulation du score se fait en 1 ligne (voir code plus loin).
- vous avez appris quelques techniques mathématiques qui vous resserviront peut-être
.
Pour simuler le score à ce jeu, il suffit de faire une fonction ressemblant à (je vous laisse faire le main qui va autour) :
Code : C | int score_pile_face(){
double x=rand_0_1();
int score=ceil(log(x)/log(0.5));
return score;
}
|
Que l'on peut condenser en un ligne
return ceil(log(rand_0_1())/log(0.5))
. Le simple appel à cette fonction renverra le score qu'on obtient en jouant à ce jeu ; l'entier qu'elle renverra pour donc ne pas être toujours le même !
Des variantes
Titi est content, il a sa machine qui lui permet de simuler un jeu infini de cartes ; mais nous pouvons encore trouver d'autres lois. Par exemple, nous pouvons prendre une pièce "déséquilibrée" qui tombe sur face avec une probabilité de 0,7. Vous pouvez tenter de refaire les calculs ci-dessus pour obtenir une formule pour cette nouvelle machine.
Nous pouvons aussi modifier un peu le jeu : on tire 100 fois à pile ou face, et notre score est le nombre de "piles" que nous avons obtenus.
On pourrait faire le même genre d'étude que juste au-dessus, mais malheureusement, nous ne pourrons pas nous servir du logarithme, ni d'aucune fonction "simple" pour inverser l'expression que nous allons trouver (Titi a tenté de m'expliquer le calcul, je n'y ai rien compris ...). Le plus efficace est alors d'appliquer un algo "naïf" :
Code : Autre1
2
3
4
5
6
| compteur=0
faire 100 fois
nombre = tirer 0 ou 1 au hasard
si nombre = 0
compteur ++
afficher compteur |
Je l'ai répété assez de fois, pour avoir une loi, il suffit que la somme des probabilités élémentaires soit égale à 1 ; mais, vu qu'il y a une infinité de termes, cela force chacun de ces termes à devenir de plus en plus petit.
Pour construire une loi, vous pouvez donc essayer de trouver une suite (infinie) de nombres qui tend vers 0 et faire en sorte que leur somme vaille 1 ; le problème n'est pas évident car si les nombres ne tendent pas "assez vite" vers 0, la somme explose et tend vers l'infini ! Mais comme je suis gentil, je ferai un petit article à ce sujet dans pas longtemps...
