Lavorare con le liste: quando il gioco si fà duro… (parte 2)

6 08 2009

google_translate.gif Translate To English!

Avete “digerito” la prima parte? Ok, allora proseguiamo! Nella prima parte abbiamo parlato anche degli array, gestendoli però con alcuni semplici metodi standard. newLisp però prevede delle funzioni (di cui alcune già viste la volta scorsa) per la manipolazione di liste-array. Prima di tutto, facciamo un “brutale” copia/incolla dal manuale, e vediamo quali funzioni abbiamo a nostra disposizione:

append Appende gli array
array Crea ed inizializza un array fino a 16 dimensioni
array-list Converte un array in una lista
array? Controlla se una espressione è un array
det Ritorna il determinante di una matrice
first Ritorna la prima riga di un array
invert Ritorna l’inversa di una matrice
last Ritorna l’ultima riga di un array
mat Esegue operazioni scalari su matrici
multiply Moltiplica due matrici
nth Ritorna un elemento di un array
rest Ritorna tutti gli elementi di un array tranne il primo
setf Set (imposta) i contenuti di un array per reference
slice Ritorna uno slice (una parte, un sottoinsieme) di un array
sort Mette in ordine gli elementi di un array
transpose Traspone una matrice

Alcune note importanti:

  1. Gli array multidimensionali vengono gestiti come array di array.
  2. Quando usato in modo interattivo, gli array sono mostrati come liste, e non c’è nessun modo per distinguerli.
  3. Gli array possono essere non-rettangolari, ma quando vengono serializzati diventano rettangolari.
  4. La funzione array crea sempre array rettangolari.
La nota più importante di tutte: gli array così creati sono veri array, e non liste! Questo significa che non si possono applicare su di essi tutte le funzioni che normalmente si applicano sulle liste, e che gli array possono fornire “out-of-bounds error” (cosa che non può accadere con le liste).

E allora quando dobbiamo usare gli array? Vanno usati quando si devono gestire GRANDI quantità di dati, ad accesso casuale, i quali risultano troppo lenti per essere gestiti tramite le liste. Per inizializzare un array dobbiamo procedere in questo modo:

> (array 2 3)
((nil nil nil) (nil nil nil))

Se volessimo inserire un elemento all’interno durante la sua creazione, aggiungiamolo con una lista:

