Parcours
/
Elixir
Elixir
/
Exercices
/
Peinture par numéros
Peinture par numéros

Peinture par numéros

Exercice d'apprentissage

Introduction

Les bitstrings

Manipuler des données binaires est un concept important dans n'importe quel langage, et Elixir offre une syntaxe élégante pour écrire, faire correspondre et construire des données binaires.

En Elixir, les données binaires portent le nom de type bitstring. Le type de données binaires (à ne pas confondre avec les données binaires en général) est une forme particulière de bitstring, que l'on verra dans un exercice ultérieur.

Les littéraux de bitstring se définissent à l'aide de la forme spéciale <<>>. Quand on définit un littéral de bitstring, on le décrit en segments. Chaque segment possède une valeur et un type, séparés par l'opérateur ::. Le type précise combien de bits serviront à encoder la valeur. On peut omettre complètement le type, ce qui donne par défaut une valeur entière de 8 bits.

# This defines a bitstring with three segments of a single bit each
<<0::1, 1::1, 0::1>>

Spécifier le type ::1 est un raccourci pour écrire ::size(1). Il faut utiliser la syntaxe plus longue si la taille en bits provient d'une variable.

Le binaire

Lorsqu'on écrit des littéraux d'entiers binaires, on peut les écrire directement en notation en base 2 en faisant précéder le littéral de 0b. À noter qu'ils seront de toute façon affichés sous forme de nombres décimaux dans les résultats de tests ou quand on utilise iex.

<<0b1011::4>> == <<11::4>>
# => true

Tronque

Si la valeur du segment dépasse la capacité du type de ce segment, elle est tronquée à partir de la gauche.

<<0b1011::3>> == <<0b0011::3>>
# => true

Ajoute au début et à la fin

On peut à la fois ajouter au début et à la fin d'un bitstring existant à l'aide de la forme spéciale. Le type ::bitstring doit être utilisé sur le bitstring existant s'il est de taille inconnue.

value = <<0b110::3, 0b001::3>>
new_value = <<0b011::3, value::bitstring, 0b000::3>>
# => <<120, 8::size(4)>>

Concatène

On peut concaténer des bitstrings stockés dans des variables à l'aide de la forme spéciale. Le type ::bitstring doit être utilisé lorsqu'on concatène deux bitstrings de tailles inconnues.

first = <<0b110::3>>
second = <<0b001::3>>
concatenated = <<first::bitstring, second::bitstring>>
# => <<49::size(6)>>

Fais correspondre des motifs

On peut aussi faire de la correspondance de motifs pour obtenir des valeurs à partir de la forme spéciale. Il faut connaître le nombre de bits de chaque fragment que l'on veut capturer, à une exception près : le type ::bitstring permet de faire correspondre un motif sur un bitstring de taille inconnue, mais uniquement pour le dernier fragment.

<<value::4, rest::bitstring>> = <<0b01101001::8>>
value == 0b0110
# => true

Inspecte les bitstrings

Note

