Go - from Google


Mappe      

La mappe (il termine inglese corrispondente è maps, come troverete nella letteratura inglese) sono un altro costrutto fondamentale in Go. Da un punto di vista teorico non sono una novità, troviamo in molti linguaggi elementi del tutto simili, magari con altri nomi (dictionaries, hash tables ecc...).
Praticamente si tratta di una sequenza non ordinata di coppie costituite da una chiave a cui corrisponde un altro elemento detto valore. Evidentemente il vantaggio è che conoscendo la chiave si può ricavare molto rapidamente il valore corrispondente. La capacità di una mappa è limitata solo dalla capacità della macchina in uso (anche se esiste il modo, come vedremo, per fissare un tetto iniziale). Ogni chiave deve essere univoca e deve essere di un tipo che supporta gli operatori == e != (uno slice, ad esempio, non va bene), come avviene anche per altri linguaggi. Per i valori invece abbiamo piena libertà (anche puntatori e altre mappe se si vuole) ed è possibile quindi sbizzarrirsi nell'uso di queste elastiche strutture.

Formalmente la dichiarazione, che usa della keyword map, è la seguente:

var map1 map[tipo della chiave]tipo del valore

es:

var map1 map[int]string

Come si vede non è necessario fornire alcuna informazione relativamente alle dimensioni, una mappa può crescere in maniera dinamica. Se non inizializzata la mappa ha valore nil. Vediamo un primo semplice esempio:

  Esempio 9.1
1
2
3
4
5
6
7
8
package main
import "fmt"

func main() {
  var map01 map[string]int
  map01 = map[string]int{"uno" : 1, "due" : 2}
  fmt.Println(map01["uno"])
}

Le righe 5 e 6 mostrano rispettivamente la creazione e il popolamento di una mappa. La 7 invece ci insegna il metodo per accedere ad un singolo elemento ovvero attraverso il solito operatore []. E' possibile modificare successivamente la nostra mappa:

map01["tre"] = 3

aggiunge un altro valore. Se la chiave indicata è già presente l'sitruzione di assegnazione sovrascrive il contenuto preesistente. Come detto le chiavi devono essere univoche per cui non potremmo ad esempio modificare la riga 6 nell'esempio 9.1 qualcosa come segue:

map01 = map[string]int{"uno" : 1, "uno" : 2}

perchè il compilatore si farebbe sentire molto chiaramente:

duplicate key "uno" in map literal

Prima di procedere ecco un altro esempio che mostra la varietà di elementi che è possibile usare all'interno di una mappa, come nell'esempio seguente dove usiamo, in luogo dei valori, delle funzioni:

  Esempio 9.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import "fmt"

func somma(x int, y int) int {
var z int = 0
z = x + y
return z
}

func main() {
var map01 map[int]int
map01 = map[int]int{1:somma(2,3), 2:somma(4,5)}
fmt.Println(map01[2])
}

Possiamo anche per mappe identificare alcune operazioni fondamentali:

m[x] = val attribuisce il valore val all'elemento x di m, sostituendo quello eventualmente già presente
delete(m, x) elimina la chiave x ed il valore corrispondete da m. Se la chiave x non è presente non fa nulla. Vedi nota (1) di seguito
y := m(x) Ovviamente inizializza y col valore della mappa m corrispondente all'indice x. Altrimenti, se la chiave è assente piazza un valore 0. Vedi nota (2).
len(m) Indica la lunghezza, ovvero il numero delle coppie chiave valore della mappa m
y, valbool := m[x] assegna a y il valore corrispondente alla chiave x e a valbool, che è un booleano naturalmente, il valore true se la chiave x esiste nella mappa m, altrimento rispettivamente 0 (ancora nota (2)) e false

nota (1) - detesto che il linguaggio in casi come questi, o meglio il runtime, si limiti a non fare nulla in modo silente. Comportamento altrove riscontrato che, ripeto, detesto trovandolo molto bug oriented. Ma, dicono, va bene così... In fondo, se lo scopo è cancellare un elemento e questo già non esiste il risultato è raggiunto... si ma questo non mi pone al riparo da errori banali che risultano poi difficili da scoprire. A meno, ovviamente, di non ricorrere a controlli a mio avviso un po' pedanti....
nota (2) - e chi glielo ha detto che voglio quel valore? Vedi sopra. Tra l'altro che differenza c'è se un elemento non esiste oppure esiste e vale 0?

Per ovviare un po' a questi problemi come detto bisogna essere un po' pignoli. Come avrete capito, una strada concettualmente semplice è effettuare un test come indicato nella tabella di sopra. Non so se sia il massimo dell'efficienza ma è molto naturale, come strada. Vediamo l'esempio:

  Esempio 9.3
1
2
3
4
5
6
7
8
9
10
package main
import "fmt"

func main() {

var map01 map[int]int
map01 = map[int]int{1:1, 2:1}
var _, valbool = map01[1]
fmt.Println(valbool)
}

In questo caso valbool testa la presenza di una certa chiave. Il risultato true o false viene riversato in valbool mentre, come evidenzia l'underscore, non ci interessa attribuire per forza un valore ad una variabile. Una volta effettuato il test in questo modo si può procedere basando la nostra azione sul valore booleano.

Anche per le mappe possiamo usare la chiara sintassi basata sulla funzione make:
 
make(map[tipochiave], tipovalore)
make(map[tipochiave], tipovalore, capacitàiniziale)


la seconda definizione evidentemente inserisce una capacità predeterminata iniziale per la mappa. Questo è utile per questione di performance, in particolare in presenza di mappe di grosse dimensioni, in quanto lo spazio viene allocato immediatamente invece di avere la necessità di ulteriori allocazioni man mano che vengono aggiunti elementi.

L'esempio seguente invece mostra fare per effetturare un loop sugli elementi di una mappa stampando dapprima la coppia chiave + valore, poi le chiavi e infine solo i valori.

  Esempio 9.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import "fmt"

func main() {

var map01 = map[string]int{"uno":1, "due":2, "tre":3}
for key, value := range map01 {
  fmt.Println(key, value)
}
for key := range map01 {
  fmt.Println(key)
}
for _,value := range map01 {
  fmt.Println(value)
}
}

Si noti l'uso di _ alla riga 13.
Come avrete notato nei vari esempio le stampe che si ottengono variano un po' per quanto riguarda l'ordine degli elementi. Questo perchè, come detto, una mappa non è ordinata e la memorizzazione non è sequenziale rispetto all'ordine da noi adottato. E' certamente possibile effettuare una operazione di ordinamento ma questa deve essere costruita in modo non proprio immediato e lo vedremo, se avrò tempo, nella sezione degli esempi. Come intuibile è necessario ordinare esternamente le chiavi e poi estrarre i valori.
Le mappe sono molto utili, comode e performanti, come detto strutture simili sono presenti in quasi tutti i linguaggi moderni. Un uso abbastanza elegante, ad esempio, tra i tanti possibili, è quello di selettori invece della classica sequenza di if... else if... In pratica se abbiamo una selezione ad esempio di questo tipo:

if scelta 1 {
a
} else if scelta 2 {
b
}

può essere tradotto tramite una mappa

map[tipo-scelta]tipo-a-b{scelta 1: a, scelta 2: b}