Parcours
/
Julia
Julia
/
Exercices
/
Matrice Exercism
Matrice Exercism

Matrice Exercism

Exercice d'apprentissage

Introduction

Bien plus haut, dans le concept Vectors, on a noté que « les tableaux peuvent être de taille arbitraire (seule la mémoire de ton matériel les limite), et peuvent avoir un nombre arbitraire de dimensions. »

Depuis, on a largement ignoré les tableaux à plus d'une dimension, histoire de rester simples. Cette décision paraîtra plus logique si tu essaies de lire les documents de référence de Julia dans toute leur complexité.

Cependant, les tableaux à dimensions supérieures sont très, très importants en calcul scientifique, il faut donc qu'on les comprenne.

Nomenclature : en suivant des siècles de précédent mathématique, on appelle les tableaux à une dimension des Vectors et les tableaux à deux dimensions des Matrices.

Les exemples de ce document seront principalement des matrices. Travailler avec 3 dimensions ou plus est syntaxiquement presque identique, mais le résultat est difficile et pénible à lire (sur un écran en 2D).

Construire des tableaux

On a créé beaucoup de vecteurs en mettant une liste séparée par des virgules entre crochets. On peut aussi utiliser des points-virgules comme séparateur.

julia> v = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> w = [1; 2; 3]
3-element Vector{Int64}:
 1
 2
 3

julia> v == w
true

Si on utilise un espace (ou une tabulation) comme séparateur, le résultat est différent.

julia> u = [1 2 3]
1×3 Matrix{Int64}:
 1  2  3

Julia définit alors ceci comme une matrice 1×3 (dans d'autres contextes, on l'appellerait un row vector).

En général, les espaces joignent horizontalement, les points-virgules (ou les retours à la ligne) joignent verticalement.

La référence à des « choses » est volontairement vague, car Julia essaiera de se débrouiller avec tout ce que tu lui donnes.

julia> [v 2v]
3×2 Matrix{Int64}:
 1  2
 2  4
 3  6

julia> [v; 2v]
6-element Vector{Int64}:
 1
 2
 3
 2
 4
 6

Il existe des fonctions hcat() et vcat() qui font la même chose, en montrant plus explicitement qu'il s'agit de concaténations horizontales et verticales. La généralisation aux dimensions supérieures est la fonction cat().

jjulia> hcat(v, 2v)
3×2 Matrix{Int64}:
 1  2
 2  4
 3  6

julia> vcat(v, 2v)
6-element Vector{Int64}:
 1
 2
 3
 2
 4
 6

Taper des matrices explicites se fait commodément dans l'ordre row-major, parce que cela correspond à l'intuition humaine (plus facile à regarder, pour les cultures à écriture horizontale) :

julia> m = [1 2 3; 4 5 6]
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

Mais attention : Julia (comme Fortran, R et Matlab, mais contrairement à C/C++ ou NumPy) stocke les tableaux à N dimensions en ordre column-major, et cela peut faire une énorme différence de performance si tu parcours les éléments. Aide le cache de ton processeur à t'aider !

# put these integers in 2 rows and 3 columns

julia> reshape(collect(1:6), 2, 3)
2×3 Matrix{Int64}:
 1  3  5
 2  4  6

L'exemple ci-dessus prend les entiers de 1 à 6 et remplit avec eux une matrice 2×3, colonne par colonne.

Il existe diverses fonctions utilitaires pour construire des types courants de tableau (uniformes ou aléatoires).

julia> zeros(2, 3)  # see also ones()
2×3 Matrix{Float64}:
 0.0  0.0  0.0
 0.0  0.0  0.0

julia> falses(2, 2)  # booleans, see also trues()
2×2 BitMatrix:
 0  0
 0  0

julia> rand(Float32, 2, 3)  # random numbers in the interval [0, 1)
2×3 Matrix{Float32}:
 0.768823  0.169633  0.632565
 0.388451  0.109176  0.850381

Indexation

Pour un tableau à 2 dimensions, on utilise généralement deux indices dans l'ordre [row, col].

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> m[1, 2] # row 1, col 2
2

