-+  Documenti
 |-  Bibliografia
 |-  Articoli
 |-  Perlfunc
 |-  F.A.Q.
 |-  F.A.Q. iclp
-+  Eventi
-+  Contatti
-+  Blog
-+  Link
R: Tags



 

Versione stampabile.


Michele Beltrame
Michele Beltrame vive a Maniago, PN, è programmatore Perl e convinto sostenitore del movimento Open Source. PuĆò essere raggiunto via e-mail all'indirizzo arthas@perl.it

Data di pubblicazione: Questo articolo è stato pubblicato su Dev nĀ°80 e in questo sito per autorizzazione espressa del "Gruppo Editoriale Infomedia" (http://www.infomedia.it).

© 2007 Michele Beltrame.

1. Introduzione 

2. Parametri non scalari 

3. Funzioni 

4. Introduzione alle regular expression 

5. Operatori interni all'espressione 

6. Quantificatori 

7. Parentesi e quantificatori minimali 

8. Conclusioni 

Introduzione

Nella scorsa lezione sono state illustrate le reference e le subroutine. Le prime sono risultate utili per passare dei parametri alle subroutine facendo in modo che fosse possibile modificarne il valore dall'interno della subroutine chiamata. Ciò che ancora manca da analizzare è come passare alle subroutine parametri non scalari (ad esempio array ed hash) e come ottenere da esse un valore di ritorno, argomenti che tratteremo tra poche righe. Inizieremo inoltre ad imparare l'utilizzo di alcune caratteristiche di Perl dedicate all'elaborazione di dati, in particolare di testi o comunque di strutture leggibili dall'uomo. Verranno infatti introdotte le regular expression, tramite le quali, superato lo shock dell'apprendimento iniziale, è possibile effettuare l'analisi di testi in maniera comoda e veloce.

 Parametri non scalari

Dovrebbe ormai essere chiaro come passare un parametro scalare ad una subroutine. Ma come fare a passare ad esempio un array? A prima vista può sembrare logico procedere come segue:

sub mysub {
  my @a  = @_;
  print @a;
}

&mysub (@mioarray);

Ragionando un momento si capisce subito che questo codice non funziona come desiderato, in quanto i parametri costituisco un array essi stessi e quindi, ponendo che @mioarray contenga tre elementi, la chiamata vista sopra è di fatto equivalente a:

&mysub ($mioarray[0], $mioarray[1], $mioarray[2]);

L'array passato è quindi visto come una lista di parametri anziché come uno unico. Ci vengono fortunatamente in aiuto le reference. È sufficiente infatti scrivere:

&mysub (\@mioarray);

e verrà correttamente passato l'array a mysub(). Più precisamente ciò che la procedura si ritrova come parametro è una reference, quindi la subroutine dovrà essere modificata come segue:

sub mysub {
  my $aref= shift;
  my @a  = @$aref;
  print @a;
}

Allo stesso modo è possibile passare ad una funzione un hash, oppure più di uno, o un array ed un hash, oppure ancora un array ed una lista di scalari. Ad esempio potremmo avere:

&mysub (\@mioarray, $miastringa, \%miohash);

I parametri possono essere poi recuperati all'interno della subroutine con:

($aref, $stringa, $href) = split;

A questo punto l'utilità delle reference è chiara: senza di esse non sarebbe possibile l'implementazione di alcune strutture dati quali gli array multidimensionali e sarebbe precluso un passaggio di parametri come quello visto poc'anzi. In realtà anche prima che Perl 5 introducesse queste strutture era possibile ovviare, in maniera sicuramente più scomoda, utilizzando ad esempio i typeglob, che ora servono solo in rari casi.

 Funzioni

In tanti casi è necessario poter ottenere un valore di ritorno da una subroutine. Se ad esempio dovessimo elevare un certo numero al cubo potremmo volere utilizzare una sintassi tipo:

$a = 3;
$b = &cubo($a);

Se invece lo scopo fosse quello di effettuare un controllo ortografico su una stringa potremmo voler scrivere:

# Torna 0 se OK, 1 se errore
$a = 'libro';
$b = &controlla($a);

Nel primo caso l'obiettivo è quello di ottenere il cubo di un numero senza alterare il valore della variabile originale, mentre lo scopo del secondo listato è quello di avere un responso da una subroutine che effettua un determinato controllo. Per questo scopo esistono le funzioni, che in realtà altro non sono che subroutine in tutto e per tutto, con la differenza che all'interno di esse è inserita un'istruzione che permette di ritornare un valore al codice chiamante. Vediamo ad esempio come potrebbe essere implementata la funzione di elevazione al cubo:

sub cubo {
  my ($n) = shift;
  my ($c) =  $n*$n*$n;
  return $c;
}

L'istruzione chiave è return, che ha un duplice effetto: termina immediatamente l'esecuzione della subroutine e ritorna un valore (il contenuto di $c in questo caso) al codice chiamante. Qualsiasi riga di codice posta sotto un return non viene quindi eseguita, e si potrebbe infatti migliorare l'efficienza di cubo() in questo modo:

sub cubo {
  my ($n) = shift;
  if ($n == 0) {
    return;
  }
  my ($c) =  $n*$n*$n;
  return $c;
}

In questo caso il primo utilizzo di return è senza parametro, il che fa sì che la funzioni torni il valore indefinito (undef) se utilizzata in contesto scalare, un array vuoto se utilizzata in contesto di lista, e nulla se utilizzata fuori contesto (cioè come una subroutine). È permesso specificare un array oppure un hash come valore di ritorno, sempre utilizzando le reference:

return \%miohash;

Intuitivamente, all'interno del codice chiamante la variabile non scalare può essere ottenuta con:

$href = &myfunc();
%h = %$href;

 Introduzione alle regular expression

Le regular expression (spesso chiamate semplicemente regexp e solo a volte tradotte in espressioni regolari) rappresentano un potentissimo strumento per la manipolazione di dati: stringhe, numeri oppure anche dati binari. Una di esse a prima vista può sembrare un incomprensibile fila di simboli senza senso, mentre in realtà un minimo di studio permette di comprenderne almeno i caratteri generali, e di rendersi conto della loro potenza. Con una regular expression è possibile sintetizzare in una sola riga di codice ciò che altrimenti ne potrebbe richiedere svariate decine. Iniziamo da un'espressione di media difficoltà, limitandoci unicamente a mostrarla per il momento:

/^([\w\-\+\.]+)@([\w\-\+\.]+).([\w\-\+\.]+)$/

Questa regular expression controlla (con qualche limitazione) la validità sintattica di un indirizzo e-mail. Essa risulterà incomprensibile a molti, ma la sua visione è necessaria a far capire a cosa ci si trova solitamente davanti quando si legge del codice Perl, e ad infondere nel lettore una sensazione mista di fascino e di angoscia. Partiamo ora dalle cose semplici:

if ($a =~ m/Anna/) {
  # Fa qualcosa
}

Il codice di cui sopra altro non fa che controllare se $a contiene da qualche parte il nome Anna. Anzitutto va notato che l'operatore che lega la variabile all'espressione è un uguale seguito da una tilde (=~). La regular expression in questo caso è un po' più leggibile:

m/Anna/

La prima lettera indica il tipo di operazione: ci sono parecchie possibilità, che verranno analizzate in seguito. Per ora limitiamoci a m, che indica matching, cioè ricerca di una stringa all'interno di un'altra. Esso è l'operatore più utilizzato nelle regular expression, e può quindi essere omesso del tutto:

/Anna/

L'espressione logica ($a =~ m/Anna/) assume valore vero nel caso in cui Anna sia contenuta in un punto qualsiasi di $a, e falso in tutti gli altri casi. Alcune situazioni in cui il valore assunto è vero sono:

$a = 'Anna';
$a = 'Sono andato con Anna al cinema';
$a = 'Anna1111';
$a = '1111Anna';

Un caso particolare in cui l'espressione risulta falsa è:

$a = 'anna';

Come la comparazione normale infatti, anche quella tramite regular expression è case sensitive.

 Operatori interni all'espressione

Esiste tutta una serie di caratteri speciali utilizzabili all'interno delle espressioni: una lista di quelli più generici è disponibile in tabella 1.

SimboloDescrizione
\...
Escape per i caratteri speciali
...|...
Alternanza (or)
(...)
Gruppo
[...]
Classe di caratteri
^
Cerca ad inizio stringa
$
Cerca a fine stringa
.
Trova un carattere qualsiasi

Ad esempio:

/^Anna/

restringe la ricerca del pattern (cioè la sottostringa) al solo inizio della stringa, quindi l'esito sarà positivo solo nel primo e nel terzo caso dei quattro esposti sopra. Naturalmente è possibile limitare la ricerca solo alla parte finale della stringa con:

/Anna$/

Ora la regular expression darà responso positivo solo nel quarto caso. È fondamentale notare che gli operatori ^ e $ causano questo comportamento solo se inseriti rispettivamente all'inizio ed alla fine dell'espressione: in una diversa posizione o contesto hanno tutt'altra funzione. Possono anche essere utilizzati entrambi:

/^Anna$/

In questa specifica situazione il matching con regular expression ha lo stesso significato di una normale comparazione tramite eq. Un carattere fondamentale è il backslash, che ha la stessa funzione che esso ricopre all'interno dei doppi apici ("?"), e cioè è un escape per i caratteri speciali, che fa sì che essi vengano non vengano considerati per il loro significato; esso è inoltre un escape per il delimitatore dell'espressione (/, se non diversamente specificato). Ad esempio:

/Anna\$/

cerca effettivamente il pattern Anna$. L'operatore di alternanza (|) equivale ad un or, pertanto la seguente espressione:

/Anna|Camilla/

ha esito positivo se la stringa contiene il pattern Anna oppure Camilla oppure entrambi. Tramite le parentesi è possibile raggruppare parti di espressione, e quindi creare pattern più complessi:

/(Anna|Camilla|Michele) va al cinema/

Un matching con quest'espressione è positivo se la stringa in cui si effettua la ricerca contiene una delle seguenti sottostringhe:

Anna va al cinema
Camilla va al cinema
Michele va al cinema

L'operatore fondamentale di ricerca generica è il punto (.): esso trova un carattere qualsiasi ad eccezione del newline (salvo diversamente specificato). Ad esempio:

/^.nna$/

trova una stringa di quattro caratteri che finisce per nna, ed è quindi comodo per trovare sia Anna che anna. Si tenga però presente che per questo scopo il punto non è consigliabile (in quanto troverebbe anche ad esempio 5nna), e che in seguito verranno illustrate delle migliori alternative.

 Quantificatori

Analizziamo ora i quantificatori, cioè gli operatori che permettono di decidere quante volte un determinato carattere/stringa deve essere presente nella stringa in cui effettuiamo la ricerca: essi sono visibili in tabella 2

QuantificatoreDescrizione
*
Trova 0 o piĆ¹ volte (massimale)
+
Trova 1 o piĆ¹ volte (massimale)
?
Trova 1 o 0 volte (massimale)
{n}
Trova esattamente n volte
{n,}
Trova almeno n volte (massimale)
{n,m}
Trova almeno n volte ma non piĆ¹ di m volte (massimale)
*?
Trova 0 o piĆ¹ volte (minimale)
+?
Trova 1 o piĆ¹ volte (minimale)
??
Trova 1 o 0 volte (minimale)
{n,}?
Trova almeno n volte (minimale)
{n,m}?
Trova almeno n volte ma non piĆ¹ di m volte (minimale)

ed il loro utilizzo avviene secondo la seguente modalità:

/^(Anna){3}$/

In questo esempio il matching ha successo solo se la stringa contiene il pattern Anna ripetuto tre volte consecutive senza alcuna spaziatura e senza altri caratteri né ad inizio né a fine stringa (AnnaAnnaAnna). Gli altri operatori funzionano allo stesso modo:

# OK se Anna compare almeno 2 volte
/^(Anna){2,}$/
# OK se Anna compare dalle 2 alle 4 volte
/^(Anna){2,4}$/
# OK se Anna compare almeno una volta
/^(Anna)+$/
# OK se Anna compare 0 o più volte (sempre OK in questo caso)
/^(Anna)*$/

Situazioni più complesse si possono analizzare combinando i vari operatori e quantificatori, ad esempio:

/^An{2}a.+cinema$/

Osservando bene questa espressione regolare si nota anzitutto che il matching avviene da inizio a fine stringa, (ci sono ^ e $). In secondo luogo viene richiesto che dopo la A ci siano esattamente due n seguite da una a, a sua volta seguita da un numero arbitrario di caratteri qualsiasi (che però devono essere almeno uno) prima che la stringa sia terminata dalla parola cinema. Possibili matching con esito positivo sono quindi:

Anna va al cinema
Anna è andata al cinema
Anna andrà al cinema

Un esempio ancora più complesso può essere:

/^(An{2}a|Camilla|Michel{1,2}e).+and.+(cinema|concerto)$/

Oscena a vedersi, l'espressione ha risultato positivo, tra le altre, per le seguenti stringhe:

Michele andò al concerto
Michelle è andata al concerto
Camilla andrà al cinema

 Parentesi e quantificatori minimali

In un'espressione regolare le parentesi hanno una duplice funzione: quella di raggruppamento e quella di estrarre parte del pattern, memorizzandolo in una variabile. Poniamo di avere il seguente codice:

$a = 'Anna va al cinema';
$a =~ /^(.+)va (.+)$/;
print "$1\n";
print "$2\n";

L'output di questo programmino è rappresentato dal nome della persone (Anna in questo caso) e dal luogo dove va (al cinema). Il contenuto delle parentesi viene infatti memorizzato in variabili dal nome $n, dove n ha base 1, andando da sinistra verso destra secondo l'ordine di apertura delle parentesi. Nel caso si abbia quindi un set di parentesi dentro l'altro, la numerazione parte comunque da quello più esterno, in quanto aperto prima. A questo punto qualcuno si starà però chiedendo a cosa servano gli operatori minimali di tabella 2. Poniamo di avere la seguente stringa:

$a  =  'disse "ci vediamo" e lei chiese "quando?"';

e di voler estrarre il contenuto della prima coppia di doppi apici. A occhio potrebbe andare bene la seguente espressione:

$a =~ /"(.+)"/;

In effetti tutto funzionerebbe se il set di apici fosse solo uno, ma nel nostro caso $1 contiene:

ci vediamo" e lei chiese "quando?

che non è esattamente quello che stiamo cercando. Questo avviene perché gli operatori sono "ingordi" (traduzione dell'inglese greedy) e quindi il + tende a prendere più caratteri possibili prima che venga analizzata la porzione successiva dell'espressione, tornando sui suoi passi solo nel caso il matching di tale porzione successiva fallisca. Ecco quindi chiaro lo scopo degli operatori minimali, o "non ingordi", che prendono invece meno caratteri possibile e si ottengono aggiungendo un punto di domanda dopo l'operatore massimale. Dunque utilizzando:

$a =~ /"(.+?)"/;

otteniamo regolarmente:

ci vediamo

 Conclusioni

Benché al primo impatto risultino assai ostiche, le regular expression sono uno strumento potentissimo per il programmatore. Ciò che si è visto in questo articolo è solo la punta dell'iceberg di quanto si può fare utilizzandole: nella prossima lezione vedremo infatti matching molto più complessi e, tra le altre cose, apparirà finalmente chiaro il funzionamento della prima espressione vista in questo articolo. Studieremo poi come effettuare sostituzioni di parti di stringhe ed altre operazioni più complesse, sempre naturalmente utilizzando le regular expression e riducendo quindi operazioni che solitamente richiedono molto codice ad una sola riga.

  1. Ellen Siever, Stephen Spainhour, Nathan Patwardhan - "Perl in a Nutshell", O'Reilly & Associates, Gennaio 1999
  2. Larry Wall, Tom Christiansen, Randal L. Schwartz - "Programming Perl (2nd edition)", O'Reilly & Associates, Settembre 1996
  3. Jon Orwant, Jarkko Hietaniemi, John Macdonald - "Mastering algorithms with Perl", O'Reilly & Associates, Agosto 1999


Ti è piaciuto questo articolo? Iscriviti al feed!











Devo ricordare i dati personali?






D:
Annunci Google