Les bitstrings peuvent être affichés (par l'exécuteur de tests ou dans iex) dans un format différent de celui qui a servi à les créer. Cela prête souvent à confusion lorsqu'on apprend les bitstrings.

Par défaut, les bitstrings sont affichés par fragments de 8 bits (un octet), même si on les a créés avec des fragments d'une taille différente.

<<2011::11>>
# => <<251, 3::size(3)>>

Si on crée un bitstring qui représente une string encodée en UTF-8 et imprimable, il s'affiche comme une string.

<<>>
# => ""

<<65, 66, 67>>
# => "ABC"

Instructions

La peinture par numéros (aussi appelée coloriage par numéros) désigne des images en noir et blanc destinées à être coloriées. Les différentes zones de l'image sont annotées avec des numéros différents, et ces numéros correspondent à des couleurs précises dans une palette de couleurs prédéfinie. Le but est de remplir les zones avec les bonnes couleurs, pour révéler au final une magnifique image haute en couleur. C'est une activité relaxante, pour les enfants comme pour les adultes.

Tu dois écrire une application de peinture par numéros en Elixir. Tu veux que ton application puisse importer et exporter des images dans un format de données personnalisé. Tu as donc décidé d'utiliser des fichiers binaires pour stocker les données de tes images.

Note

Cet exercice suppose que tu connaisses les nombres binaires et que tu comprennes les principes qui permettent de convertir des nombres binaires en nombres décimaux et des nombres décimaux en nombres binaires.

Imaginons que tu aies une image de smiley, comme celle ci-dessous. L'image a un fond blanc. Le smiley a un contour noir et une couleur de remplissage jaune.

Cette image utilise 3 couleurs. Disons que l'on attribue des indices à ces couleurs :

  • 0 (binaire : 0b00) pour le blanc,
  • 1 (binaire : 0b01) pour le noir,
  • 2 (binaire : 0b10) pour le jaune.

On peut maintenant utiliser ces indices de couleur pour représenter la couleur de chaque pixel.

Smiley Smiley avec les indices de couleur

1. Calcule la taille en bits de la palette

Implémente la fonction PaintByNumber.palette_bit_size/1. Elle doit prendre le nombre de couleurs de la palette et renvoyer combien de bits sont nécessaires pour représenter autant d'indices de couleur en binaire. Les indices de couleur commencent toujours à 0 et sont des entiers consécutifs croissants.

Par exemple, représenter 13 couleurs différentes nécessite 4 bits. 4 bits peuvent stocker jusqu'à 16 indices de couleur (2^4). 3 bits ne suffiraient pas, car 3 bits ne peuvent stocker que jusqu'à 8 indices de couleur (2^3).

PaintByNumber.palette_bit_size(13)
# => 4

Remarque : il n'existe pas de fonction log2 dans la bibliothèque standard d'Elixir. Tu apprendras plus tard comment utiliser les bibliothèques Erlang depuis Elixir, où tu trouveras cette fonction. Pour le moment, résous cette tâche avec la récursion et l'opérateur de puissance ** à la place.

2. Crée une image vide

Implémente la fonction PaintByNumber.empty_picture/0. Elle doit renvoyer un bitstring vide.

3. Crée une image de test

Une image de test prédéfinie sera utilisée pour tester manuellement les fonctionnalités de base de ton application. Cette image de test est composée de 4 pixels de 4 couleurs différentes.

Implémente la fonction PaintByNumber.test_picture/0. Elle doit renvoyer un bitstring composé de 4 segments. Chaque segment doit avoir une taille de 2 bits. Les segments doivent avoir pour valeurs 0, 1, 2 et 3.

4. Ajoute un pixel au début d'une image

Implémente la fonction PaintByNumber.prepend_pixel/3. Elle doit prendre trois arguments : un bitstring contenant l'image à laquelle on ajoute un pixel au début, le nombre de couleurs de la palette et l'indice de la couleur du nouveau pixel. Elle doit renvoyer un bitstring contenant l'image avec le nouveau pixel ajouté au début.

picture = <<2::4, 0::4>>
color_count = 13
pixel_color_index = 11

PaintByNumber.prepend_pixel(picture, color_count, pixel_color_index)
# => <<178, 0::size(4)>>
# (which is equal to <<11::4, 2::4, 0::4>>)

5. Récupère le premier pixel d'une image

Implémente la fonction PaintByNumber.get_first_pixel/2. Elle doit prendre deux arguments : un bitstring contenant l'image dans laquelle on lit, et le nombre de couleurs de la palette. Elle doit renvoyer l'indice de couleur du premier pixel de l'image donnée. Si on lui donne une image vide, elle doit renvoyer nil.

picture = <<19::5, 2::5, 18::5>>
color_count = 20

PaintByNumber.get_first_pixel(picture, color_count)
# => 19

6. Supprime le premier pixel d'une image

Implémente la fonction PaintByNumber.drop_first_pixel/2. Elle doit prendre deux arguments : un bitstring contenant l'image de laquelle on retire un pixel, et le nombre de couleurs de la palette. Elle doit renvoyer l'image sans le premier pixel. Si on lui donne une image vide, elle doit renvoyer une image vide.

picture = <<2::3, 5::3, 5::3, 0::3>>
color_count = 6

PaintByNumber.drop_first_pixel(picture, color_count)
# => <<180, 0::size(1)>>
# (which is equal to <<5::3, 5::3, 0::3>>)

7. Concatène deux images

Implémente la fonction PaintByNumber.concat_pictures/2. Elle doit prendre deux arguments, deux bitstrings. Elle doit renvoyer un bitstring qui est le résultat de l'ajout du premier argument au début du second.

picture1 = <<52::6, 51::6>>
picture2 = <<0::6, 34::6, 12::6>>

PaintByNumber.concat_pictures(picture1, picture2)
# => <<211, 48, 34, 12::size(6)>>
# (which is equal to <<52::6, 51::6, 0::6, 34::6, 12::6>>)
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Elixir Exercism

Prêt à commencer Peinture par numéros ?

Inscris-toi sur Exercism pour apprendre et maîtriser Elixir avec 58 concepts168 exercices, et un vrai mentorat humain, le tout gratuitement.