# Stay within bounds! There is no row 3.
julia> m[3, 1]
ERROR: BoundsError: attempt to access 2×3 Matrix{Int64} at index [3, 1]

julia> m[3]
2

Le dernier exemple est peut-être surprenant : un seul indice n'est pas une erreur, et renvoie un seul élément.

L'explication remonte au commentaire sur l'ordre column-major : Julia descend la colonne 1, puis la colonne 2, jusqu'à trouver le 3e élément en mémoire.

Attention : cela a quelques usages quand on écrit des bibliothèques génériques, mais c'est plus souvent source de confusion !

Un problème similaire se pose quand on interroge la taille d'un tableau. length() donne le nombre total d'éléments, size() donne un tuple de ndims() éléments contenant la longueur de chaque dimension.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> length(m)
6

julia> size(m)  # 2 rows, 3 cols
(2, 3)

julia> ndims(m)  # how many dimensions? Like `m |> size |> length`
2

Indexation avec des plages et des tableaux

On peut facilement copier une sous-matrice.

Dans l'exemple ci-dessous, la fonction reshape() force un tableau aux dimensions indiquées en remplissant colonne par colonne, puis on en prélève une tranche.

julia> m12 = reshape(collect(1:12), 4, 3)
4×3 Matrix{Int64}:
 1  5   9
 2  6  10
 3  7  11
 4  8  12

julia> m12[2:4, 1:2]
3×2 Matrix{Int64}:
 2  6
 3  7
 4  8

# some rows, all columns
julia> m12[2:4, :]
3×3 Matrix{Int64}:
 2  6  10
 3  7  11
 4  8  12

Un : tout seul signifie « copie tout dans cette dimension ».

Utilise des vecteurs pour des lignes ou des colonnes non consécutives :

julia> m12[[1, 3], :]  # rows 1 and 3
2×3 Matrix{Int64}:
 1  5   9
 3  7  11

Appliquer des fonctions à un tableau

On a déjà vu des fonctions d'agrégation comme sum() et maximum() appliquées à des collections à une dimension, où elles opèrent sur tous les éléments et renvoient un résultat scalaire.

Cela fonctionne aussi en dimensions supérieures. Cependant, on peut vouloir n'appliquer la fonction qu'à une seule dimension, par exemple en sommant vers le bas ou vers la droite pour renvoyer un tableau avec une singleton dimension de taille 1.

Pour cela, il existe un argument nommé optionnel dims.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> sum(m)  # sum everything
21

julia> sum(m; dims=1)  # sum down
1×3 Matrix{Int64}:
 5  7  9

julia> sum(m; dims=2)  # sum across
2×1 Matrix{Int64}:
  6
 15

# other aggregation functions are similar
julia> maximum(m; dims=2)
2×1 Matrix{Int64}:
 3
 6

La valeur de dims est la dimension qui devient singleton, donc dims=1 => 1×3 et dims=2 => 2×1 dans les exemples ci-dessus.

Par extension, les tableaux à dimensions supérieures peuvent réduire plusieurs dimensions, si dims est défini sur un tableau ou une plage (là encore, comprendre le résultat peut demander un peu de réflexion !).

Écrire des fonctions qui tiennent compte des dimensions

L'argument nommé dims est courant dans les fonctions natives comme sum(), mais comment écrire l'équivalent dans notre propre code ?

Une bonne réponse consiste à utiliser des fonctions d'ordre supérieur comme reduce(), ce qui sera détaillé dans un concept ultérieur.

Sinon, Julia fournit plusieurs fonctions qui permettent de traiter les tableaux à N dimensions comme s'il s'agissait de vecteurs de vecteurs imbriqués.

Pour les matrices, eachrow() et eachcol() sont pratiques, mais la fonction plus générale est eachslice(), qui gère des dimensions arbitraires.

# m is as in the previous examples

julia> eachrow(m)
2-element RowSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Int64, Base.Slice{Base.OneTo{Int64}}}, true}}:
 [1, 2, 3]
 [4, 5, 6]

