Mathématiques avec Python et Ruby/Nombres pseudo-aléatoires en Ruby

Nombres pseudo-aléatoires modifier

Le traditionnel nombre pseudo-aléatoire compris entre 0 et 1 s'obtient avec

puts(rand)

Si on veut un nombre entier aléatoire, on peut mettre le nombre d’occurrences possibles entre parenthèses après le rand. Par exemple, pour lancer un dé à 6 faces, on obtient avec rand(6) un nombre entre 0 et 5. Donc pour lancer un dé, on fait

puts(rand(6).succ)
 

Pour simuler une variable aléatoire binomiale de paramètres n et p, on peut utiliser le fait que celle-ci est la somme de n variables de Bernoulli indépendantes entre elles. Façon Ruby, cela peut se faire avec l'algorithme suivant (basé sur une analogie avec un jeu de pile ou face, où p est la probabilité d'avoir pile et n le nombre de lancers de la pièce):

  1. On crée un ensemble de n objets, par exemple une liste de nombres (1..n);
  2. On extrait de celle-ci les nombres victorieux (ceux pour lesquels une pièce est tombée sur pile);
  3. On compte les objets retenus.

En Ruby cela donne

def binomial(n,p)
    return ((1..n).select { |i| rand<p }).size
end


10.times do puts(binomial(8,0.2)) end

Lancer de dés modifier

Un dé modifier

Pour voir si le dé est équilibré, on peut le lancer quelques milliers de fois et compter combien de fois chaque face est sortie... ou laisser faire le travail par Ruby:

effectifs=[0]*6
n=6000
n.times do effectifs[rand(6)]+=1 end
puts(effectifs)

Deux dés modifier

Pour lancer deux dés et additionner leurs résultats, on fait comme ci-dessus et on additionne. Seulement le tableau des effectifs est indexé de 0 à 10 (2 de moins que les résultats des lancers):

effectifs=[0]*11
n=6000
n.times do effectifs[rand(6)+rand(6)]+=1 end
puts(effectifs)

Avec des cartes modifier

Tirer une carte au hasard modifier

Puisque le jeu de cartes est un tableau, il suffit de choisir un indice au hasard pour tirer une carte au hasard. Par prudence, on va faire semblant de ne pas savoir qu'il y a 32 cartes dans le jeu (et qu'elles sont numérotées de 0 à 31). On va tirer 3200 fois une carte d'un jeu de 32 et compter le nombre de fois qu'on a eu un as de pique. Pour cela on va utiliser un compteur du nombre de victoires (gains) et on va lui injecter une unité chaque fois que le nom de la carte est 1 pique:

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
valeurs=valeurs.collect { |v| v.to_s}
couleurs=['carreau','cœur','pique','trèfle']
univers=[]
valeurs.collect{|v| couleurs.collect{|c| univers.push(v+' '+c)}}

n=3200
gains=(1..n).inject {|g,i| g+(univers[rand(univers.size)]=='1 pique' ? 1 : 0) }
puts(gains.to_f/n)

Tirer 5 cartes au hasard modifier

Pour constituer une main de 5 cartes, il suffit a priori de faire 5 fois l'opération précédente:

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
valeurs=valeurs.collect { |v| v.to_s}
couleurs=['carreau','cœur','pique','trèfle']
univers=[]
valeurs.collect{|v| couleurs.collect{|c| univers.push(v+' '+c)}}

hand=[]
((1..5).to_a).collect{hand.push(univers[rand(univers.size)])}
puts(hand)

Seulement il peut arriver qu'on ait deux fois l'as de pique dans la même main ! En effet le script précédent réalise un tirage avec remise, pour lequel les calculs de probabilités sont plus faciles, mais irréaliste pour les jeux de cartes et le Loto. Ce dont on a besoin dans le cas présent, c'est d'un tirage sans remise, qui se produira en enlevant les cartes au fur et à mesure qu'on les met dans la main de 5 cartes.

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
valeurs=valeurs.collect { |v| v.to_s}
couleurs=['carreau','cœur','pique','trèfle']
univers=[]
valeurs.collect{|v| couleurs.collect{|c| univers.push(v+' '+c)}}

hand=[]
while hand.size<5
    jeu=univers-hand
    carte=jeu[rand(jeu.size)]
    hand.push(carte)