> (array 2 3 '(1))
((1 1 1) (1 1 1))
> (array 2 3 (sequence 1 6))
((1 2 3) (4 5 6))
> (array 2 3 '("X"))
(("X" "X" "X") ("X" "X" "X"))

Per inserire un valore nell’array possiamo ricorrere alla funzione setf, che inserirà il valore “by-reference” (quindi è molto veloce):

> (setq mio (array 2 3 '(".")))
(("." "." ".") ("." "." "."))
> (setf (mio 0 1) "X")
"X"
> mio
(("." "X" ".") ("." "." "."))
> (setf (mio 1 2) "Z")
"Z"
> mio
(("." "X" ".") ("." "." "Z"))
Ricordatevi che gli indici partono sempre da zero!

Possiamo anche lavorare con array in altri array:

> (setf (mio 1 0) '(A B C))
(A B C)
> mio
(("." "X" ".") ((A B C) "." "Z"))
> (setf (mio 1 ) '(X Y Z))
(X Y Z)
> mio
(("." "X" ".") (X Y Z))

Nell’ultimo caso ho sostituito un intera dimensione dell’array (in termini di contenuti) con un’altra! Ed ora guardate qui:

> (setf (mio 3 ) '(W A S D))
ERR: array index out of bounds in function setf : 3

Ho cercato di inserire un elemento in un posto inesistente, ed ho ottenuto un Out of bounds. Questo perchè la funzione (setf), lavorando per reference, deve necessariamente lavorare su elementi esistenti (non si può referenziare qualcosa che non esiste!).

Come posso estrarre i dati di una riga? Ed una cella singola? Guardate questi esempi:

> (setq mio (array 4 3 (sequence 0 11)))
((0 1 2) (3 4 5) (6 7 8 ) (9 10 11))
> (mio 1 1)
4
> (mio 1)
(3 4 5)

> (mio -1) ;; INDICE NEGATIVO: LEGGO L'ULTIMO ELEMENTO!
(9 10 11)

Si possono anche estrarre “pezzi” di array, tramite la funzione (slice):

> (slice mio 1 2)
((3 4 5) (6 7 8))

Per essere certi di lavorare con degli array, possiamo usare la funzione (array?):

> (setq mio (array 3 2 (sequence 1 6)))
((1 2) (3 4) (5 6))
> (array? mio)
true
> (array? (array-list mio)) ;; CONVERTO L'ARRAY IN LISTA
nil

> (list? (array-list mio)) ;; CONVERTO L'ARRAY IN LISTA
true

Invece se si vuole convertire una lista in un array, dobbiamo usare la funzione (flat) che si occuperà di “appiattire” la lista e darla in pasto al generatore di array:

> (setq mioArray (array 2 3 (flat lista)))
((1 2 3) (4 5 6))
> (setq mioArray (array 3 2 (flat lista)))
((1 2) (3 4) (5 6))

E’ interessante notare come questo sistema permetta di “distribuire” i valori nell’array, senza preoccuparsi della “forma” dell’array stesso nè della forma della lista.

Prima di concludere (ma la terza parte di questo workshop è già alle porte ;-)  ) vediamo le funzioni (source) (save). Esse permettono di serializzare espressioni, simboli, context, etc… e, ovviamente, array!

Facciamo subito una prova:

> (setq mioArray (array 3 2 (sequence 1 6)))
((1 2) (3 4) (5 6))

> (println (source 'mioArray))
(set 'mioArray (array 3 2 (flat '(
  (1 2)
  (3 4)
  (5 6)))))

"(set 'mioArray (array 3 2 (flat '(\n  (1 2) \n  (3 4) \n  (5 6)))))\n\n"

Ho aggiunto la funzione (println) per comodità di lettura. La funzione (source) genera tutto l’indispensabile per ricrare l’elemento da un’altra parte, o per salvarlo. La funzione (source) fà la stessa cosa, ma salva il codice generato direttamente su file.

Ora pensate al potenziale di queste funzioni, unite alla (net-eval).

Come dite? Non sapete cosa è la (net-eval)?! Ne parleremo in un prossimo workshop, insieme alla programmazione distribuita. Promesso!





Lavorare con le liste: quando il gioco si fà duro… (parte 1)

2 08 2009

google_translate.gif
Translate To English!

Come ormai ben sapete, le liste sono l’elemento cardine di newLisp. Le liste sono validi sostituti degli array, delle hash tables, degli array associativi, etc… Molti linguaggi di programmazione infatti complicano la vita al programmatore inserendo diversi tipi di dato con, ovviamente, funzioni ad-hoc per il loro uso. In newLisp la situazione è diversa: la base di tutto sono le liste. E la cosa più importante è che le funzioni necessarie per il loro uso sono semplicemente perfette. In questo workshop faremo meglio la loro conoscenza. Vi accorgerete quanto sono flessibili. Si inizia! Prima di tutto realizzeremo dei classici: cioè vedremo come implementare (e gestire!) gli stacks (LIFO) e le code (FIFO).

Lavorare con uno stack.

Cosa è uno stack LIFO (Last In First Out, cioè l’ultimo elemento che entra è il primo ad uscire)? Allora, prendete dei libri e poneteli ad uno ad uno all’interno di una scatola, uno sopra all’altro, formando una pila. In cima, dove si trova ilcoperchio è l’unica apertura. Potete facilmente verificare che l’ultimo libro posto sulla pila (messo sopra tutti gli altri), sarà il primo libro che potrete riprendere: LIBRO 3 <— Cima della pila (ultimo libro posto sulla pila) LIBRO 2 LIBRO 1 <— Fondo della pila La regola per implementare uno stack è che i libri non si possono prendere dal mezzo (libro 2), nè dal fondo della pila (libro 1). Per creare un oggetto come questo si usano, ovviamente, li liste ;-) Qui c’è tutta la flessibilità di newLisp, infatti una lista-stack è una comune lista, ma noi la gestiremo  in modo tale da farla comportare come uno stack. Per inserire dati nello stacca usiamo la funzione (push), mentre per estrarre elementi useremo (pop). Ecco un esempio:

> (setq scatola '())
()
> (push 'libro1 scatola)
(libro1)
> (push 'libro2 scatola)
(libro2 libro1)
> (push 'libro3 scatola)
(libro3 libro2 libro1)
>

Notate come l’ultimo libro inserito, libro3, si trova in cima alla lista (primo elemento della lista). Ora preleviamo gli elementi:

> (pop scatola)
libro3
> (pop scatola)
libro2
> (pop scatola)
libro1
> scatola
()
>

La funzione pop preleva l’ultimo elemento inserito. Notate che, ad ogni chiamata pop, gli elementi vengono fisicamente eliminati dalla lista (scatola). Al termine delle tre chiamate pop, la scatola sarà vuota. Semplicissimo!

Lavorare con una coda.

Le code sono contenitori di tipo FIFO (First In First Out). Immaginate un tubo. Esso avrà due “porte di accesso”: una per l’ingresso (le palline entreranno da questa porta), ed una per l’uscita:

ENTRATA –> pallina-rossa –> pallina-verde –> pallina blu –> USCITA

La pallina blu è la prima ad essere entrata (First In) e sarà la prima ad uscire (First Out).
Come implementarlo in newLisp?

> (setq tubo '())
()
> (push 'pallina-blu tubo)
(pallina-blu)
> (push 'pallina-verde tubo)
(pallina-verde pallina-blu)
> (push 'pallina-rossa tubo)
(pallina-rossa pallina-verde pallina-blu)
>

Uso la stessa funzione push per inserire le palline nel tubo. Ora come le prelevo?

> (pop tubo -1)
pallina-blu
> (pop tubo -1)
pallina-verde
> (pop tubo -1)
pallina-rossa
> tubo
()

L’indice “-1″ usato nella funzione pop indica di prelevare l’ultimo elemento della lista, e non il primo. Abbiamo quindi realizzato un contenitore molto diverso dallo stack, ma abbiamo usato gli stessi elementi (una lista, la funzione pop e la push). Semplice, flessibile, efficace.

Creare e manipolare un array.

Cosa è un array? Immaginate un corridoio, molto lungo, con tantissime porte, una dietro all’altra, tutte numerate (0, 1, 2, 3, etc…). Ogni porta dà accesso ad una piccola stanza, in cui depositare qualcosa. Potete mettere oggetti nelle stanze facendo riferimento al numero della stanza. Lo stesso vale per prelevare oggetti da esse. In molti linguaggi di programmazione, gli array hanno una lunghezza fissa, e talvolta possono essere ridimensionati (ma ciò comporta diversi svantaggi: in alcuni linguggi il ridimensionamento viene fatto RICOPIANDO tutti gli elementi in un nuovo array, con grande spreco di risorse e di tempo).
In newLisp gli array possono essere gestiti con… le liste ovviamente! La parte interessante è che abbiamo molteplici strumenti per farlo (funzioni), ognuno con caratteristiche particolari. Sono veramente tante le strade che si possono percorrere per gestire un array, ed alcune di esse si mescolano con il concetto di liste, permettendo quindi una gestione molto più flessibile e dinamica degli array classici. Io vi mostrerò solo le funzioni inerenti ad una gestione tipica di un array, mentre in seguito vedremo come far “esplodere” le vere potenzialità di newLisp e delle sue liste!
Partiamo con un esempio:

> (setq mio-array '(1 2 3 4 5 6 7 8 9 10))
> (setf (mio-array 2) 'X)

(1 2 X 4 5 6 7 8 9 10)

Prima di tutto creiamo una lista con i numeri da 1 a 10. Poi, usando la funzione setf, metto il simbolo X nella stanza numero 2 (cioè la terza stanza del corridoio).
NOTA: il primo numero della stanza vale “0″ (zero), e non “1″. Quindi la stanza “2″ è la terza stanza del corridoio (il linguaggio C, C++, Java, etc… usano tutti questa stessa notazione).
NOTA 2: il valore inserito ha sovrascritto il precedente! Questo è il funzionamento di un array.
Normalmente in un array, leggendo il suo valore, non si elimina tale valore (come invece accade negli stack e nelle code). Vediamo quindi come fare.

> mio-array
(1 2 X 4 5 6 7 8 9 10) <--- mio-array contiene questi valori
> (mio-array 1)        <--- chiedo di leggere il valore contenuto nella stanza numero "1"
2
> (mio-array 2)        <--- chiedo di leggere il valore contenuto nella stanza numero "2"
X
> (mio-array 3)        <--- chiedo di leggere il valore contenuto nella stanza numero "3"
4

Oppure posso usare quest’altro modo (usando la funzione nth):

> mio-array
(1 2 X 4 5 6 7 8 9 10)
> (nth 2 mio-array)
X
> (nth 3 mio-array)
4

Vorrei mettere in evidenza anche quest’altra forma:

> mio-array
(1 2 X 4 5 6 7 8 9 10)
> (2 mio-array)    <--- Ottengo i valori della stanza "2" e seguenti.
(X 4 5 6 7 8 9 10)
> (5 mio-array)    <--- Ottengo i valori della stanza "5" e seguenti.
(6 7 8 9 10)

In quest’ultimo caso vengono letti TUTTI i valori a partire dalla stanza indicata.

Ma come potrei gestire gli array multidimensionali? La risposta è semplice: le liste sono dei “contenitori” di simboli o… altre liste. Un array con n-dimensioni può essere facilmente rappresentato come delle matrici ad n-dimensioni. Le matrici sono facilmente rappresentabili con le liste…

Matrice = [ [1 2] [3 4] ]

Questa matrice la posso rappresentare con un array “tradizionale”, in questo modo:

Matrice[0, 0] = 1
Matrice[0, 1] = 2
Matrice[1, 0] = 3
Matrice[1, 1] = 4

Ecco come scriverlo in newLisp:

> (setq Matrice '( (1 2) (3 4) ) )
((1 2) (3 4))

Come possiamo accedere ai suoi elementi?

> (Matrice 0)
(1 2)
> (Matrice 1)
(3 4)
> (Matrice 1 0)
3
> (Matrice 1 1)
4

Ed ora, che abbiamo visto come implementare gli array con le liste, prima di concludere questo workshop, vi dò una bella notizia: in newLisp esistono gli array! Quelli veri! Li vedremo nella seconda puntata… per ora è tutto! (per quelli che non sanno aspettare, leggete qui – in inglese -)

A presto!





Usare newLisp nella shell di Linux

1 05 2009

google_translate.gif Translate To English!

Salve a tutti,
dopo innumerevoli sofferenze sono riuscito finalmente a “cacciare via” Windows Vista dal mio PC, ed ora finalmente lavoro esclusivamente con Linux, sia sul mio desktop che sul notebook (che uso anche in ufficio).

Me la cavo nell’uso di Linux (faccio avanti e indietro tra Windows ed il Pinguino da un bel pò), ma non mi posso certo dichiarare un esperto.
Uno dei problemi che ho trovato è stato l’uso della bash per creare script per gli usi più disparati. Inoltre mi è sempre dispiaciuto “perdere tempo” ad imparare qualcosa limitato ad un solo sistema (sono un sostenitore dei sistemi cross-platform). Ccosì mi sono detto: invece di usare la bash per le IF, FOR, etc… perché non usare newLisp, ed usare i comandi shell solo dove necessario?
Detto fatto!
Eccomi qui quindi a fornire una piccola “guida” ed alcuni consigli sull’argomento.

La possibilità e la flessibilità che offre la command-line di Linux (e dei sistemi Un*x in genere) è devastante, incredibile, magnifica. Ci si può letteralmente fare di tutto!
Una cosa che mi ha sempre affascinato è che, se propriamente configurato, uno script può diventare un comando vero e proprio (al contrario di Windows in cui si devono creare “scripts” in files BAT – batch). Inoltre, in Linux, posso scegliere tra una grande varietà di shell, le quali si preoccuperanno di interpretare i comandi inseriti. Ma non solo: se non volessi usare una shell (esempio la bash, oppure csh, etc…) posso usare un vero linguaggio di scripting come python , perl, e…  newLisp ovviamente!

I passi da fare sono pochi e molto semplici:

Apriamo una sessione terminale (quasi tutte le versioni di Linux hanno un menu da cui richiamare tale shell). A questo punto dovremmo trovarci nella nostra cartella (folder) nella directory home (esempio: /home/alessandro). Potete controllare “la vostra posizione” digitando il comando pwd (print working directory). Se per esempio vi siete loggati come “alessandro”, allora dovreste avere qualcosa simile a:

alessandro@alessandro@alessandro1 ~ $ pwd
/home/alessandro

Per evitare di fare danni (dobbiamo fare solamente delle prove per ora…), creiamo una nuova sottodirectory (se non ce l’avete già!) e chiamatela temp, e poi ci andiamo dentro (cd = change directory):

mkdir temp
cd temp

Ora controlliamo dove si trova l’interprete newLisp installato (lo avete già installato ovviamente… vero?!).
Digitate il comando which newlisp:

>> which newlisp
/usr/bin/newlisp

Otterrete il percorso completo del comando, che nel nostro caso corrisponde a /usr/bin/newlisp

A questo punto tirate fuori il vostro editor preferito, e create un nuovo file chiamato lista, e scriviamoci il seguente script:#!/usr/bin/newlisp

#!/usr/bin/newlisp

(if (= (length (main-args)) 3)
    (! (append "ls " (main-args 2 ) ) )
    (! "ls")
    );if

(exit)

Per poterlo eseguire dobbiamo “spiegare” a Linux che si tratta di un file eseguibile. Potete farlo sia usando l’interfaccia grafica (se usate ambienti come Gnome, KDE, etc…) probabilmente potrete cliccare sul file con il pulsante destro, e poi selezionerete il flag “file eseguibile” (o simile). Ma siccome noi siamo dei veri hackers (e la tastiera è il prolungamento delle nostre dita!) lo faremo da console. Il comando è il seguente:

chmod 744 lista

Per i dettagli sull’uso del comando potete leggere qui (è in italiano!).
Il file appena creato è MOLTO semplice: legge la linea comando (i parametri passati al comando “lista”); se non ci sono argomenti (nessun parametro passato) allora mostra la lista dei files nella directory corrente, altrimenti legge il parametro come directory la mostrare.

Analizziamo le parti più interessanti del codice:

#!/usr/bin/newlisp

Questo è chiamato sha-bang ed è ciò che permette a Linux di eseguire “la magia”: i due simboli #! sono la rappresentazione testuale di due bytes che rappresentano il magic-number del file: Linux (e Un*x in genere) usa il magic-number di un file per capire di che tipo di file si tratta (quello che normalmente in Windows è invece fatto tramite l’estensione del file).Questo significa che Linux non ha bisogno di mettere l’estensione ai files per capire di cosa si tratta (potete quindi prendere una immagine e chiamarla immagine.txt e Linux la aprirà con il corretto visualizzatore, Gimp per esempio). Ciò che segue il magic-number è l’interprete da usare per eseguire lo script sottostante (newLisp nel nostro caso).
Quindi non è assolutamente detto che gli script in Linux debbano essere fatti con l’interprete shell (bash, csh, etc…). Per esempio molte distribuzioni Linux usano quintali di script in Python per svolgere i più disparati compiti di gestione e amministrazione.

Molto interessante è la funzione (!). Essa infatti ci permette di eseguire comandi del sistema operativo. Il “problema” è che in questo modo non riusciamo però a determinare l’esito del comando, nonchè i valori ritornati. Se avete questa necessità, potete utilizzare la funzione (exec). Essa infatti ritornerà i dati che normalmente vengono redirezionati nella console output. Quindi un comando ls (come quello che abbiamo usato nel nostro script), ci darebbe un risultato simile al seguente:

> (exec "ls /")
("bin" "boot" "cdrom" "dev" "etc" "home" "initrd.img" "lib" "lost+found" "media"
 "mnt" "opt" "proc" "root" "sbin" "srv" "sys" "tmp" "usr" "var" "vmlinuz")

La funzione ritorna il contenuto della directory root come una lista di valori.Provate anche il comando seguente:

> (exec "ls -l /")
("total 88" "drwxr-xr-x   2 root root  4096 2009-05-01 01:01 bin" "drwxr-xr-x   4 root root  4096 2009-05-01 01:06 boot"
 "lrwxrwxrwx   1 root root    11 2009-04-30 23:09 cdrom -> media/cdrom" "drwxr-xr-x  15 root root 14240 2009-05-01 12:02 dev"
 "drwxr-xr-x 132 root root 12288 2009-05-01 12:02 etc" "drwxr-xr-x   3 root root  4096 2009-04-30 23:12 home"
 "lrwxrwxrwx   1 root root    32 2009-04-30 23:16 initrd.img -> boot/initrd.img-2.6.27-7-generic"
 "drwxr-xr-x  16 root root 12288 2009-05-01 01:01 lib" "drwx------   2 root root 16384 2009-04-30 23:09 lost+found"
 "drwxr-xr-x   3 root root  4096 2008-10-29 23:53 media" "drwxr-xr-x   2 root root  4096 2008-10-20 14:27 mnt"
 "drwxr-xr-x   4 root root  4096 2009-05-01 01:59 opt" "dr-xr-xr-x 172 root root     0 2009-05-01 11:04 proc"
 "drwxr-xr-x  13 root root  4096 2009-05-01 11:25 root" "drwxr-xr-x   2 root root  4096 2009-05-01 01:01 sbin"
 "drwxr-xr-x   2 root root  4096 2008-10-29 23:53 srv" "drwxr-xr-x  12 root root     0 2009-05-01 11:04 sys"
 "drwxrwxrwt  15 root root  4096 2009-05-01 12:33 tmp" "drwxr-xr-x  13 root root  4096 2009-05-01 01:13 usr"
 "drwxr-xr-x  14 root root  4096 2008-11-06 01:13 var" "lrwxrwxrwx   1 root root    29 2009-04-30 23:16 vmlinuz -> boot/vmlinuz-2.6.27-7-generic")

Magnifico! Ottengo in una lista tutti i dettagli sulla directory! Ora è facilissimo fare un parse dei dati e produrre risultati ben più complessi! Potremmo, per esempio, commare tutte le dimensioni dei files e mostrarle alla fine.

Il resto del programma è semplicemente newLisp.

NOTA: ricordatevi di terminare lo script con la funzione (exit), altrimenti al termine dell’esecuzione del comando vi ritroverete nella console newLisp!

Fin qui tutto bene: però io, poco tempo fà, nella scrittura di uno script mi sono trovato con il problema di dare comandi con privilegi di amministratore (dovevo montare dei drive con il protocollo Samba). Avrei potuto semplicemente usare il comando sudo mount … ma digitare il comando sudo ci obbliga ad inserire, la prima volta, la password. Questa operazione potrebbe rivelarsi più complicata del previsto se eseguiamo il nostro nuovo programma direttamente dall’interfaccia grafica (senza mostrare il terminale). E allora, come possiamo fare?
Possiamo ricorrere al comando gksudo (per Gnome, XFCE, etc…) oppure kdesu per KDE: in questo modo vi verrà mostrata una finestra (nell’ambiente grafico), nascondendo così completamente il terminale.

Inoltre, se proprio volete trasformare il vostro script in una applicazione grafica, potete ricorrere anche al programma zenity. Esso permette di creare componenti visuali dalla shell, ed il risultato tornerà alla shell stessa. Per esempio, digitando il comando:

>> zenity --calendar

Verrà mostrato in accordo con la GUI corrente, un grazioso calendario. Non appena l’utente sceglie da data, il programma la ritornerà nella console. In questo modo potremmo permettere all’utente di scegliere visualmente, con il mouse, informazioni utili, senza l’uso (e la visualizzazione) della console stessa.
Installate zenity, e provate ad eseguire la funzione seguente dalla shell di newLisp:

>> (append "Data selezionata: " ( (exec "zenity --calendar") 0) )

Verrà mostrato il calendario, e poi newLisp mostrerà la data scelta.

Bene, per ora è tutto, spero che queste informazioni possano esservi utili come lo sono state a me!

A presto!