« Tcl: Manipolazione delle liste • Appunti di Tcl/Tk • Tcl: I socket »
Dopo le liste e le stringhe, terminiamo lo studio dei tipi di dati in Tcl con gli array. Gli array in Tcl sono associativi. Sono simili alle liste in quanto sono una collezione di stringhe o elementi. Per un certo verso rappresentano una generalizzazione delle liste. Infatti in una lista ad ogni elemento è associata una posizione espressa da un numero, che permette di accedere al valore di quell'elemento tramite il comando lindex(n). In un array associativo similmente ad ogni valore di un elemento è associato un nome (una stringa arbitraria), che pure permette di accedere quell'elemento. In questo caso il nome della variabile che è un elemento dell'array si ottiene utilizzando il seguente formato:
arrayName(elementName)
Le ( ) in Tcl prendono il posto dell'operatore di subscript [ ] del C. Una volta definito almeno un elemento di un array, esiste anche la variabile array nel suo complesso che si indica semplicemente con arrayName. Risulta spesso utile fare riferimento ad un intero array. Ad esempio una procedura potrebbe importare tramite global(n) con un solo argomento un intero array dallo spazio globale:
proc myProc {...} { global arrayName ... ... arrayName(...) ... ... }
Questa tecnica viene spesso preferita all'uso di variabili globali distinte, in quanto riduce la possibilità di collisione tra nomi di moduli diversi che sono uniti insieme a formare un'applicazione e rende più comodo aggiungere nuove variabili globali da condividere tra più procedure: basta aggiungere elementi all'array associativo e non si deve fare nient'altro. Usando delle variabili globali distinte, può capitare, quando si aggiunge una variabile globale da condividere, di dimenticarsi di aggiornare tutti i comandi global nelle procedure che devono utilizzarla. Questo può introdurre un baco di cui è difficile rendersi conto, in quanto il settaggio della nuova variabile in una procedura che non l'ha importata produce una variabile locale con lo stesso nome: cambiando questa variabile locale i cambiamenti non si rifletteranno in quella globale come invece si voleva. L'uso di un array associativo riduce la possibilità di incappare in questi errori. In questo modo gli array associativi vengono usati in modo simile alle variabili di tipo struttura del linguaggio C, che invece sono assenti in Tcl.
In alcuni linguaggi di più basso livello e di sistema come il C (ma anche Basic e Fortran), gli array associativi non sono disponibili direttamente, ma cablati nel linguaggio sono solo gli array con indici numerici (detti semplicemente array). Questo perché l'uso degli array associativi ha un maggior costo computazionale rispetto a quelli numerici. In Tcl ci sono solo array associativi e si devono usare gli array associativi anche per simulare quelli numerici, quindi si paga sempre questo costo ulteriore, che tuttavia in molti casi è in pratica trascurabile.
Finora abbiamo visto solo come creare gli array e come accedere ai loro elementi. La manpage array(n) descrive tutti gli ulteriori comandi disponibili sugli array. Di seguito vengono descritti solo i più importanti.
array set arrayName list
Questo comando rappresenta una sintassi alternativa per assegnare uno o più elementi di un array. Esso converte la lista list in assegnazioni di uno o più elementi di un array associativo di nome arrayName. Se la variabile arrayName non esiste già e la lista list è vuota, arrayName viene creato come un array vuoto. Se la lista list non è vuota, deve consistere di un numero pari di elementi. Ciascun elemento dispari della lista (vale a dire con gli indici 0, 2, 4, 6, ecc) viene usato come una chiave (ovvero il nome dell'elemento o indice) dell'array arrayName, mentre l'elemento seguente (di indice dispari come 1, 3, 5, 7, ecc) nella lista rappresenta il nuovo valore associato a quella chiave nell'array associativo.
Come esempio convertiamo la lista dei pianeti del nostro sistema solare con le loro distanze medie dal sole in un array associativo indicizzato con i nomi (in italiano) dei pianeti:
$ array set distanzaPianeti { Mercurio 57.9 Venere 108.2 Terra 149.6 Marte 227.9 Giove 778.3 Saturno 1427 Urano 2870 Nettuno 4497 Plutone 5900 } % puts $distanzaPianeti(Terra) 149.6
array get arrayName ?pattern?
È il comando inverso di array set e converte una parte di un array associativo (al limite l'intero array) in una lista. La lista che ritorna contiene sequenze di due elementi; il primo elemento di una coppia (uno degli elementi di indici 0, 2, 4, 6, ecc.) è una chiave (il nome di un elemento di arrayName) e il secondo (il corrispondente elemento di indice 1, 3, 5, 7, ecc.) è il valore dell'elemento dell'array. L'ordine delle coppie è indefinito, in quanto in un array associativo non viene mantenuto un ben definito ordine tra le chiavi. Se non viene specificato alcun pattern, la lista che ne risulta includerà tutti gli elementi dell'array. Se viene specificato un pattern, vengono inclusi solo quegli elementi i cui nomi fanno corrispondenza col pattern (vengono usate le regole di corrispondenza definite per string match). Viene ritornata la lista vuota, se arrayName non è il nome di una variabile di tipo array, oppure se l'array non contiene nessun elemento.
Continuando l'esempio precedente (output spezzato su più linee per comodità):
% array get distanzaPianeti Saturno 1427 Nettuno 4497 Urano 2870 Marte 227.9 Venere 108.2 Giove 778.3 Terra 149.6 Mercurio 57.9 Plutone 5900 % array get distanzaPianeti Terra Terra 149.6 % array get distanzaPianeti *no Saturno 1427 Nettuno 4497 Urano 2870
Altro esempio: tra le variabili globali predefinite nell'interprete, c'è l'array tcl_platform, che contiene alcune informazioni fondamentali sulla piattaforma hardware/software su cui gira Tcl. Il frammento di codice seguente stampa a video una semplice rappresentazione dell'intero array, convertendolo prima in una lista e poi iterando su questa lista tramite foreach(n):
foreach {key val} [array get tcl_platform] { puts $key=$val }
Due esempi di output:
osVersion=4.9-RELEASE byteOrder=littleEndian machine=i386 platform=unix os=FreeBSD user=ant wordSize=4
osVersion=4.10 byteOrder=littleEndian machine=intel platform=windows os=Windows 95 user=ant wordSize=4
In particolare la chiave platform consente di selezionare diverse sezioni di codice da eseguire a seconda della piattaforma (interpretazione condizionale):
switch $tcl_platform(platform) { unix { ... } windows { ... } macintosh { ... } }
array exists arrayName
Ritorna 1 se arrayName è una variabile di tipo array. Ritorna 0 se non esiste una variabile con quel nome o se è piuttosto una variabile scalare.
% array exists distanzaPianeti 1
array size arrayName
Ritorna una stringa che indica il numero degli elementi nell'array arrayName. Qualora arrayName non sia il nome di un array, ritorna 0
% array size distanzaPianeti 9
array names arrayName ?mode? ?pattern?
Ritorna la lista di tutte le chiavi (i nomi degli elementi) dell'array arrayName che corrispondono al pattern specificato (o semplicemente di tutte le chiavi se il pattern viene omesso). Se non ci sono elementi nell'array (che soddisfano la corrispondenza col pattern), oppure se arrayName non è una variabile array, ritorna una stringa vuota. Per default la corrispondenza viene fatta come col comando string match, ma il parametro mode permette di cambiare le regole di corrispondenza. Per maggiori dettagli su questo punto vedere la manpage array(n).
In questi esempi, notate che l'ordine in cui sono restituite le chiavi non è definito:
% array names distanzaPianeti Saturno Nettuno Urano Marte Venere Giove Terra Mercurio Plutone % array names distanzaPianeti *no Saturno Nettuno Urano
foreach key [array names tcl_platform] { puts $key=$tcl_platform($key) }
Oltre che usare il comando foreach(n) in combinazione coi comandi array names oppure array get come abbiamo visto negli esempi, esiste un terzo modo per iterare attraverso il contenuto di un array associativo. Questa modalità utilizza i quattro sottocomandi di array(n) descritti brevemente di seguito, detti comandi di iterazione. Questa ulteriore modalità esiste perché è più efficiente rispetto agli altri due metodi, soprattutto nel caso di grossi array di cui si vuole iterare su tutti o quasi tutti gli elementi. Inoltre, mentre il metodo foreach è più facile da usare per realizzare un ciclo da eseguire tutto all'interno di una singola procedura, questo metodo con i comandi di iterazione è più appropriato quando l'iterazione è distribuita tra diverse procedure (che usano lo stesso searchId, ad esempio questo gli viene passato come parametro per valore).
array startsearch arrayName
Inizializza una ricerca elemento-per-elemento attraverso l'array arrayName e ritorna un identificatore della ricerca (searchId o search identifier) che dovrà essere passato agli altri comandi di iterazione. Sullo stesso array si possono avere in corso differenti iterazioni: ciascuna iterazione ha il suo searchId che ne individua lo stato corrente.
array nextelement arrayName searchId
Ritorna il nome (o indice) del prossimo elemento in una iterazione su arrayName, oppure una stringa vuota se tutti gli elementi di arrayName sono stati già visitati da questa iterazione.
array anymore arrayName searchId
Ritorna 1 se ci sono ancora elementi da processare in una ricerca su un array (che sarebbero ritornati dal comando array nextelement). Ritorna 0 se tutti gli elementi sono già stati visitati. Questo comando risulta particolarmente utile nel caso in cui un array ha un elemento avente per chiave la stringa vuota, in quanto in questo caso non è possibile distinguere il caso in cui la ricerca è terminata da quello in in cui il prossimo elemento è quello con indice "" tramite il comando array nextelement.
array donesearch arrayName searchId
Deve essere chiamato quando considerate terminata una ricerca. Distrugge tutte le informazioni di stato associate con la ricerca. Ritorna la stringa vuota.
In tutti i comandi eccetto ovviamente array startsearch, l'argomento searchId identifica la ricerca e deve essere un valore che è stato ritornato dal comando startsearch.
Attenzione: se vengono aggiungi o cancellati elementi dall'array, tutte le ricerche in corso verranno automaticaticamente terminate, come se fosse stato invocato il comando array donesearch; questo significa che se provate ad eseguire il comando array nextelement su una ricerca dopo una modifica dell'array, questo fallisce e ritorna la stringa vuota (analogamente array anymore ritorna 0, anche se ci sarebbero altri elementi non esplorati nell'array).
Un esempio col suo output (ricordo che l'ordine dei pianeti può anche essere diverso nel vostro caso):
puts [format "%-10s %s\n" pianeta distanza] set sId [array startsearch distanzaPianeti] while {[array anymore distanzaPianeti $sId]} { set key [array nextelement distanzaPianeti $sId] puts [format {%-10s %6.1f} $key $distanzaPianeti($key)] }
pianeta distanza Saturno 1427.0 Nettuno 4497.0 Urano 2870.0 Marte 227.9 Venere 108.2 Giove 778.3 Terra 149.6 Mercurio 57.9 Plutone 5900.0
Non essendoci pianeti che hanno per nome la stringa nulla, l'iterazione sull'array distanzaPianeti può essere scritta anche in questo modo:
puts [format "%-10s %s\n" pianeta distanza] set sId [array startsearch distanzaPianeti] while {[set key [array nextelement distanzaPianeti $sId]] != ""} { puts [format {%-10s %6.1f} $key $distanzaPianeti($key)] }
I searchId in questo caso sono stringhe del tipo: s-1-distanzaPianeti, s-2-distanzaPianeti, ecc., ma questo è un dettaglio dipendente dall'implementazione e lo rileviamo solo per curiosità.
« Tcl: Manipolazione delle liste • Appunti di Tcl/Tk • Tcl: I socket »