Al

Aléatoire en Julia

2 exercices

À propos de Aléatoire

De nombreux programmes ont besoin de valeurs (apparemment) aléatoires pour simuler des événements du monde réel.

Parmi les exemples courants et familiers, on trouve :

  • Un tirage à pile ou face : une valeur aléatoire parmi ('H', 'T').
  • Le lancer d'un dé : un nombre entier aléatoire de 1 à 6.
  • Le mélange d'un jeu de cartes : un ordre aléatoire d'un tableau de cartes.

Générer de véritables valeurs aléatoires avec un ordinateur est un défi technique étonnamment difficile, c'est pourquoi on désigne souvent ces résultats sous le nom de « pseudo-aléatoires ».

Important : ce concept ne couvre pas les nombres aléatoires cryptographiquement sûrs, qui représentent un défi bien plus difficile.

Toutefois, des bibliothèques bien conçues comme le module Random de la bibliothèque standard de Julia sont rapides, flexibles, et donnent des résultats largement suffisants pour la plupart des applications de modélisation, de simulation et de jeux.

Julia répartit les fonctionnalités aléatoires sur plusieurs endroits :

  • Quelques fonctions de base mais très polyvalentes dans Base, qui sont toujours disponibles.
  • Un éventail plus large d'options dans le module Random.
  • Des fonctionnalités plus spécialisées dans des paquets qu'il faut installer avant de les utiliser (et qui ne sont pas disponibles dans Exercism).

Random fait partie de la bibliothèque standard et est probablement préinstallé, mais tu devras ajouter using Random en haut de ton programme pour faire entrer son contenu dans l'espace de noms.

La fonction rand()

Ce que fait cette fonction dépend des arguments que tu lui donnes. Les options sont nombreuses.

Sans argument, elle génère un nombre décimal entre 0 (inclus) et 1. Il s'agit d'une distribution uniform où toutes les valeurs ont la même probabilité, comme nous le verrons dans la section « Travailler avec les distributions », ci-dessous.

Un seul argument entier génère un vecteur de cette longueur.

julia> rand()
0.10261774967264703

julia> rand(5)
5-element Vector{Float64}:
 0.24134501977563894
 0.5664193284851202
 0.9804412082089355
 0.6229551330613335
 0.47589221741904664

Pour obtenir une autre plage, il suffit de décaler et de mettre à l'échelle le résultat de façon appropriée.

L'exemple ci-dessous utilise le broadcasting pour la soustraction, abordé dans le concept Opérations vectorielles. Le .- applique simplement cette arithmétique à chaque élément du vecteur.

# numbers between -1.0 and +1.0
julia> (rand(5) .- 0.5) * 2
5-element Vector{Float64}:
 -0.5303906759076336
  0.9635682226775855
 -0.048823697086981754
  0.465842804648374
  0.9880834344780736

Avec un type comme seul argument, rand utilise typemin et typemax comme bornes. Ce n'est probablement pas ce que tu veux !

Pour des entiers aléatoires, on peut fournir une plage, et éventuellement le nombre de valeurs à générer.

julia> rand(Int64)
-9159538335234594326 # not very useful

julia> rand(1:10, 5)
5-element Vector{Int64}:
 1
 1
 1
 4
 7

Dans l'exemple rand(1:10, 5) ci-dessus, remarque qu'il y a des valeurs répétées (par coïncidence), parce que chaque tirage est indépendant. Il s'agit d'un « tirage avec remise », dont nous parlerons plus en détail ci-dessous.

Pour des nombres à virgule flottante dans une plage, tu devras généralement préciser un pas. Sinon, le pas vaudra 1.0 par défaut, ce qui est rarement utile.

julia> rand(2.4:0.01:3.2, 4)
4-element Vector{Float64}:
 3.19
 2.53
 3.14
 3.13

Sinon, fournis un tableau ou un tuple, et rand renverra une entrée aléatoire :

julia> rand([4, 9, 16, 25])
16

# coin flip
julia> rand(['H', 'T'])
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)

# mixed types in tuple
julia> rand( (1, 3.2, "name"), 2 )
2-element Vector{Any}:
 1
  "name"

Tirage avec ou sans remise

