Una tupla è una lista ordinata e finita di elementi, immutabile. Le tuple richiedono che tutte le posizioni abbiano un tipo fisso. Questo, a sua volta, significa che il compilatore sa quale tipo si trova in ogni posizione. I tipi usati in una tupla possono essere diversi in ogni posizione, ma devono essere noti in fase di compilazione.
A seconda che i tipi dei valori della tupla possano essere interpretati in fase di compilazione, la tupla può essere creata in modi diversi. Se i valori sono noti in fase di compilazione, la tupla può essere creata usando la sintassi letterale delle tuple, altrimenti occorre dichiararli esplicitamente. È anche importante che i tipi dei valori corrispondano ai tipi specificati nella tupla e che il numero di valori corrisponda al numero di tipi specificati. Ecco un esempio di definizione tramite la sintassi letterale delle tuple:
tuple = {1, "foo", 'c'} # Tuple(Int32, String, Char)
C'è anche la possibilità di creare una tupla usando la classe Tuple.
tuple = Tuple(Int32, String, Char).new(1, "foo", 'c')
In alternativa, puoi specificare esplicitamente il tipo della variabile a cui è assegnata la tupla.
tuple : Tuple(Int32, String, Char) = {1, "foo", 'c'}
Specificare esplicitamente il tipo della tupla può essere utile, dato che permette di definire che una posizione debba contenere un tipo unione. Questo significa che una posizione può contenere più tipi.
tuple : Tuple(Int32 | String, String, Char) = {1, "foo", 'c'}
Puoi creare una tupla da un array usando il metodo from della classe Tuple.
Questo richiede che il tipo della tupla sia specificato.
array = [1, "foo", 'c']
tuple = Tuple(Int32, String, Char).from(array)
Puoi convertire una tupla in un array usando il metodo to_a.
Il tipo degli elementi dell'array risultante è l'unione dei tipi di ciascun campo della tupla.
tuple = {1, "foo", 'c'}
array = tuple.to_a
array # => [1, "foo", 'c']
Come gli array, le tuple hanno indici che partono da zero, quindi il primo elemento si trova all'indice 0.
A differenza degli array, però, il tipo di ogni elemento è fisso e noto in fase di compilazione; quindi, quando si accede a una tupla tramite indice, il tipo dell'elemento è specifico per quella posizione.
Per accedere a un elemento di una tupla, puoi usare l'operatore [].
array = [1, "foo", 'c']
array[0] # => 1
typeof(array[0]) # => Int32 | String | Char
tuple = {1, "foo", 'c'}
tuple[0] # => 1
typeof(tuple[0]) # => Int32
Un'altra differenza rispetto all'accesso agli elementi di un array è che, se l'indice è specificato, il compilatore controlla che rientri nei limiti della tupla. Questo significa che otterrai un errore in fase di compilazione invece di un errore a runtime.
tuple = {1, "foo", 'c'}
tuple[3]
# => Error: index out of bounds for Tuple(Int32, String, Char) (3 not in -3..2)
Se però l'indice è memorizzato in una variabile, il compilatore non potrà verificare in fase di compilazione se rientra nei limiti della tupla e segnalerà invece un errore a runtime.
Puoi ottenere una sottotupla di una tupla usando l'operatore [] con un intervallo.
Quello che viene restituito è una nuova tupla con gli elementi dell'intervallo specificato.
L'intervallo deve essere fornito in fase di compilazione, altrimenti il compilatore non potrà conoscere i tipi degli elementi della sottotupla.
Questo significa che l'intervallo deve essere un letterale di intervallo e non essere assegnato a una variabile.
tuple = {1, "foo", 'c'}
subtuple = tuple[0..1] # Tuple(Int32, String)
i = 0..1
tuple[i]
# Error: Tuple#[](Range) can only be called with range literals known at compile-time
Le tuple sono utili quando vuoi raggruppare un numero fisso di valori i cui tipi sono noti in fase di compilazione. Questo perché le tuple richiedono meno memoria e sono più veloci degli array, grazie alla loro immutabilità. Un altro caso d'uso è restituire più valori da un metodo. È particolarmente utile se i valori hanno tipi diversi, dato che ogni posizione della tupla può avere un tipo diverso.
Non conviene usare le tuple quando serve una struttura dati che possa crescere o ridursi di dimensione o che debba essere modificata spesso.
I simboli sono identificatori con un nome che possono essere usati per riferirsi a un valore.
I simboli si creano tramite un letterale simbolo, anteponendo un nome al carattere :, ad esempio :foo.
Possono anche essere scritti tra virgolette, ad esempio :"foo", il che permette, per esempio, di usare spazi nel nome.
:foo # => :foo
:"foo boo" # => :"foo boo"
:foo == :"foo" # => true
I simboli sono usati in molti punti del linguaggio, ad esempio come chiavi nei namedtuple e per rappresentare i nomi di metodi e variabili.
I simboli in Crystal sono piuttosto diversi da quelli di Ruby. In Crystal un simbolo è un tipo di costante e quindi viene assegnato in fase di compilazione. Questo significa che i simboli non possono essere creati dinamicamente, cosa possibile invece in Ruby.
In Crystal i simboli sono rappresentati come Int32, il che li rende molto efficienti.
I simboli possono essere convertiti in stringhe, ma non viceversa. Questo perché i simboli vengono creati in fase di compilazione e le stringhe a runtime.
:foo.to_s # => "foo"
Mentre ti prepari a cuocere dei biscotti per i tuoi amici, hai scoperto di dover convertire alcune delle misure usate nella ricetta. Conoscendo solo il sistema metrico, devi trovare un modo per convertire in millilitri (mL) le comuni misure americane usate per i dolci, per semplificarti il lavoro.
Usa questa tabella di conversione per la tua soluzione:
| Unità da convertire | volume | in millilitri (mL) |
|---|---|---|
| mL | 1 | 1 |
| US cup | 1 | 240 |
| US fluid ounce | 1 | 30 |
| US teaspoon | 1 | 5 |
| US tablespoon | 1 | 15 |
Essendo un programmatore di talento in formazione, decidi di usare i millilitri come unità di transizione per facilitare la conversione da qualsiasi unità elencata a qualsiasi altra (anche a se stessa).
Implementa il metodo KitchenCalculator.get_volume.
Data una coppia di volume sotto forma di tupla, deve restituire solo la componente numerica.
KitchenCalculator.get_volume({:cup, 2.0})
# => 2.0
Implementa il metodo KitchenCalculator.to_milliliter.
Data una coppia di volume sotto forma di tupla, deve convertire il volume in millilitri usando la tabella di conversione.
KitchenCalculator.to_milliliter({:cup, 2.5})
# => {:milliliter, 600.0}
Implementa il metodo KitchenCalculator.from_milliliter.
Data una coppia di volume sotto forma di tupla e l'unità desiderata, deve convertire il volume nell'unità desiderata usando la tabella di conversione.
KitchenCalculator.from_milliliter({:milliliter, 1320.0}, :cup)
# => {:cup, 5.5}
Implementa il metodo KitchenCalculator.convert.
Data una coppia di volume sotto forma di tupla e l'unità desiderata, deve convertire il volume dato nell'unità desiderata.
KitchenCalculator.convert({:teaspoon, 9.0}, :tablespoon)
# => {:tablespoon, 3.0}
Iscriviti a Exercism per imparare e padroneggiare Crystal con 26 concetti142 esercizi e il mentoring di persone reali, tutto gratis.