Home Rhyylen
Contatto
Programmazione
 
 
Crystal Language
Capitolo 12
Tuple

Una tuple (che di seguito chiamerò ennupla) è una collezione immutabile, indicizzata e allocata nello stack di elementi di tipo diverso. Molto simile ad un array ma, come detto immutabile anche nella documentazione ufficiale viene definito come array immutabile. Formalmente tra l'altro sta a metà tra un hash e, appunto, un array:

{elemento-1, elemento-2, ... elemento-n}
ovvero una sequenza di elementi separati da una virgola all'interno di una coppia di parentesi graffe, ad esempio:


t01 = {1, 'a', 0.1, "aaa"}

Questo è il modo più semplice per definire una ennupla
Oppure si ricorre al solito new passando gli argomenti che vogliamo


t01 = Tuple.new(1, 'a', "aaa")

Se abbiamo un array a disposizione possiamo usare from, anche se come sistema non mi pare molto comodo. Bisogna mappare, con un cast compatibile ovviamente, tutti gli elementi dell'array nella ennupla:

ar1 = [1,2,3,4]
t01 = Tuple(Int32, Int32, Int32, Int32).from(ar1)

L'operatore per estrarre il singolo elemento è il solito [] tenendo presente che anche in questo costrutto vale la solita indicizzazione sequenziale per interi partenti da zero e anche qui è possibile avere una indicizzazione con numeri negativi. Come ormai dovrebbe essere consueto, si può usare l'operatore []? per controllare i casi di indici non validi. Da notare che dal momento che il compilatore in questo caso conosce perfettamente l'allocazione di ogni singolo elemento, selezionare un indice non valido con []  è un evento che viene intercettato a compile time. L'operatore [] ci permette anche di estrarre dei range:

t01 = {1, 2, "aa", 0.1}
x01 = t01[1..3]
puts typeof(x01)

Tuttavia non potete usarlo per sostituire un elemento della ennupla con un altro stante l'immutabilità di questo costrutto.

Interessante è anche il metodo at(indice) che ci permette anche un messaggio personalizzato in caso di indice :

t01 = {1, "aa", 'a'}
puts t01.at(1) {puts "not found"}
puts t01.at(71) {puts "not found"}

output:

aa
not found

Può essere comodo, invece usare questo metodo per attribuire un valore di default ad una variabile in caso di uso di indici scorretti:

t01 = {1, "aa", 'a', 2}
x = t01.at(1)
y = t01.at(5) {10}
puts x
puts y

output:

aa
10


Per controllare la dimensione, ovvero il numero di elementi di una ennupla si usa, come al solito, size, banalmente ad esempio

puts t01.size

in uno dei tanti esempi precedenti.
Per quanto riguarda il resto dei metodi possibili per una ennupla, non ci sono grandi particolarità da segnalare da quanto già visto con le altre sequenze, li vediamo in un esempio e li commentiamo:

  Esempio 12.1
1
2
3
4
5
6
7
8
t01 = {1, "aa", 'a', 2}
t02 = t01.clone
puts t02
puts t01.first
puts t01.last
t03 = t01.map &.to_s
puts t03
puts t01.reverse

  • La riga 2 ci illustra come creare una copia della ennupla
  • Le righe 4 e 5 estraggono il primo e l'ultimo elemento della ennupla. Vanno in errore se la ennupla è vuota e, se esiste questo dubbio, si possono usare le controparti first? e last? che in caso di ennupla vuota restituiscono nil.
  • La riga 6 ci permette invece di costruire una ennupla con i vari elementi mappati in uno di nostra scelta, in questo caso abbiamo costruito t03 in modo che tutti i suoi elementi siano stringhe. Inutile dire che la conversione verso il tipo scelto come destinazione deve essere realizzabile.
  • La riga 8 invece ci come come invertire una ennupla

Se volete iterare tra gli elementi di una ennupla c'è disponibile il solito each e il omologo reverse_each che lavora allo stesso modo all'incontrario:

  Esempio 12.2
1
2
3
4
5
t01 = {1,2,3,1.1}
t01.each do |x|
puts x
puts x * x
end

Ovviamente potete usare anche una delle semplici iterazioni che già conosciamo:

  Esempio 12.3
1
2
3
4
5
6
t01 = {1, "aa", 'a', 5}
x = 0
while x < t01.size
puts t01[x]
x = x + 1
end

Una variante interessante delle ennuple sono le ennuple con nome (Named tuple, in inglese). Queste si distinguono dalle ennuple "normali" per il fatto che ad ogni elemento possiamo associare un nome con cui recuperarlo, questo certamente ci ricorda il meccanismo chiave valore di un hash. Concettualmente si tratta di costrutti molto simili la differenza principale è che un NamedTuple è immutabile ed è noto, nella sua conformazione definitiva, già a compile time. In aggiunta, una NamedTuple è immutabile mentre una hash può essere modificata.

nt01 = {anno: 1990, mese: "agosto"}
puts nt01[:anno]        # 1990
puts nt01[:mese]        # agosto

Davvero molto simile ad un hash. Si possono ricavare chiavi e valori:

puts nt01.keys
puts nt01.values

e si può attraversare nello stesso modo visto per gli hash:

nt01 = {anno: 1990, mese: "agosto"}
nt01.each {|chiave, valore| puts "la chiave #{chiave} ha valore #{valore}" }


Le ennuple le ritroveremo parlando delle funzioni in quanto sono un partner privilegiato di queste per questioni legate alla ottimale gestione della memoria ma soprattutto in quanto struttura perfetta per restituire più parametri di ritorno dalle funzioni stesse come vedremo.