end

puts(hand)

Le jeu dont on a extrait les cartes est une variable locale, et à la fin de ce script il y a toujours 32 cartes dans l'univers.

Ensuite on peut compter les carrés d'as parmi 10 000 parties:

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
valeurs=valeurs.collect { |v| v.to_s}
couleurs=['carreau','cœur','pique','trèfle']
univers=[]
valeurs.collect{|v| couleurs.collect{|c| univers.push(v+' '+c)}}
 
carres=0
10000.times do
    hand=[]
    while hand.size<5
        jeu=univers-hand
        carte=jeu[rand(jeu.size)]
        hand.push(carte)
    end
    if (hand.select {|c| c[0..1]=='1 '}).size==4
        carres+=1
    end
end
 
puts(carres.to_f/10000)

On construit une liste avec les cartes de hand dont le nom commence par un 1 suivi d'un espace (les as), et lorsque la longueur de cette liste est égale à 4, on incrémente le compteur carres. À la fin de l'exécution du script, on a donc le nombre de carrés d'as parmi les 10 000 tirages, et en le divisant par 10 000, on a la fréquence de ces carrés d'as. Elle est très faible !

Mélanger un jeu de cartes modifier

En 1741, dans les Mémoires de l'Académie des Sciences de Berlin, Leonhard Euler publiait un article titré Calcul de la probabilité du jeu de rencontre. Le nom initial du jeu de rencontre était jeu de treize parce qu'il se jouait à 13 cartes. Mais Euler généralise le jeu à n cartes.

Il le définit ainsi:

Le jeu de rencontre est un jeu de hasard, où deux personnes ayant chacune un entier jeu de cartes, en tirent à la fois une carte après l'autre, jusqu'à ce qu'il arrive, qu'elles rencontrent la même carte: et alors l'une des deux personnes gagne. Or, lorsqu'une telle rencontre n'arrive point du tout, alors c'est l'autre des deux personnes qui gagne. Cela posé, on demande la probabilité, que l'une et l'autre de ces deux personnes aura de gagner.

Dans cet excellent article (16 pages en Français),

Euler montre que

Pourvu donc que le nombre de cartes ne soit pas moindre que 12, l'espérance de A sera toujours à celle de B à peu près comme 12 à 7 ... Ou bien parmi 19 jeux qu'on joue, il y en aura probablement 12 qui font gagner A, et 7 qui feront gagner B.

Pour mélanger un jeu de cartes, on peut construire une main de 32 cartes ! Ensuite on peut répéter l'expérience 1900 fois, et compter combien de fois il y a eu au moins une rencontre (le nombre de rencontres strictement positif), et enfin, par division par 1900, estimer la fréquence de ces rencontres, et la comparer avec le quotient de 12 par 19 donné par Euler:

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
valeurs=valeurs.collect { |v| v.to_s}
couleurs=['carreau','cœur','pique','trèfle']
univers=[]
valeurs.collect{|v| couleurs.collect{|c| univers.push(v+' '+c)}}
 
gains=0
1900.times do
    hand=[]
    while hand.size<32
        jeu=univers-hand
        carte=jeu[rand(jeu.size)]
        hand.push(carte)
    end
    rencontres=((0..31).to_a).select { |i| hand[i]==univers[i] }
    if rencontres.size>0
        gains+=1
    end
end
 
puts(gains.to_f/1900)
puts(12.0/19)

Un exemple de résultat sur 1900 jeux:

0.634736842105263

0.631578947368421

La valeur exacte de la probabilité d'une rencontre est donnée par Euler, dont la formule se traduit en Ruby par

a=(1..32).inject {|p,n| p+(-1)**n/(1..n).inject(1) {|f,k| f*k}}
puts(1-1/a)

et qui donne la valeur  ...

Méthode de Monte-Carlo modifier

Pour calculer la valeur approchée de   par la méthode de Monte-Carlo, on crée un nuage de points à coordonnées pseudo-aléatoires, et on compte combien d'entre eux sont à moins d'une unité de distance de l'origine du repère:

points=(1..1000000).inject{|p,n| p+(Math.hypot(rand,rand)<1? 1 : 0) }
puts(points.to_f/1000000*4)

Le résultat est correct à trois décimales près.