Apprendre C++ avec Qt : Annexe 2 Utilisation de nombres pseudo-alatoires
Il arrive assez frquemment que l'on souhaite utiliser des nombres "alatoires", tirs dans un intervalle donn. La librairie standard propose la fonction r and( ) , qui renvoie un nombre entier compris dans l'intervalle [ 0, RAND_MAX] . L'utilisation de cette fonction implique de prendre en charge plusieurs dtails qui permettent de l'adapter aux besoins rencontrs.
Pour utiliser dans vos propres projets les fonctions dcr i t es dans ce document , il vous suffit d'en copier le corps dans une fonction que vous aurez pralablement cre (il peut s'agir soit d'une fonction globale, soit d'une fonction membre d'une classe, selon vos besoins). Les oprations copier/coller tant trs mal prises en charge par Adobe Acrobat Reader, le code de ces fonctions est galement disponible sous la forme d'un fichier texte. 1 - Amorage du gnrateur Si l'on ne souhaite pas obtenir la mme squence de nombres chaque excution du programme, il faut "amorcer" le gnrateur de nombres pseudo-alatoires. Dans la librairie standard, c'est la fonction sr and( ) qui est charge de cet amorage. La fonction sr and( ) n'est pas plus capable de gnrer un nombre authentiquement alatoire que ne l'est r and( ) : pour que l'amorage effectu ne soit pas toujours le mme (et donc ne conduise pas la mme squence de nombres pseudo-alatoires chaque excution), il convient de transmettre sr and( ) un paramtre dont nous ayons bon espoir qu'il sera diffrent lors de chaque excution du programme. La solution traditionnellement adopte est d'utiliser la fonction t i me( ) , qui renvoie le nombre de secondes sparant le 1er janvier 1970, 0 heures, du moment de l'appel de la fonction 1 .
sr and( t i me( NULL) ) ; / / amor age de r and( ) l ' ai de de l ' heur e cour ant e
Les fonctions r and( ) et sr and( ) sont dclares dans le fichier stdlib.h, qui contient galement la dfinition de la constante RAND_MAX. La fonction t i me( ) est, pour sa part, dclare dans le fichier time.h. Selon le compilateur utilis et le type de projet en cours, il peut tre ncessaire de faire figurer explicitement les directives #i ncl ude appropries au dbut des fichiers dans lesquels ces fonctions et/ou cette constante sont utilises.
L'amorage du gnrateur de nombres pseudo-alatoires ne doit tre effectu qu'une seule fois au cours de l'excution du programme, faute de quoi le caractre "alatoire" des nombres renvoys par r and( ) risque de disparatre. 2 - Tirage d'une srie de valeurs Une fois le gnrateur amorc, on peut utiliser la fonction r and( ) pour obtenir un nombre Si l'on dispose d'une fonction af f i che( ) capable d'afficher l'cran des valeurs entires, l'excution du fragment de code suivant
/ / on suppose que sr and( ) a dj t appel e i nt t i r age; 1 i nt n; 2 f or ( n=0 ; n < 50 ; n = n + 1) 3 { 4 t i r age = r and( ) ; 5 af f i che( t i r age) ; 6 } 7
se traduira par l'apparition d'une srie du genre
1 Il ne s'agit donc pas simplement de l'heure, qui risque d'tre fcheusement constante si notre programme est lanc automatiquement tous les jours la mme heure... Document du 21/12/04 - Retrouvez la version la plus rcente sur http://www.up.univ-mrs.fr/wcpp C++ - Annexe 2 Nombres pseudo-alatoires 2/3
16682 795 29983 21753 15209 16146 29791 9228 13562 13890 6752 27027 12034 5265 5507 17471 4098 7880 28280 21710 1357 24124 5460 26321 6336 12207 17635 5811 20141 14705 12351 6391 24600 3274 11937 1729 25894 30365 22904 21400 978 17221 20664 20586 30434 31171 6517 24528 281 21019 3 - Contrle de la plage de valeurs admissibles La fonction r and( ) n'ayant aucun argument, il n'est pas possible de contrler directement l'intervalle des valeurs possibles pour un tirage.
i nt unI nt = r and( ) ; / / unI nt est dans l ' i nt er val l e [ 0, RAND_MAX]
Dans la plupart des cas, un traitement s'impose donc, pour ramener la valeur renvoye par r and( ) dans l'intervalle qui nous intresse 2 . La fonction suivante est capable de tirer un nombre compris entre des bornes qui lui sont communiques par passage de paramtres :
i nt unI nt Ent r e( i nt a, i nt b) 1 { 2 i nt mi ni mum= a; 3 i nt i nt er val l e = b - a; 4 i f ( a > b) 5 { 6 mi ni mum= b; 7 i nt er val l e = a - b; 8 } 9 r et ur n mi ni mum+ ( r and( ) %i nt er val l e) ; 10 } 11
Remarquez que, si r and( ) renvoie parfois RAND_MAX, la fonction unI nt Ent r e( ) ne renvoie JAMAIS une valeur gale au paramtre max, ce qui signifie que, lorsque mi n vaut 0, unI nt Ent r e( ) a exactement max valeurs diffrentes possibles.
Le fragment de code suivant est exemple d'utilisation de la fonction unI nt Ent r e( ) . Il simule 30 lancs successifs d'un d 6 faces, dont les rsultats sont stocks dans une QVal ueLi st :
/ / on suppose que sr and( ) a dj t appel e QVal ueLi st <i nt > l esVal eur s; 1 whi l e ( l esVal eur s. count ( ) < 30) 2 l esVal eur s. append( unI nt Ent r e( 1, 7) ) ; 3
L'excution de cette squence placera dans la liste une srie du genre :
2 1 3 4 4 6 4 4 6 6 3 4 2 2 4 5 1 3 3 4 2 2 5 1 6 1 4 1 6 6 4 - Tirage sans remise La fonction r and( ) n'est capable d'effectuer que des tirages indpendants les uns des autres. En d'autres termes, r and( ) fonctionne comme un d qui aurait RAND_MAX+1 faces : lors d'un lanc, la valeur obtenue lors du lanc prcdent autant de chances d'apparatre que n'importe quelle autre valeur.
Cette situation est trs diffrente de celle rencontre, par exemple, lorsqu'on distribue des cartes : la probabilit qu'un joueur reoive une carte identique une carte dj attribue un autre joueur est alors nulle. Lorsqu'un programme exige un tirage de ce type (ce qu'on appelle un tirage "sans remise"), il est ncessaire d'en assurer explicitement la gestion.
La fonction suivante stocke dans une QVal ueLi st des valeurs diffrentes les unes des autres, tires dans un intervalle dont les bornes lui sont (tout comme la QVal ueLi st et le nombre de valeur tirer) communiques par passage de paramtres :
2 Oubliez tout de suite, si vous l'avez eu, l'ide de changer la dfinition de la constante RAND_MAX. Cette constante, tout comme I NT_MAX ou CLOCKS_PER_SEC, n'est l que pour vous permettre d'crire du code indpendant des caractristiques du compilateur, et en aucun cas pour vous permettre de connatre ou de modifier les valeurs en questions. J-L Pris - 21/12/04 C++ - Annexe 2 Nombres pseudo-alatoires 3/3 bool t i r eSansRemi se( QVal ueLi st <i nt > &l i st e, unsi gned i nt nbVal , i nt a, i nt b) 1 { 2 l i st e. cl ear ( ) ; 3 i nt mi n = a; 4 i nt max = b; 5 i f ( mi n > max) 6 { 7 mi n = b; 8 max = a; 9 } 10 i f ( nbVal > max - mi n) / / mi ssi on i mpossi bl e ! 11 r et ur n f al se; 12 / / f abr i cat i on d' une ur ne cont enant l es val eur s de mi n max, dans l ' or dr e cr oi ssant i nt i ; 13 QMap <i nt , i nt > ur ne; 14 f or ( i = mi n ; i < max; ++i ) 15 ur ne[ i - mi n] = i ; 16 / / t i r age sans r emi se i nt l i mi t eTi r age = ur ne. count ( ) ; 17 f or ( i =0 ; i < nbVal ; ++i ) 18 { 19 i nt posi t i onTi r ee = unI nt Ent r e( 0, l i mi t eTi r age) ; 20 l i st e. append( ur ne[ posi t i onTi r ee] ) ; 21 ur ne[ posi t i onTi r ee] = ur ne[ - - l i mi t eTi r age] ; 22 } 23 r et ur n t r ue; / / si gnal e que t out s' est bi en pass 24 } 25
Cette fonction s'assure qu'une valeur donne ne figurera pas deux fois dans la liste en simulant la prsence d'une urne, dans laquelle les valeurs utilises seraient tires : chaque fois qu'une valeur est place dans la liste, elle est galement rendue indisponible en la remplaant dans l'urne par celle dont la position va tre rendue inaccessible lors du tirage suivant.
La fonction t i r eSansRemi se( ) peut tre utilise pour obtenir un nombre quelconque de valeurs, toutes diffrentes, tires d'un intervalle quelconque ( condition, bien entendu, que cet intervalle comporte au moins autant de valeurs qu'il en est demand).
Dans l'exemple suivant, la liste est garnie avec dix valeurs tires dans l'intervalle [0, 32[, ce qui pourrait s'apparenter tirer 10 cartes dans un jeu de 32 :
/ / on suppose que sr and( ) a dj t appel e QVal ueLi st <i nt > l esVal eur s; 1 t i r eSansRemi se( l esVal eur s, 10, 0, 32) ; 2
L'excution de cette squence placera dans la liste une srie du genre :
22 3 21 19 10 17 20 13 6 28
Remarquez que, si le nombre de valeurs demandes est gal la taille de l'intervalle de tirage, appeler la fonction t i r eSansRemi se( ) revient mlanger les valeurs plutt qu' en choisir certaines :
QVal ueLi st <i nt > val eur sDansLeDesor dr e; t i r eSansRemi se( val eur sDansLeDesor dr e, 32, 0, 32) ;