Imagine que l'on a un sac contenant 3 boules rouges et 4 boules vertes, et que l'on tire une boule au hasard dans le sac. Pour obtenir une deuxième boule, il y a deux possibilités :

  1. Remettre la première boule dans le sac et bien secouer le tout avant d'en tirer une autre. Le nombre de boules est alors le même qu'avant (7), et le rapport entre rouges et vertes est lui aussi identique.
  2. Poser la première boule sur la table avant d'en tirer une deuxième. Il ne reste alors que 6 boules dans le sac, et le rapport rouge:vert dépend de la couleur de la première boule.

Le scénario 1 se fait avec remise, le scénario 2 sans remise, et ils donnent des résultats différents.

Pour simuler un tirage sans remise en Julia, il y a quelques options.

Le plus simple (et, dans Exercism, la seule option) : utiliser Random.shuffle() pour mettre les entrées dans un ordre aléatoire, puis prendre les n premiers éléments. C'est très bien pour de petits problèmes, mais cela passe mal à l'échelle sur de grandes collections : shuffle doit générer le tableau entier, même si tu n'en veux qu'une petite fraction.

Pour faire un tirage avec remise « correctement », installe le paquet StatsBase.jl. Celui-ci fournit la fonction sample() avec tout un éventail d'options.

On peut raisonnablement espérer que des fonctionnalités similaires seront intégrées à Random dans une future version, pour faire partie de la bibliothèque standard (les exemples de code de ce document ont été testés avec Julia 1.11).

Travailler avec les distributions

Jusqu'ici, on s'est concentré sur les cas où tous les résultats ont la même probabilité. Par exemple, rand(1:100) a autant de chances de donner n'importe quel entier de 1 à 100.

De nombreuses situations du monde réel sont bien moins simples que cela. Par conséquent, les statisticiens ont créé une grande variété de distributions pour décrire mathématiquement des résultats « du monde réel ».

Distributions uniformes

La fonction rand() décrite ci-dessus s'utilise quand toutes les probabilités sont égales. On parle alors d'une distribution [uniform][uniform-distribution].

Distribution gaussienne

Appelée aussi distribution « normale » ou « courbe en cloche », c'est une façon très courante de décrire l'imprécision de valeurs mesurées.

Par exemple, suppose que l'usine où tu travailles vient d'acheter 10 000 boulons censés être identiques. Tu veux programmer le robot de l'usine pour qu'il les manipule, alors tu pèses un échantillon de 100 boulons et tu constates qu'ils ont un poids moyen (ou mean) de 4,731 g. Il est extrêmement improbable que cela signifie qu'ils pèsent tous exactement 4,731 g. Peut-être constates-tu que les valeurs vont de 4,627 à 4,794 g mais se regroupent autour de 4,731 g.