julia> eachcol(m)
3-element ColumnSlices{Matrix{Int64}, Tuple{Base.OneTo{Int64}}, SubArray{Int64, 1, Matrix{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, true}}:
 [1, 4]
 [2, 5]
 [3, 6]

Le type est un peu effrayant, mais uniquement parce qu'il s'agit d'une vue sur le tableau d'origine, ce qui permet de travailler dessus sans le copier. Les tableaux Julia peuvent peser des téraoctets, donc les copier peut être un cauchemar de performance !

Une vue comme celle-ci peut servir à parcourir en boucle, à faire de la diffusion, ou à toutes les autres opérations qu'on a vues dans le programme jusqu'ici.

De plus, les compréhensions peuvent être puissantes et polyvalentes avec un tableau en entrée. Des cas simples ont été mentionnés dans le concept Loops, mais une discussion plus complète aura lieu dans un concept ultérieur.

La diffusion en plusieurs dimensions

On a abordé le cas à une dimension dans le concept Vector Operations.

julia> v = [1.2, 1.5, 1.7]
3-element Vector{Float64}:
 1.2
 1.5
 1.7

julia> v .- 0.5
3-element Vector{Float64}:
 0.7
 1.0
 1.2

L'opérateur pointé .- traite le singleton 0.5 comme équivalent à [0.5, 0.5, 0.5] en lui appliquant un broadcasting qui le fait correspondre aux dimensions, puis effectue la soustraction élément par élément.

Étendre cela à des dimensions supérieures n'est vraiment qu'une répétition du même principe.

Par exemple, lorsqu'on diffuse la multiplication d'un vecteur ligne 2x1 [1.0 1.5 2.0] avec la matrice 2x3 [1 2 3; 4 5 6], cela équivaut à diffuser [1.0 1.5 2.0] en [1.0 1.5 2.0; 1.0 1.5 2.0], puis à effectuer la multiplication élément par élément.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> m .* [1.0 1.5 2.0]  # broadcast a 1x3 row vector down
2×3 Matrix{Float64}:
 1.0  3.0   6.0
 4.0  7.5  12.0

julia> m .* [1.0 1.5 2.0; 1.0 1.5 2.0]  # 2x3 elementwise multiplication
2×3 Matrix{Float64}:
 1.0  3.0   6.0
 4.0  7.5  12.0

julia> m ./ [1, 2]  # broadcast a 2x1 column vector across
2×3 Matrix{Float64}:
 1.0  2.0  3.0
 2.0  2.5  3.0

julia> m ./ [1 1 1; 2 2 2]  # 2x3 elementwise division
2×3 Matrix{Float64}:
 1.0  2.0  3.0
 2.0  2.5  3.0

Remarque : ce sont les dimensions non-singleton qui doivent avoir la même taille (par exemple 2x3 Matrix .* 2x1 Matrix) pour la diffusion.

Par ailleurs, une fonction peut être diffusée sur chaque élément d'une Matrix exactement comme on le fait avec un Vector.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> (x -> x^2).(m)  # broadcast a function to all elements
2×3 Matrix{Int64}:
  1   4   9
 16  25  36

Tu peux avoir l'impression, au début, que le potentiel de confusion pour le programmeur croît exponentiellement avec le nombre de dimensions, mais la pratique aide beaucoup. De plus, la familiarité avec les tableaux NumPy se transpose assez bien à Julia, malgré la syntaxe différente.

Instructions

Une matrice de points est une image en deux dimensions composée de points et d'espaces. Les points forment l'image voulue sur le fond constitué des espaces.

Une image en matrice de points peut être stockée et manipulée en mémoire sous la forme d'une Matrix en deux dimensions. Dans ce qui suit, les « espaces » des matrices seront représentés par 0 et les « points » seront non nuls.

1. Définis la Matrix du logo Exercism

La Matrix se présente comme suit, avec 0 comme espace et 1 comme point :

[
    0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0;
    0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
    0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0;
    0 1 0 0 1 0 1 0 0 0 0 1 0 1 0 0 1 0;
    0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;
    0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0;
    0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0;
    0 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0;
    0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0;
    0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0;
]

Si on l'affiche, voici ce que ça donne :

  XX          XX  
 X              X 
 X   X      X   X 
 X  X X    X X  X 
 X              X 
X                X
 X    X    X    X 
 X     X  X     X 
 X      XX      X 
 X              X 
  XX          XX

Définis les fonctions frown!() et frown() qui prennent la Matrix du logo Exercism. Renvoie une Matrix dans laquelle la bouche souriante est remplacée par une bouche renfrognée.

La Matrix obtenue s'afficherait comme ceci :

  XX          XX
 X              X
 X   X      X   X
 X  X X    X X  X
 X              X
X                X
 X      XX      X
 X     X  X     X
 X    X    X    X
 X              X
  XX          XX

3. Assemble un mur d'autocollants

Définis la fonction stickerwall(), qui prend en entrée la matrice du logo Exercism. Renvoie une Matrix de la matrice de points qui s'affiche comme ceci :

  XX          XX    XX          XX
 X              X  X              X
 X   X      X   X  X   X      X   X
 X  X X    X X  X  X  X X    X X  X
 X              X  X              X
X                XX                X
 X    X    X    X  X      XX      X
 X     X  X     X  X     X  X     X
 X      XX      X  X    X    X    X
 X              X  X              X
  XX          XX    XX          XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  XX          XX    XX          XX
 X              X  X              X
 X   X      X   X  X   X      X   X
 X  X X    X X  X  X  X X    X X  X
 X              X  X              X
X                XX                X
 X      XX      X  X    X    X    X
 X     X  X     X  X     X  X     X
 X    X    X    X  X      XX      X
 X              X  X              X
  XX          XX    XX          XX

4. Remplace les points par le nombre de pixels de chaque colonne

On n'est pas obligé d'utiliser 1 comme point, et on pourrait donc, si on le souhaite, encoder d'autres informations utiles. Définis la fonction colpixelcount(), qui prend en entrée n'importe quelle matrice de points dont les points valent 1. Renvoie une matrice de points de même taille, dans laquelle les points de chaque colonne correspondent au nombre de points de cette colonne.

Si on lui donne la Matrix du logo Exercism en entrée, le résultat est une Matrix comme suit :

[
    0 0 2 2 0 0 0 0 0 0 0 0 0 0 2 2 0 0;
    0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
    0 8 0 0 0 1 0 0 0 0 0 0 1 0 0 0 8 0;
    0 8 0 0 1 0 2 0 0 0 0 2 0 1 0 0 8 0;
    0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;
    0 8 0 0 0 0 2 0 0 0 0 2 0 0 0 0 8 0;
    0 8 0 0 0 0 0 1 0 0 1 0 0 0 0 0 8 0;
    0 8 0 0 0 0 0 0 1 1 0 0 0 0 0 0 8 0;
    0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0;
    0 0 2 2 0 0 0 0 0 0 0 0 0 0 2 2 0 0;
]

5. Affiche une matrice de points

Plutôt que de simplement parler d'afficher nos créations, on aimerait pouvoir le faire pour les examiner facilement. Définis la fonction render(), qui prend en entrée une matrice de points. Renvoie une string dans laquelle les points sont affichés sous forme de 'X', les 0 sous forme de ' ', et où des retours à la ligne relient chaque ligne.

julia> render(E)
"  XX          XX  \n X              X \n X   X      X   X \n X  X X    X X  X \n X              X \nX                X\n X    X    X    X \n X     X  X     X \n X      XX      X \n X              X \n  XX          XX  "

Une fois imprimé, le résultat doit afficher la matrice Exercism comme prévu.

julia> print(render(E))
  XX          XX
 X              X
 X   X      X   X
 X  X X    X X  X
 X              X
X                X
 X    X    X    X
 X     X  X     X
 X      XX      X
 X              X
  XX          XX

Cela doit aussi fonctionner avec des « points » différents de 1 :

julia> print(render(colpixelcount(E)))
  XX          XX
 X              X
 X   X      X   X
 X  X X    X X  X
 X              X
X                X
 X    X    X    X
 X     X  X     X
 X      XX      X
 X              X
  XX          XX
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Julia Exercism

Prêt à commencer Matrice Exercism ?

Inscris-toi sur Exercism pour apprendre et maîtriser Julia avec 35 concepts128 exercices, et un vrai mentorat humain, le tout gratuitement.