« Tcl: Manipolazione delle stringheAppunti di Tcl/TkTcl: Manipolazione degli array »

Tcl

manipolazione delle liste

Come avevamo anticipato, Tcl ha come strutture dati solo stringhe, liste e array e come tipo unico tipo di dati solo le stringhe. Le liste infatti sono rappresentate tramite stringhe. Una lista è una collezione ordinata di elementi; ogni elemento di una lista viene considerato come una singola unità, anche se risulta composto da più parole o addirittura da parecchie linee di testo. Gli elementi di una lista vanno separati tramite spazi bianchi. I caratteri di quotatura { } possono essere innestati e usati per delimitare un unico elemento di una lista che ha all'interno spazi bianchi. I caratteri "" assolvono alla stessa funzione, con la differenza che all'interno si effettuano le sostituzioni, ma non possono essere innestati. Tramite l'uso di { } è facile innestare una lista dentro l'altra. Una lista vuota viene denotata da un paio di parentesi graffe che non racchiudono niente {}, oppure come una stringa nulla "".

Persino un comando Tcl è una lista: il primo elemento è il nome del comando, mentre gli argomenti del comando sono i membri successivi della lista.

Per creare delle liste potete utilizzare il comando set(n), in quanto un'intera lista non è altro che una stringa e i suoi elementi sono opportune sottostringhe, delimitate tramite {} o "", che tuttavia sono necessarie solo nei casi di ambiguità.

Come abbiamo detto l'ordine degli elementi in una lista è significativo (e questo dettaglio la distingue da un'altra struttura altrettanto importante in matematica e in informatica, l'insieme). Ad esempio questa è la lista dei nomi dei pianeti nel nostro sistema solare (avendo trascurato i pianetini minori), elencati in ordine di distanza media crescente dal sole:

% set pianeti {Mercurio Venere Terra Marte Giove Saturno Urano Nettuno Plutone}
Mercurio Venere Terra Marte Giove Saturno Urano Nettuno Plutone

Quest'altra lista contiene come elementi delle liste di due elementi i cui elementi sono il nome del pianeta in italiano e la distanza media dal sole (in milioni di km):

% 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}
}

 {Mercurio 57.9}
 {Venere 108.2}
 {Terra 149.6}
 {Marte 227.9}
 {Giove 778.3}
 {Saturno 1427}
 {Urano 2870}
 {Nettuno 4497}
 {Plutone 5900}

Un'alternativa per rappresentare questi dati è l'array associativo:

set distanzaPianeti(Mercurio) 57.9
set distanzaPianeti(Venere) 108.2
set distanzaPianeti(Terra) 149.6
set distanzaPianeti(Marte) 227.9
set distanzaPianeti(Giove) 778.3
set distanzaPianeti(Saturno) 1427
set distanzaPianeti(Urano) 2870
set distanzaPianeti(Nettuno) 4497
set distanzaPianeti(Plutone) 5900

Anche se non strettamente necessario, è meglio usare il comando list(n) per creare delle liste, in quanto assicura che gli elementi vengano raggruppati nel modo voluto.

list ?arg arg ...?

list(n) ritorna una lista i cui elementi sono i singoli argomenti oppure la lista vuota (stringa vuota) se non viene specificato alcun argomento. In questo modo la stringa risultante comprenderà solo uno spazio tra ogni elemento:

% set distanzaPianeti [list \
 {Mercurio 57.9} \
 {Venere 108.2}  \
 {Terra 149.6}   \
 {Marte 227.9}   \
 {Giove 778.3}   \
 {Saturno 1427}  \
 {Urano 2870}    \
 {Nettuno 4497}  \
 {Plutone 5900}  \
]
{Mercurio 57.9} {Venere 108.2} {Terra 149.6} {Marte 227.9} {Giove 778.3} {Saturno 1427} {Urano 2870} {Nettuno 4497} {Plutone 5900}

Inoltre list(n) aggiunge i backslash e le graffe necessarie per distinguere ogni elemento della lista (che è un singolo argomento di list(n)) da un'altro, senza che il programmatore debba preoccuparsene:

% list puts "hello world"
puts {hello world}
% set quotes [list {""} "{}"]
{""} {{}}
% lindex $quotes 0
""
% lindex $quotes 1
{}
% set quotes [list "{" "}" {"}]
\{ \} {"}
% lindex $quotes 0
{
% lindex $quotes 1
}
% lindex $quotes 2
"

Un uso molto importante del comando list consiste nel costruire un comando Tcl da far eseguire all'interprete tramite eval(n). Infatti un comando Tcl può essere visto come una lista il cui primo elemento è il nome del comando e tutti gli altri elementi rappresentano gli argomenti del comando. Usando list(n) per costruire il comando a partire dai suoi elementi, anziché utilizzare l'interpolazione delle stringhe, si è sicuri che le graffe e i backslash necessari per distinguere correttamente i vari argomenti vengono aggiunti automaticamente, altrimenti dovete farlo voi e nel caso di stringhe generiche che possono essere incluse nel codice ad esempio come valori dei parametri la cosa non è affatto semplice.

Il comando lindex tratta il primo argomento stringa come una lista Tcl e ritorna l'elemento numero index della lista. Il primo elemento ha indice 0. Se index è fuori dai limiti, ossia è negativo oppure maggiore o uguale al numero degli elementi della lista, verrà ritornata la stringa vuota. Come con il comando string index, index può assumere il valore end, che indica l'ultimo elemento della lista, oppure end-intero che si riferisce all'ultimo elemento della lista meno l'offset intero specificato.

lindex list index

Quando lindex estrae un elemento, effettua le stesse sostituzioni dell'interprete Tcl, ad eccezione della sostituzione delle variabili e dei comandi che non vengono effettuate.

llength(n) conta quanti elementi vi sono in una lista. Ritorna una stringa che contiene un numero intero.

llength list

% lindex $distanzaPianeti 2
Terra 149.6
% lindex [lindex $distanzaPianeti 2] 1 ;#quanto dista la Terra dal Sole?
149.6
% llength $pianeti ;#quanti pianeti ha il nostro Sistema Solare?
9

Supponiamo di voler iterare su tutti gli elementi di una lista per fare qualche elaborazione su ciascun elemento, ad esempio semplicemente stamparlo. Possiamo farlo con un ciclo for(n) e una variabile indice:

% for {set i 0} {$i < [llength $pianeti]} {incr i} {
    puts [lindex $pianeti $i]
}
Mercurio
Venere
Terra
Marte
Giove
Saturno
Urano
Nettuno
Plutone

Questa è una operazione molto frequente. Siccome il ciclo for non è molto leggibile, Tcl ha un comando apposito per iterare su tutti gli elementi di una lista, foreach(n):

foreach varname list body

foreach(n), per ogni elemento della lista list, assegna il valore dell'elemento alla variabile varname (come se fosse stato usato il comando lindex per recuperare un elemento dalla lista) e chiama l'interprete Tcl ricorsivamente per eseguire il codice body.

Riscriviamo l'esempio precedente con foreach(n) anziché for(n) e notiamo che il comando foreach(n) è più compatto e leggibile; inoltre è quasi il doppio più veloce del ciclo for(n) equivalente:

% puts $pianeta
can't read "pianeta": no such variable
% foreach pianeta $pianeti {puts $pianeta}
Mercurio
Venere
Terra
Marte
Giove
Saturno
Urano
Nettuno
Plutone
% puts $pianeta
Plutone

All'interno di un ciclo foreach(n) è possibile utilizzare i comandi break(n) e continue(n), che hanno lo stesso effetto che avevano nel ciclo for(n). foreach(n) ritorna la stringa nulla.

Il comando split(n) rappresenta un altro modo per creare delle liste, oltre a quelli già visti tramite i comandi set(n) e list(n).

split string ?splitChars?

split(n) ritorna una lista i cui elementi sono ottenuti spezzando la stringa string ogni volta che si incontra uno dei caratteri contenuti nella stringa splitChars. SplitChars per default contiene solo caratteri di spaziatura. Se la stringa string contiene in fila alcuni caratteri di splitChars, oppure se il primo o l'ultimo carattere della stringa è un carattere di splitChars verranno generate delle stringhe vuote come elementi della lista ritornata da split(n), in quanto ciascuno dei caratteri appartenenti alla stringa splitChars viene considerato come un separatore di elementi della lista. Se splitChars viene specificata ed è una stringa vuota, allora viene costruita una lista avente per elementi i singoli caratteri della stringa string.

% split Mercurio,Venere,Terra,Marte,Giove,Saturno,Urano,Nettuno,Plutone ,
Mercurio Venere Terra Marte Giove Saturno Urano Nettuno Plutone
% split 26-04-1977 -
26 04 1977
% split Roma {}
R o m a

Attenzione che quando split spezza in corrispondenza degli spazi bianchi, se vi sono dei caratteri come { o } li quota col backslash, per evitare che altri comandi per le liste interpretino le graffe come operatori di raggruppamento:

% set lista [split "a b {c d e} {f {g h}}"]
a b \{c d e\} \{f \{g h\}\}
% foreach elem $lista {puts $elem}
a
b
{c
d
e}
{f
{g
h}}

Oltre che comandi per creare una lista ed accedere ai suoi elementi, sono a disposizione comandi per modificare le liste, ad esempio per aggiungere o rimuovere elementi da una lista.

linsert list index element ?element element ...?

linsert(n) ritorna una nuova lista a cui sono stati aggiunti i nuovi elementi nell'ordine specificato, proprio prima dell'elemento della lista list di indice index. L'indice si può specificare in tre modi, esattamente come col comando lindex. Se index è minore o uguale a zero, i nuovi elementi verranno inseriti comunque all'inizio della lista. Se index è end oppure è maggiore o uguale del numero degli elementi della lista, i nuovi elementi verranno appesi alla lista.

% set sistemaSolare [linsert $pianeti 0 Sole]
Sole Mercurio Venere Terra Marte Giove Saturno Urano Nettuno Plutone
% set distanzeSistemaSolare [linsert $distanzaPianeti 0 {Sole 0}]
{Sole 0} {Mercurio 57.9} {Venere 108.2} {Terra 149.6} {Marte 227.9} {Giove 778.3} {Saturno 1427} {Urano 2870} {Nettuno 4497} {Plutone 5900}

lappend varName ?value value value ...?

lappend(n) appende nuovi elementi, ciascuno individuato da un singolo argomento value, alla lista varName, separandoli tramite degli spazi. Se la lista varName non esiste ancora, verrà creata come una lista contenente i soli elementi specificati.

Per appendere elementi ad una lista potete anche utilizzare il comando set(n) o append(n), appendendo la stringa che rappresenta i nuovi elementi, ma in questo modo dovete preoccuparvi di delimitare i singoli elementi, mentre con lappend(n) no. Quindi lappend(n) risulta più comodo, oltre che più leggibile e più veloce.

lreplace list first last ?element element ...?

Ritorna una nuova lista ottenuta sostituendo gli elementi di list nell'intervallo first, last (estremi inclusi) con gli argomenti element. Se first è minore o uguale a zero, lreplace(n) inizia a sostituire dal primo elemento della lista, che esiste sicuro nel caso di lista non vuota. Se la lista è vuota, i parametri first e last vengono ignorati e viene restituita una lista formata dagli elementi element. Per le liste non vuote, è richiesto che l'elemento indicato dal parametro first esista, altrimenti si verifica un errore. end si riferisce all'ultimo elemento della lista. Se last è minore di zero ma maggiore di first, allora gli elementi specificati saranno inseriti in testa alla lista. Se invece last è minore di first, i nuovi elementi verranno inseriti prima di first e non verrà cancellato alcun elemento. Se vengono specificati meno argomenti element rispetto al numero di posizioni tra first e last, allora gli elementi che si trovano nelle posizioni per cui non sono stati specificati i nuovi elementi vengono cancellati. In questo modo lreplace(n) può essere utilizzata per cancellare elementi da una lista; in particolare se non viene specificato alcun argomento element, allora gli elementi tra first e last vengono semplicemente cancellati.

concat ?arg arg ...?

Concatena ciascun argomento, considerato come una lista, in una lista singola che restituisce. Inoltre, elimina eventuali spazi in testa e in coda negli argomenti arg (ma solo in testa e in coda) ed usa un solo spazio separatore tra ciascun argomento. Il numero degli argomenti può essere qualsiasi. Se non vi sono argomenti, restituisce una stringa vuota.
% set pianetiDensi {Mercurio Venere Terra Marte}
Mercurio Venere Terra Marte
% set pianetiGioviani {Giove Saturno Urano Nettuno Plutone}
Giove Saturno Urano Nettuno Plutone
% set pianeti [concat $pianetiDensi $pianetiGioviani]
Mercurio Venere Terra Marte Giove Saturno Urano Nettuno Plutone

lset varName ?index...? newValue

Nella sua forma più semplice e tipica, quando viene specificato un solo valore indice index, questo comando sostituisce l'index-esimo elemento della variabile varName considerata come una lista con il nuovo valore newValue. La nuova lista così ottenuta, oltre ad essere memorizzata nella variabile varName, viene anche ritornata dal comando lset. index parte da 0 e può essere end o end-intero. Un index negativo o maggiore o uguale del numero degli elementi in $varName produce un errore. In altri termini, il comando lset non può aggiungere elementi alla fine della lista. Per maggiori informazioni consultare la manpage lset(n).

join list ?joinString?

Ritorna una stringa costruita concatenando le stringhe che rappresentano i valori di tutti gli elementi della lista list, utilizzando la stringa joinString per separare ciascuna coppia di elementi adiacenti. Se omessa joinString assume il valore di default di una stringa costituita da un solo carattere spazio.
% join $pianeti ,
Mercurio,Venere,Terra,Marte,Giove,Saturno,Urano,Nettuno,Plutone
% join $distanzaPianeti ", "
Mercurio 57.9, Venere 108.2, Terra 149.6, Marte 227.9, Giove 778.3, Saturno 1427, Urano 2870, Nettuno 4497, Plutone 5900

TODO: lsearch, lsort, lrange.

« Tcl: Manipolazione delle stringheAppunti di Tcl/TkTcl: Manipolazione degli array »