Il s'agit de la Gaussian distribution, pour laquelle les probabilités culminent à la moyenne et diminuent symétriquement de part et d'autre (d'où la « courbe en cloche »). Pour simuler cela dans un logiciel, il faut un moyen de préciser la largeur de la courbe (en général, des boulons chers se regroupent plus étroitement autour de la moyenne que des boulons bon marché !).

Par convention, on utilise pour cela l'standard deviation : des valeurs petites pour une courbe pointue et étroite, des valeurs grandes pour une courbe basse et large. Les mathématiciens adorent les lettres grecques, donc on utilise μ (« mu ») pour représenter la moyenne et σ (« sigma ») pour représenter l'écart type. Ainsi, si tu lis que « 95 % des valeurs se situent à moins de 2σ de μ » ou que « le boson de Higgs a été détecté avec une confiance de 5 sigma », ce genre de remarques se rapportent à l'écart type.

Nous en reparlerons dans le concept Statistics.

La fonction randn()

Abréviation de « random normal », elle est similaire à la variante à virgule flottante de rand(), sauf que les valeurs suivent une distribution gaussienne de moyenne 0 et d'écart type 1.

Là encore, tu voudras peut-être multiplier la sortie brute de randn pour régler l'écart type, et la décaler pour la moyenne. L'exemple ci-dessous la convertit en une moyenne de 30 et un écart type de 5.

julia> raw = randn(5)
5-element Vector{Float64}:
  3.0762588867281475
  1.5101100620253902
 -0.5914858221637778
  0.684175554069735
 -0.8416433926114673

julia> raw * 5 .+ 30
5-element Vector{Float64}:
 45.38129443364074
 37.55055031012695
 27.04257088918111
 33.420877770348675
 25.791783036942665

Il est difficile de voir, à la simple lecture de la sortie, que les valeurs brutes se regroupent plus près de zéro que pour une distribution uniforme. Si tu en doutes, génère 1 000 valeurs ou plus et trace-les pour que ce soit plus évident.

Le module Random

Ce module contient le niveau de fonctionnalités suivant, omis de Base pour contribuer à réduire la taille de la configuration par défaut de Julia.

Random complète rand et randn de Base avec des versions qui modifient leur argument, rand! et randn!.

Un ajout utile est randstring, qui génère une string d'une longueur donnée. Par défaut, elle utilise des lettres majuscules et minuscules plus les chiffres de 0 à 9, mais d'autres choix peuvent être précisés.

julia> using Random

julia> randstring(20)
"BoJnIxrS33pJiWggXZQV"

En outre, il existe une fonction bitrand qui génère un BitArray aléatoire d'une longueur donnée.

julia> bitrand(5)
julia> bitrand(5)
5-element BitVector:
 1
 1
 0
 0
 1

Mélanges et permutations

Pour mélanger aléatoirement les entrées d'un Vector, on dispose de shuffle, et aussi de shuffle! pour modifier le vecteur d'entrée sur place.

julia> v = ['A', '1', '2', 'J', 'Q', 'K'];

julia> shuffle(v)
6-element Vector{Char}:
 'K': ASCII/Unicode U+004B (category Lu: Letter, uppercase)
 '1': ASCII/Unicode U+0031 (category Nd: Number, decimal digit)
 'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
 'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase)
 '2': ASCII/Unicode U+0032 (category Nd: Number, decimal digit)
 'Q': ASCII/Unicode U+0051 (category Lu: Letter, uppercase)

# shuffles are random:
julia> shuffle(v)
6-element Vector{Char}:
 '2': ASCII/Unicode U+0032 (category Nd: Number, decimal digit)
 'K': ASCII/Unicode U+004B (category Lu: Letter, uppercase)
 'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
 'Q': ASCII/Unicode U+0051 (category Lu: Letter, uppercase)
 'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase)
 '1': ASCII/Unicode U+0031 (category Nd: Number, decimal digit)

Parfois, il est utile d'obtenir plutôt les indices mélangés. Pour cela, utilise randperm(n) où n est la longueur de la séquence.

julia> randperm(6)
6-element Vector{Int64}:
 6
 2
 4
 1
 3
 5

En pratique, l'exemple ci-dessus donne les mêmes résultats que shuffle(1:6).

Parmi les fonctions apparentées, on trouve randsubseq pour extraire des entrées avec une probabilité fixe, et randcycle pour les permutations cycliques. Ces fonctions demandent des connaissances spécialisées, alors consulte la documentation si elles t'intéressent.

Graines et algorithmes

Plusieurs algorithmes de génération de nombres aléatoires (RNG) sont intégrés en standard dans Random, et toute personne dotée des compétences mathématiques adéquates peut en ajouter d'autres. Ce genre de choses dépasse largement le cadre de ce document !

Une raison plus courante de travailler avec les RNG est de préciser une seed, ce qui a pour effet de rendre la séquence de sorties « aléatoires » reproductible d'une exécution à l'autre.

Une telle reproductibilité n'a pas sa place dans du code de production, mais elle peut faciliter les tests et le débogage.

Autres paquets

En dehors d'Exercism, il existe de nombreux paquets installables liés au hasard, aux probabilités et aux statistiques. Pour en savoir plus, voir le concept Statistics.

Le paquet StatsBase.jl

La plupart des fonctions de StatsBase sont assez techniques et sans rapport avec ce document.

L'exception est StatsBase.sample, qui fournit une implémentation complète du tirage avec ou sans remise (voir la section précédente, plus haut). Il existe aussi des fonctions pour le tirage pondéré (non uniforme).

Le paquet Distributions.jl

Les distributions uniform et normal (ou gaussienne) ont été décrites plus haut.

Le module Random contient aussi randexp pour échantillonner selon la distribution exponentielle, qui est liée à la distribution de Poisson (très courante).

Pour un choix d'options bien plus large, il y a le paquet Distributions.jl, destiné à ceux qui ont les bases appropriées en statistiques.

Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet

Apprends Aléatoire

L'entraînement est verrouillé

Déverrouille 1 exercice de plus pour t'entraîner sur Aléatoire