Lavorare con i dati binari è un concetto importante in qualsiasi linguaggio, ed Elixir fornisce una sintassi elegante per scrivere, confrontare e costruire dati binari.
In Elixir, i dati binari sono indicati come il tipo stringa di bit. Il tipo di dati binari (da non confondere con i dati binari in generale) è una forma specifica di stringa di bit, di cui parleremo in un esercizio successivo.
I letterali stringa di bit sono definiti usando la forma speciale per le stringhe di bit <<>>. Quando si definisce un letterale stringa di bit, lo si definisce in segmenti. Ogni segmento ha un valore e un tipo, separati dall'operatore ::. Il tipo specifica quanti bit verranno usati per codificare il valore. Il tipo può essere omesso completamente; in tal caso, il valore predefinito è un intero a 8 bit.
# This defines a bitstring with three segments of a single bit each
<<0::1, 1::1, 0::1>>
Specificare il tipo come ::1 è una scorciatoia per scrivere ::size(1). Devi usare la sintassi più lunga se la dimensione in bit proviene da una variabile.
Quando scriviamo letterali interi binari, possiamo scriverli direttamente nella notazione in base 2 anteponendo al letterale 0b. Tieni presente che verranno comunque visualizzati come numeri decimali quando stampati nei risultati dei test o quando si usa iex.
<<0b1011::4>> == <<11::4>>
# => true
Se il valore del segmento supera la capacità del tipo del segmento, verrà troncato da sinistra.
<<0b1011::3>> == <<0b0011::3>>
# => true
Puoi sia anteporre che aggiungere in coda a una stringa di bit esistente usando la forma speciale. Il tipo ::bitstring deve essere usato sulla stringa di bit esistente se questa è di dimensione sconosciuta.
value = <<0b110::3, 0b001::3>>
new_value = <<0b011::3, value::bitstring, 0b000::3>>
# => <<120, 8::size(4)>>
Possiamo concatenare stringhe di bit memorizzate in variabili usando la forma speciale. Il tipo ::bitstring deve essere usato quando si concatenano due stringhe di bit di dimensioni sconosciute.
first = <<0b110::3>>
second = <<0b001::3>>
concatenated = <<first::bitstring, second::bitstring>>
# => <<49::size(6)>>
Il pattern matching può essere usato anche per ottenere valori dalla forma speciale. Devi conoscere il numero di bit per ogni frammento che vuoi catturare, con un'eccezione: il tipo ::bitstring può essere usato per fare pattern matching su una stringa di bit di dimensione sconosciuta, ma può essere usato solo per l'ultimo frammento.
<<value::4, rest::bitstring>> = <<0b01101001::8>>
value == 0b0110
# => true
Le stringhe di bit potrebbero essere stampate (dal test runner o in iex) in un formato diverso da quello usato per crearle. Questo spesso causa confusione quando si imparano le stringhe di bit.
Per impostazione predefinita, le stringhe di bit vengono visualizzate in frammenti di 8 bit (un byte), anche se le hai create con frammenti di dimensione diversa.
<<2011::11>>
# => <<251, 3::size(3)>>
Se crei una stringa di bit che rappresenta una stringa stampabile codificata in UTF-8, viene visualizzata come stringa.
<<>>
# => ""
<<65, 66, 67>>
# => "ABC"
La pittura con i numeri (nota anche come colorare con i numeri) è una tecnica che usa immagini in bianco e nero pensate per essere colorate. Le diverse aree dell'immagine sono annotate con numeri diversi, e i numeri corrispondono a colori specifici in una palette di colori predefinita. L'obiettivo è riempire le aree con i colori giusti, rivelando alla fine una bellissima immagine colorata. È un'attività rilassante sia per i bambini che per gli adulti.
Il tuo compito è scrivere un'app di pittura con i numeri in Elixir. Vuoi che l'app sia in grado di importare ed esportare immagini in un formato di dati personalizzato. Hai deciso di usare i file binari per memorizzare i dati delle immagini.
Questo esercizio presuppone che tu abbia familiarità con i numeri binari e che capisca i principi alla base della conversione dei numeri binari in numeri decimali e dei numeri decimali in numeri binari.
Immaginiamo di avere l'immagine di una faccina sorridente, come quella mostrata qui sotto. L'immagine ha uno sfondo bianco. La faccina ha un bordo nero e un riempimento giallo.
Questa immagine usa 3 colori. Assegniamo degli indici a questi colori:
0 (binario: 0b00) per il bianco,1 (binario: 0b01) per il nero,2 (binario: 0b10) per il giallo.Ora possiamo usare quegli indici di colore per rappresentare il colore di ogni pixel.
| Faccina sorridente | Faccina sorridente con gli indici di colore |
|---|---|
![]() |
![]() |
Implementa la funzione PaintByNumber.palette_bit_size/1. Deve accettare il numero di colori della palette e restituire quanti bit sono necessari per rappresentare quel numero di indici di colore come numeri binari. Gli indici di colore iniziano sempre da 0 e sono numeri interi consecutivi in ordine crescente.
Ad esempio, per rappresentare 13 colori diversi servono 4 bit. 4 bit possono memorizzare fino a 16 indici di colore (2^4). 3 bit non basterebbero, perché 3 bit possono memorizzare solo fino a 8 indici di colore (2^3).
PaintByNumber.palette_bit_size(13)
# => 4
Nota: non esiste una funzione log2 nella libreria standard di Elixir. In seguito imparerai a usare le librerie Erlang da Elixir, dove puoi trovare questa funzione. Per ora, risolvi questo compito con la ricorsione e l'operatore di potenza **.
Implementa la funzione PaintByNumber.empty_picture/0. Deve restituire una bitstring vuota.
Un'immagine di test predefinita verrà usata per il test manuale delle funzionalità di base dell'app. L'immagine di test è composta da 4 pixel con 4 colori diversi.
Implementa la funzione PaintByNumber.test_picture/0. Deve restituire una bitstring composta da 4 segmenti.
Ogni segmento deve avere una dimensione in bit pari a 2. I segmenti devono avere i valori 0, 1, 2 e 3.
Implementa la funzione PaintByNumber.prepend_pixel/3. Deve accettare tre argomenti: una bitstring con l'immagine a cui stiamo anteponendo, il numero di colori della palette e l'indice del colore del nuovo pixel. Deve restituire una bitstring con l'immagine a cui è stato aggiunto il nuovo pixel all'inizio.
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>>)
Implementa la funzione PaintByNumber.get_first_pixel/2. Deve accettare due argomenti: una bitstring con l'immagine da cui stiamo leggendo e il numero di colori della palette. Deve restituire l'indice del colore del primo pixel dell'immagine data. Se riceve un'immagine vuota, deve restituire nil.
picture = <<19::5, 2::5, 18::5>>
color_count = 20
PaintByNumber.get_first_pixel(picture, color_count)
# => 19
Implementa la funzione PaintByNumber.drop_first_pixel/2. Deve accettare due argomenti: una bitstring con l'immagine da cui stiamo rimuovendo un pixel e il numero di colori della palette. Deve restituire l'immagine senza il primo pixel. Se riceve un'immagine vuota, deve restituire un'immagine vuota.
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>>)
Implementa la funzione PaintByNumber.concat_pictures/2. Deve accettare due argomenti, due bitstring. Deve restituire una bitstring che è il risultato dell'anteposizione del primo argomento al secondo argomento.
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>>)
Iscriviti a Exercism per imparare e padroneggiare Elixir con 58 concetti168 esercizi e il mentoring di persone reali, tutto gratis.