Questa sezione delle FAQ risponde a domande relative alle manipolazione
di numeri, date, stringhe, array, hash, ed a varie questioni sui dati.
Internamente, il vostro computer rappresenta i numeri in virgola mobile
in binario. I computer digitali (che lavorano in potenze di due) non
possono memorizzare tutti i numeri in maniera esatta. Alcuni numeri reali
perdono precisione in questo processo. Questo problema è relativo a come i
computer memorizzano i numeri ed incide su tutti i linguaggi di
programmazione, non solo su Perl.
In perlnumber sono mostrati tutti i minimi dettagli della
rappresentazione e conversione dei numeri.
Per limitare il numero di cifre decimali nei numeri, potete usare la
funzione printf o sprintf. Consultate perlop/``Floating-point
Arithmetic'' per maggiori dettagli. printf "%.2f", 10/3; my $numero = sprintf "%.2f", 10/3;
La funzione int() molto probabilmente sta funzionando
bene. Sono i numeri a non essere esattamente quelli che credete.
Per prima cosa, date un'occhiata alla voce sopra ``Perché ottengo una
lunga serie di decimali (es. 19.9499999999999) invece dei numeri che
dovrei ottenere (es. 19.95)?''.
Per esempio, questo print int(0.6/0.2-2), "\n";
nella maggior parte dei computer stamperà 0, non 1, visto che anche
semplici numeri quali 0.6 e 0.2 non possono essere rappresentati
esattamente da numeri in virgola mobile. Quello che pensate essere sopra
un 'tre' è in effetti una cosa più simile a 2.9999999999999995559.
Perl considera tali i numeri ottali ed esadecimali solo quando
compaiono in maniera letterale all'interno del vostro programma. Gli
ottali letterali in perl devono iniziare con uno ``0'', mentre quelli
esadecimali devono essere preceduti da ``0x''. Se i valori vengono letti
da qualche parte e poi assegnati, non viene effettuata alcuna conversione.
Dovete esplicitamente usare oct() oppure hex()
se desiderate che tali valori siano convertiti in decimale.
oct() interpreta sia numeri esadecimali (``0x350'') che
ottali (``0350'' o anche senza lo zero all'inizio, come ``377''), mentre
hex() converte solo gli esadecimali, con o senza ``0x''
all'inizio, come ``0x255'', ``3A'', ``ff'', oppure ``deadbeef''. La
conversione inversa da decimale ad ottale può essere effettuata con i
formati ``%o'' o ``%O'' di sprintf(). Per convertire da decimale ad
esadecimale provate i formati ``%x'' o ``%X'' di sprintf().
Questo problema si presenta spesso quando si cercano di usare chmod(),
mkdir(), umask(), oppure sysopen(), a cui i permessi vengono forniti in
ottale per diffusa tradizione. chmod(644, $file); # SBAGLIATO
chmod(0644, $file); # corretto
Notate che l'errore nella prima linea è stato quello di specificare il
decimale 644, anziché l'ottale 0644. Il problema può essere meglio
osservato così: printf("%#o",644); # stampa 01204
Sicuramente non intendevate eseguire chmod(01204, $file);
- o sì?
Se volete usare valori numerici come argomenti a chmod() e
simili, cercate di esprimerli come ottali letterali, cioè con uno zero
all'inizio e con le cifre successive limitate all'intervallo 0..7.
Ricordate che int() si limita a troncare verso lo 0. Per
arrotondare a un qualche numero di decimali, la via più facile è di solito
sprintf() o printf(). printf("%.3f", 3.1415926535); # stampa 3.142
Il modulo POSIX (parte della distribuzione standard di Perl) implementa
ceil(), floor() e un certo numero di altre funzioni
matematiche e trigonometriche. use POSIX;
$ceil = ceil(3.5); # 4
$floor = floor(3.5); # 3
Nelle versioni dalla 5.000 alla 5.003 di Perl, la trigonometria veniva
fatta dal modulo Math::Complex. Con la versione 5.004, il modulo
Math::Trig (parte della distribuzione standard) implementa le funzioni
trigonometriche. Usa internamente il modulo Math::Complex e alcune
funzioni potrebbero sfuggire dall'asse dei reali verso il piano dei
complessi, ad esempio il seno inverso di 2.
L'arrotondamento può avere serie implicazioni nelle applicazioni
finanziarie, e il metodo di arrotondamento usato dovrebbe essere
specificato con cura. In questi casi, probabilmente è una buona idea non
fidarsi del sistema usato da Perl, qualunque esso sia, ma implementare la
funzione di arrotondamento per conto vostro.
Per vederne il motivo, notate come, anche con printf,
resti un problema di incertezza sui valori intermedi: for ($i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i} 0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
0.8 0.8 0.9 0.9 1.0 1.0
Non prendetevela con Perl. In C è lo stesso. La IEEE dice che va fatto
così. In Perl, i numeri i cui valori assoluti sono interi inferiori a
2**31 (sulle macchine a 32 bit) lavorano abbastanza similmente agli interi
in matematica. Per gli altri tipi di numeri non c'è garanzia.
Come sempre con il Perl, c'è più di un modo di fare le cose. Più sotto
ci sono alcuni esempi di approcci per effettuare delle comuni conversioni
tra rappresentazioni numeriche. Tutto ciò è da intendersi a titolo
esemplificativo piuttosto che esaustivo.
Alcuni degli esempi qui sotto usano il modulo Bit::Vector da CPAN. La
ragione per cui potreste scegliere Bit::Vector rispetto alle funzioni
incorporate in perl è che lavora con numeri di OGNI dimensione, che è
ottimizzato per la velocità su certe operazioni e che, almeno per qualche
programmatore, la notazione usata potrebbe essere familiare.
- Come
si convertono esadecimali in decimali
- Usando la conversione incorporata in perl della notazione 0x:
$dec = 0xDEADBEEF;
Usando la funzione hex: $dec = hex("DEADBEEF");
Usando pack: $dec = unpack("N", pack("H8", substr("0" x 8 . "DEADBEEF", -8)));
Usando il modulo Bit::Vector da CPAN: use Bit::Vector;
$vec = Bit::Vector->new_Hex(32, "DEADBEEF");
$dec = $vec->to_Dec();
- Come si converte
da decimale ad esadecimale
- Usando sprintf:
$esa = sprintf("%X", 3735928559); # maiuscole A-F
$esa = sprintf("%x", 3735928559); # minuscole a-f
Usando unpack: $esa = unpack("H*", pack("N", 3735928559));
Usando Bit::Vector: use Bit::Vector;
$vet = Bit::Vector->new_Dec(32, -559038737);
$esa = $vet->to_Hex();
Bit::Vector supporta conteggi di bit arbitrari: use Bit::Vector;
$vet = Bit::Vector->new_Dec(33, 3735928559);
$vet->Resize(32); # elimina gli 0 iniziali se non sono voluti
$esa = $vet->to_Hex();
- Come si
converte da ottale a decimale
- Usando la conversione incorporata in perl di numeri con 0 iniziali:
$dec = 033653337357; # notate lo 0 iniziale!
Usando la funzione oct: $dec = oct("33653337357");
Usando Bit::Vector: use Bit::Vector;
$vet = Bit::Vector->new(32);
$vet->Chunk_List_Store(3, split(//, reverse "33653337357"));
$dec = $vec->to_Dec();
- Come si
converte da decimale ad ottale
- Usando sprintf:
$ott = sprintf("%o", 3735928559);
Usando Bit::Vector: use Bit::Vector;
$vet = Bit::Vector->new_Dec(32, -559038737);
$ott = reverse join('', $vet->Chunk_List_Read(3));
- Come si
converte da binario a decimale
- Il Perl 5.6 permette di scrivere numeri binari direttamente con la
notazione 0b:
$numero = 0b10110110;
Usando oct: my $input = "10110110";
$decimale = oct( "0b$input" );
Usando pack e ord: $decimale = ord(pack('B8', '10110110'));
Usando pack e unpack per stringhe di grandi dimensioni: $int = unpack("N", pack("B32",
substr("0" x 32 . "11110101011011011111011101111", -32)));
$dec = sprintf("%d", $int); # substr() e` usata per allungare la stringa con degli zeri
# a sinistra per portarla a 32 caratteri
Usando Bit::Vector: $vet = Bit::Vector->new_Bin(32, "11011110101011011011111011101111");
$dec = $vet->to_Dec();
- Come si
converte da decimale a binario
- Usando sprintf (perl 5.6+):
$bin = sprintf("%b", 3735928559);
Usando unpack: $bin = unpack("B*", pack("N", 3735928559));
Usando Bit::Vector: use Bit::Vector;
$vet = Bit::Vector->new_Dec(32, -559038737);
$bin = $vet->to_Bin();
Le rimanenti trasformazioni (ad es. esa -> ott, bin -> esa,
ecc.) sono lasciate come esercizio al lettore volenteroso.
Il comportamento degli operatori artimetici binari varia a seconda che
vengano utilizzati su numeri o stringhe. Gli operatori trattano una
stringa come una serie di bit e lavorano su di essi (la stringa
"3" è la sequenza di bit 00110011). Gli
operatori lavorano con la forma binaria di un numero (il numero
3 è la sequenza di bit 00000011).
Dunque, con 11 & 3 si esegue l'operazione ``and'' su
numeri (produce 3). Con "11" & "3" si compie
l'operazione ``and'' su stringhe (produce "1").
La maggior parte dei problemi con & e |
nasce poiché i programmatori pensano di avere in mano dei numeri, ma in
realtà hanno delle stringhe. I rimanenti problemi nascono dal fatto che i
programmatori scrivono: if ("\020\020" & "\101\101") {
# ...
}
ma una stringa contenente due byte nulli (il risultato di
"\020\020" & "\101\101") non rappresenta un valore falso
in Perl. Dovete scrivere: if ( ("\020\020" & "\101\101") !~ /[^\000]/) {
# ...
}
Usate i moduli Math::Matrix o Math::MatrixReal (disponibili su CPAN)
oppure l'estensione PDL (anch'essa disponibile su CPAN).
Per chiamare una funzione su ciascun elemento di un array e
collezionarne i risultati, usate: @risultati = map { la_mia_funz($_) } @array;
Per esempio: @triplo = map { 3 * $_ } @singolo;
Per chiamare una funzione su ciascun elemento di un array senza
prenderne in considerazione i risultati: foreach $iteratore (@array) {
una_qualche_funz($iteratore);
}
Per chiamare una funzione su ciascun intero in un (breve) intervallo,
potete usare: @risultati = map { una_qualche_funz($_) } (5 .. 25);
ma dovete essere consapevoli che l'operatore .. crea un
array di tutti gli interi nell'intervallo. Per grandi intervalli, questo
potrebbe portar via molta memoria. Usate invece: @risultati = ();
for ($i=5; $i < 500_005; $i++) {
push(@risultati, una_qualche_funz($i));
}
Questa situazione è stata risolta nel Perl 5.005. L'uso di
.. in un ciclo for itererà sull'intervallo senza
crearlo tutto. for my $i (5 .. 500_005) {
push(@risultati, una_qualche_funz($i));
}
non creerà una lista di 500.000 interi.
Procuratevi il modulo http://www.cpan.org/modules/by-module/Roman
.
Se state usando una versione di Perl antecedente alla 5.004, dovete
chiamare srand una volta, all'inizio del vostro programma,
per inizializzare il generatore di numeri casuali. BEGIN { srand() if $] < 5.004 }
La versione 5.004 e le successive chiamano automaticamente
srand all'avvio. Non chiamate srand più di una
volta -- rendereste i vostri numeri meno casuali, non di più.
I calcolatori sono bravi ad essere prevedibili, ma non nell'essere
casuali (malgrado le apparenze causate dagli errori nei vostri programmi
:-). Fate riferimento all'articolo random della collezione ``Far
More Than You Ever Wanted To Know'' [``Molto più di quanto avreste mai
voluto sapere'', NdT], cortesia di Tom Phoenix, che parla di questo
argomento. John Von Neumann disse ``Chiunque tenti di generare numeri
casuali con metodi deterministici vive, ovviamente, nel peccato''.
Se volete numeri casuali più casuali di quanto rand
(assieme a srand) possa fare, dovreste provare anche il
modulo Math::TrulyRandom, disponibile su CPAN. Fa uso delle imperfezioni
dell'orologio di sistema per generare numeri casuali, ma ci vuole un po'
di tempo. Se volete un generatore di numeri pseudocasuali migliore di
quello che il vostro sistema operativo mette a disposizione, consultate
``Numerical Recipes in C'' all'indirizzo http://www.nr.com/.
Usate la semplice funzione che segue. Essa seleziona un intero a caso
tra (e possibilmente includendo!) i due interi dati, ad es.,
intero_a_caso_tra(50,120) sub intero_a_caso_tra ($$) {
my($min, $max) = @_;
# Si assume che i due argomenti siano essi stessi interi!
return $min if $min == $max;
($min, $max) = ($max, $min) if $min > $max;
return $min + int rand(1 + $max - $min);
}
La funzione localtime restituisce il giorno della settimana. Senza
alcun argomento, localtime utilizza l'orario attuale. $giorno_dell_anno = (localtime)[7];
Il modulo POSIX può anche dare un formato ad una data usando il giorno
dell'anno o la settimana dell'anno. use POSIX qw/strftime/;
my $giorno_dell_anno = strftime "%j", localtime;
my $settimana_dell_anno = strftime "%W", localtime;
Per ottenere il giorni dell'anno per qualsiasi data, utilizzate il
modulo Time::Local per convertire un orario in secondi dall'epoch [data di
riferimento; nella cultura Unix il 1/1/1970 00:00:00, NdT] da passare a
localtime. use POSIX qw/strftime/;
use Time::Local;
my $settimana_dell_anno = strftime "%W",
localtime( timelocal( 0, 0, 0, 18, 11, 1987 ) );
Il modulo Date::Calc fornisce due funzioni per calcolare questi
valori. use Date::Calc;
my $giorno_dell_anno = Day_of_Year( 1987, 12, 18 );
my $settimana_dell_anno = Week_of_Year( 1987, 12, 18 );
Usate le seguenti semplici funzioni: sub secolo {
return int((((localtime(shift || time))[5] + 1999))/100);
} sub millennio {
return 1+int((((localtime(shift || time))[5] + 1899))/1000);
}
Potete anche utilizzare la funzione di POSIX strftime()
che può essere un po' lenta ma che è facile da leggere e da
manutenere. use POSIX qw/strftime/; my $settimana_dell_anno = strftime "%W", localtime;
my $giorno_dell_anno = strftime "%j", localtime;
Su alcuni sistemi, si noterà che la funzione strftime()
del modulo POSIX è stata estesa in maniera non standard per usare il
formato %C, che a volte viene indicato come ``secolo''. Non
lo è, poiché sulla maggior parte di quei sistemi, esso rappresenta solo le
prime due cifre dell'anno a quattro cifre, e quindi non può essere
utilizzato per determinare in maniera affidabile il secolo oppure il
millennio correnti.
Se state memorizzando la data come numero di secondi dall'epoch [data
di riferimento; nella cultura Unix il 1/1/1970 00:00:00, NdT], potete
semplicemente sottrarre una data dall'altra. Se avete in mano una data
strutturata (anno, giorno, mese, ora, minuto e secondo sono cioè valori
ben distinti) allora, per motivi di accessibilità, semplicità, ed
efficienza, usate timelocal o timegm (dal modulo
Time::Local incluso nella distribuzione standard del Perl) per convertire
le date strutturate in secondi dall'epoch. Se non conoscete il formato
preciso delle vostre date, dovreste probabilmente usare i moduli
Date::Manip o Date::Calc da CPAN prima di iniziare a scrivere la vostra
routine che permetta di gestire formati di data arbitrari.
Se la stringa è sufficientemente regolare da avere sempre lo stesso
formato, si può dividerla e passarne le parti a timelocal nel
modulo standard Time::Local. Altrimenti, sarà necessario cercare nei
moduli Date::Calc e Date::Manip dal CPAN.
(*) NdT: data di riferimento; nella cultura Unix il 1/1/1970
00:00:00
Usate il modulo Time::JulianDay (parte del pacchetto Time-modules
disponibile presso CPAN).
Prima di immergersi troppo a fondo in questo argomento, assicuratevi di
volere davvero il Giorno Giuliano. Vi interessa un metodo per
ottenere i giorni seriali in modo da poter semplicemente dire quanti
giorni essi sono distanti o in modo di poter fare anche altra aritmetica
sulle date? Se vi interessa fare aritmetica con le date, questa può essere
fatta utilizzando i moduli Date::Manip oppure Date::Calc.
Ci sono troppi dettagli e molta confusione su questo argomento per
coprirli in questa FAQ, ma il termine è applicato (correttamente) ad un
calendario ora soppiantato dal Calendario Gregoriano, poiché il Calendario
Giuliano sbaglia nel corretto aggiustamento per gli anni bisestili che
cadono sui centenari (assieme ad altri fastidi). Il termine è inoltre
utilizzato (erroneamente) per significare: [1] giorni nel Calendario
Gregoriano; e [2] giorni trascorsi da una determinata partenza o 'epoch',
solitamente il 1970 nel mondo Unix ed il 1980 nel mondo MS-DOS/Windows. Se
si trova che quello realmente desiderato non è il primo significato,
allora si esaminino i moduli Date::Manip e Date::Calc. (Grazie a David
Cassell per la maggior parte di questo testo)
Se volete solamente trovare la data (e non il medesimo istante), potete
usare il modulo Date::Calc. use Date::Calc qw(Today Add_Delta_Days); my @data = Add_Delta_Days( Today(), -1 ); print "@data\n";
La maggior parte delle persone provano ad usare l'istante di tempo
piuttosto che il calendario per calcolare le date, ma questo presuppone
che i giorni siano di ventiquattro ore. Per la maggior parte delle
persone, ci sono due giorni all'anno che non lo sono: i giorni del
cambiamento da e verso l'ora estiva (ora legale). Russ Albery offre questa
soluzione. sub ieri {
my $adesso = defined $_[0] ? $_[0] : time;
my $prima = $adesso - 60 * 60 * 24;
my $adesso_legale = (localtime $adesso)[8] > 0;
my $prima_legale = (localtime $prima)[8] > 0;
$prima - ($prima_legale - $adesso_legale) * 60 * 60;
}
Dovrebbe fornire ``quest'ora, ieri'' in secondi dall'epoch [data di
riferimento; nella cultura Unix il 1/1/1970 00:00:00, NdT], relativa al
primo argomento oppure all'ora corrente se non viene specificato alcun
argomento, adatta ad essere passata a localtime() o per
qualsiasi altra cosa abbiate bisogno di farci. $adesso_legale indica se
siamo o meno in ora legale; $prima_legale indica se il punto di 24 ore
prima era in ora legale. Se $adesso_legale e $prima_legale sono uguali,
non è stato passato alcun confine, e la correzione sottrarrà 0. Se
$prima_legale è 1 e $adesso_legale è 0, viene sottratta un'ora in più
dall'ora di ieri, poiché è stata guadagnata un'ora extra mentre si passava
all'ora legale. Se $prima_legale è 0 e $adesso_legale è 1, viene sottratta
un'ora negativa (cioè aggiunta un'ora) all'ora di ieri, poiché è stata
persa un'ora.
Tutto questo accade perché durante quei giorni, quando si esce o entra
da/nell'ora legale, un ``giorno'' non dura 24 ore: o è di 23 o di 25.
L'impostazione esplicita di $adesso_legale e $prima_legale è necessaria
poiché localtime restituisce la struttura tm di sistema, e la
struttura tm di sistema, almeno su Solaris, non garantisce
alcun particolare valore positivo (come, ad esempio, 1) per il campo
isdst, ma solo un valore positivo. E quel valore può anche
essere negativo, se le informazioni sull'ora legale (DST) non sono
disponibili (questa sub tratta quei casi semplicemente come se non ci
fosse l'ora legale).
Notate che tra le 2 e le 3 del mattino del giorno dopo l'uscita della
zona dall'ora legale, l'ora esatta di ``ieri'' corrispondente all'ora
corrente non è chiaramente definita. Osservate inoltre che, se usato tra
le 2 e le 3 del mattino del giorno dopo l'entrata della zona nell'ora
legale, il risultato sarà tra le 3 e le 4 del mattino del giorno
precedente; la correttezza di ciò è discutibile.
Questa sub non tenta di tenere conto dei leap seconds [secondi che
occasionalmente vengono inseriti per correggere l'ora UTC rispetto a
quella basata sulla rotazione terrestre, NdT] (molte cose non fanno
questo).
Risposta breve: No, Perl non ha un problema per quanto riguarda l'anno
2000. Si, Perl è conforme a Y2K (qualsiasi cosa ciò significhi). I
programmatori che avete assunto per usarlo, tuttavia, probabilmente non lo
sono.
Risposta lunga: La domanda impedisce una reale comprensione della
questione. Perl è conforme a Y2K esattamente come la vostra matita--non di
più, e non di meno. Potete usare la vostra matita per scrivere una nota
non conforme a Y2K? Certo che potete. è colpa della matita? Ovviamente
no.
Le funzioni per la data e l'ora fornite con il Perl (gmtime e
localtime) forniscono un'informazione adeguata per determinare l'anno ben
oltre il 2000 (per le macchine a 32 bit i problemi arriveranno nel 2038).
L'anno restituito da queste funzioni quando sono usate in contesto di
lista è l'anno meno 1900. Per gli anni tra il 1910 ed il 1999
capita che esso sia un numero decimale di due cifre. Per evitare
il problema dell'anno 2000 evitate semplicemente di trattare quel numero
come un numero a due cifre. Non lo è.
Quando gmtime() e localtime() sono usate in
contesto scalare, esse restituiscono una stringa ``timestamp'' contenente
il numero completo dell'anno. Per esempio, $timestamp =
gmtime(1005613200) imposta $timestamp a ``Tue Nov 13 01:00:00
2001''. Non c'è alcun problema con l'anno 2000 in questo caso.
Ciò non significa che il Perl non può essere usato per creare programmi
non conformi a Y2K. Può. Ma così può anche la vostra matita. È colpa
dell'utente, non del linguaggio. Rischiando di offendere l'NRA: ``Perl non
viola Y2K, la gente lo fa''. Consultate http://language.perl.com/news/y2k.html
per un'esposizione più lunga.
La risposta a questa domanda è di solito un'espressione regolare,
possibilmente con della logica ausiliaria. Per i dettagli consultate le
domande più specifiche (numeri, indirizzi email, ecc.).
Dipende da cosa si intende con 'escape'. Gli escape delle URL sono
trattati in perlfaq9. Gli escape con il carattere backslash
(``\'') si rimuovono con: s/\\(.)/$1/g;
Questo non espanderà "\n" o "\t" o qualsiasi
altro escape speciale.
Per trasformare "abbcccd" in "abccd": s/(.)\1/$1/g; # aggiungete /s per includere gli 'a capo'
Questa soluzione trasforma "abbcccd" in
"abcd": y///cs; # y == tr, ma e` piu` corta :-)
Questo è documentato in perlref. In generale, la cosa presenta
molti problemi di quoting e di leggibilità, ma è possibile. Per
interpolare una chiamata a subroutine (in contesto di lista) in una
stringa: print "La mia sub quella volta ha restituito @{[miasub(1,2,3)]} .";
Consultate anche ``Come posso espandere le variabili nelle stringhe
testuali?'' in questa sezione della FAQ.
Questa non è una cosa che può essere risolta con una sola espressione
regolare, indipendentemente da quanto complessa essa sia. Per trovare
qualcosa compreso tra due caratteri singoli, uno schema come
/x([^x]*)x/ memorizzerà in $1 i caratteri contenuti nel
mezzo. In caso di caratteri multipli, sarà necessario qualcosa come
/alpha(.*?)omega/. Tuttavia, nessuna di queste soluzioni sarà
in grado di gestire gli annidamenti. Per espressioni bilanciate che usano
(, {, [ o < come
delimitatori, usate il modulo Regexp::Common da CPAN, o consultate
perlre/(??{ code }). Negli altri casi, dovrete scrivervi un
parser.
Se siete seriamente intenzionati a scrivere un parser, esiste un certo
numero di moduli e strumenti che vi renderanno la vita molto più facile.
Ci sono i moduli CPAN Parse::RecDescent, Parse::Yapp, e Text::Balanced; ed
il programma byacc. A partire da perl 5.8, Text::Balanced fa parte della
distribuzione standard.
Un approccio semplice, dall'interno e distruttivo che potreste voler
provare, consiste nel tentare di estrarre le parti più piccole una alla
volta: while (s/BEGIN((?:(?!BEGIN)(?!END).)*)END//gs) {
# fate qualcosa con $1
}
Un approccio più complesso e tortuoso consiste nel far fare il lavoro
alle espressioni regolari del perl al posto vostro. Il seguente codice è
di Dean Inada, e sembra partecipare all'Obfuscated Perl Contest, ma
funziona davvero: # $_ contiene la stringa da analizzare
# BEGIN ed END sono i delimitatori di apertura e chiusura per il
# testo tra essi compreso. @( = ('(','');
@) = (')','');
($re=$_)=~s/((BEGIN)|(END)|.)/$)[!$3]\Q$1\E$([!$2]/gs;
@$ = (eval{/$re/},$@!~/unmatched/i);
print join("\n",@$[0..$#$]) if( $$[-1] );
Utilizzate reverse() in contesto scalare, come documentato
in perlfunc/``reverse''. $invertita = reverse $stringa;
Lo si può fare da soli: 1 while $stringa =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
Oppure potete usare il modulo Text::Tabs (che fa parte della
distribuzione standard del Perl). use Text::Tabs;
@linee_espanse = expand(@linee_con_tab);
Utilizzate Text::Wrap (parte della distribuzione standard del
Perl): use Text::Wrap;
print wrap("\t", ' ', @paragrafi);
I paragrafi che si passano a Text::Wrap non devono contenere 'a capo'.
Text::Wrap non giustifica le linee (renderle tutte della stessa
lunghezza).
Oppure utilizzate il modulo CPAN Text::Autoformat. La formattazione di
file può essere fatta semplicemente creando un alias nella shell, in
questo modo: alias fmt="perl -i -MText::Autoformat -n0777 \n
-e 'print autoformat $_, {all=>1}' $*"
Consultate la documentazione di Text::Autoformat per apprezzarne le
molte possibilità.
Potete accedere ai primi caratteri di una stringa con substr(). Per
ottenere il primo carattere, ad esempio, partite dalla posizione 0 e
catturate la stringa di lunghezza 1. $stringa = "Just another Perl Hacker";
$primo_car = substr( $string, 0, 1 ); # 'J'
Per cambiare parte di una stringa, potete usare il quarto parametro
(opzionale), che è la stringa di sostituzione. substr( $stringa, 13, 4, "Perl 5.8.0" );
Potete anche usare substr() a sinistra di un
assegnamento. substr( $stringa, 13, 4 ) = "Perl 5.8.0";
Dovete tenere il conto di N da soli. Ad esempio, diciamo che volete
modificare la quinta occorrenza di "whoever" o
"whomever" in "whosoever" o
"whomsoever", senza preoccuparvi di lettere maiuscole o
minuscole. Ogni esempio assume che $_ contenga la stringa da
modificare. $conto = 0;
s{((whom?)ever)}{
++$conto == 5 # e` la quinta occorrenza?
? "${2}soever" # si`, scambiala
: $1 # lasciala li`
}ige;
Nel caso più generale, potete usare il modificatore /g in
un ciclo while, tenendo il conto delle corrispondenze. $VOGLIO = 3;
$conto = 0;
$_ = "Pesce uno pesce due pesce rosso pesce blu";
while (/\bpesce\s+(\w+)/gi) {
if (++$conto == $VOGLIO) {
print "Il terzo pesce e` $1.\n";
}
}
Che stampa: "Il terzo pesce e` rosso". Potete anche usare
un contatore per le ripetizioni e poi ripetere il pattern, come qui: /(?:\s*pesce\s+\w+){2}\s+pesce\s+(\w+)/i;
Ci sono varie strade, con un diverso grado di efficienza. Se ciò che
desiderate è il conto delle occorrenze di un determinato carattere singolo
(X) all'interno di una stringa, potete utilizzare la funzione
"tr///" in questo modo: $stringa = "QuestaXlineaXhaXalcuneXxXalXsuoXinterno";
$conto = ($stringa =~ tr/X//);
print "Ci sono $conto caratteri X nella stringa";
Questo funziona benissimo quando state cercando un singolo cattere. Se
state cercando di contare le occorrenze di una sottostringa composta da
più caratteri all'interno di una stringa, "tr///" non
funziona. Quello che potete fare è inserire la ricerca globale di un
pattern [schema, NdT] all'interno di un while(). Per esempio, contiamo gli
interi negativi: $stringa = "-9 55 48 -2 23 -76 4 14 -44";
while ($stringa =~ /-\d+/g) { $conto++ }
print "Ci sono $conto interi negativi nella stringa";
Un'altra versione usa una ricerca globale in contesto di lista,
assegnandone poi il risultato ad uno scalare, generando un conto del
numero di sottostringhe trovate. $conto = () = $stringa =~ /-\d+/g;
Per rendere maiuscola la prima lettera di ogni parola: $riga =~ s/\b(\w)/\U$1/g;
Questo codice ha lo strano effetto di convertire "l'ombra
rinfresca" in "L'Ombra Rinfresca". A volte, magari, è
proprio ciò che desiderate. Altre volte potreste aver bisogno di una
soluzione più completa (suggerita da brian d foy): $stringa =~ s/ (
(^\w) #all'inizio della riga
| # o
(\s\w) #preceduto da spazio bianco
)
/\U$1/xg;
$stringa =~ s/([\w']+)/\u\L$1/g;
Per rendere un'intera riga maiuscola: $riga = uc($riga);
Per forzare ciascuna parola ad essere minuscola, con la prima lettera
maiuscola: $riga =~ s/(\w+)/\u\L$1/g;
Potete (e probabilmente dovreste) abilitare la gestione del locale per
i caratteri che necessitano di essa, aggiungendo un use
locale al vostro programma. Consultate perllocale per una
serie infinita di dettagli sul locale.
L'operazione sopra descritta è a volte indicata come il porre qualcosa
in ``title case'' [impostare cioè maiscole e minuscole come nei titoli,
NdT], ma ciò non è del tutto corretto. Prendete ad esempio in
considerazione la corretta scelta delle maiuscole per il film Dottor
Stranamore, Ovvero Come Imparai a Non Preoccuparmi e ad Amare la
Bomba.
Il modulo di Damian Conway the
Text::Autoformat manpage fornisce alcune astute trasformazioni tra
maiuscole e minuscole: use Text::Autoformat;
my $x = "Dottor Stranamore, Ovvero Come Imparai a Non ".
"Preoccuparmi e ad Amare la Bomba"; print $x, "\n";
for my $stile (qw( sentence title highlight ))
{
print autoformat($x, { case => $stile }), "\n";
}
Diversi moduli possono gestire questo tipo di analisi sintattica ---
Text::Balanced, Text::CSV, Text::CSV_XS e Text::ParseWords, tra gli
altri.
Prendete l'esempio di tentare di estrarre da una stringa i singoli
campi, che sono separati da virgole. Non potete utilizzare
split(/,/), poiché non dovete dividere se la virgola si trova
tra virgolette. Per esempio, considerate una linea come la seguente: SAR001,"","Cimetrix, Inc","Bob Smith","CAM",N,8,1,0,7,"Error, Core Dumped"
A causa della restrizione per quanto riguarda i caratteri tra
virgolette, il problema è piuttosto complesso. Fortunatamente, abbiamo
Jeffrey Friedl, autore di Mastering Regular Expressions, che si
occupa della cosa per noi. Suggerisce (ponendo che la vostra stringa sia
contenuta in $testo): @nuovo = ();
push(@nuovo, $+) while $testo =~ m{
"([^\"\\]*(?:\\.[^\"\\]*)*)",? # raggruppa la frase all'interno delle virgolette
| ([^,]+),?
| ,
}gx;
push(@nuovo, undef) if substr($testo,-1,1) eq ',';
Se desiderate inserire delle virgolette all'interno di un campo
delimitato da virgolette, usate dei backslash come escape (ad es.
"in \"questo\" modo".
In alternativa, il modulo Text::ParseWords (contenuto nella
distribuzione standard di Perl) vi permette di scrivere: use Text::ParseWords;
@new = quotewords(",", 0, $text);
C'è anche un modulo TEXT::CSV (Comma-Separated Values [valori separati
da virgola, NdT]) su CPAN.
Sebbene l'approccio più semplice sembrerebbe essere $stringa =~ s/^\s*(.*?)\s*$/$1/;
Ciò non è solo inutilmente lento e distruttivo, ma ha anche problemi se
la stringa contiede degli 'a capo'. é molto più veloce fare questa
operazione in due passi: $stringa =~ s/^\s+//;
$stringa =~ s/\s+$//;
Oppure in maniera più elegante: for ($stringa) {
s/^\s+//;
s/\s+$//;
}
Questa espressione idiomatica sfrutta il comportamento con alias [il $_
, NdT] di foreach per raccogliere il codice a fattor comune.
Si può fare questo su più righe alla volta, su array o anche sui valori di
un hash se si usa una slice [selettore per parte di una lista, NdT]: # toglie gli spazi dallo scalare, dagli elementi dell'array
# ed in tutti i valori dell'hash
foreach ($scalare, @array, @hash{keys %hash}) {
s/^\s+//;
s/\s+$//;
}
Nei seguenti esempi, $lungh è la lunghezza cui vogliamo
portare la stringa, $testo o $num contengono la
stringa da allungare, e $carat contiene il carattere con cui
riempire. Se si conosce questo carattere in anticipo, si può usare una
costante composta da una stringa di un solo carattere invece della
variabile $carat. E allo stesso modo potete usare un intero
al posto di $lungh se conoscete già la lunghezza.
Il metodo più semplice utilizza la funzione sprintf. Può
riempire sulla sinistra o sulla destra con spazi, sulla sinistra con zeri
e non troncherà il risultato. La funzione pack può solo
riempire le stringhe con degli spazi sulla destra e troncherà il risultato
fino ad una lunghezza massima di $lungh. # Riempimento di una stringa a sinistra con spazi (nessun troncamento):
$riempito = sprintf("%${lungh}s", $testo);
$riempito = sprintf("%*s", $lungh, $testo); # stessa cosa # Riempimento di una stringa a destra con spazi (nessun troncamento):
$riempito = sprintf("%-${lungh}s", $testo);
$riempito = sprintf("%-*s", $lungh, $testo); # stessa cosa # Riempimento di un numero a sinistra con zeri (nessun troncamento):
$riempito = sprintf("%0${lungh}d", $num);
$riempito = sprintf("%0*d", $lungh, $num); # stessa cosa # Riempimento di una stringa a destra con spazi usando pack (verra` troncata):
$riempito = pack("A$lungh",$testo);
Se si ha la necessità di riempire con un carattere che non sia lo
spazio o lo zero, si può usare uno dei metodi seguenti. Generano tutti una
stringa di riempimente usando l'operatore x e la combinano
con $testo. Questi metodi non troncano
$testo.
Riempimento a sinistra e a destra con qualsiasi carattere, creando una
nuova stringa: $riempito = $carat x ( $lungh - length( $testo) ) . $testo;
$riempito = $testo. $carat x ( $lungh - length( $testo) );
Riempimento a sinistra e a destra con qualsiasi carattere, modificando
direttamente $testo: substr( testo, 0, 0 ) = $carat x ( $lungh - length( $testo) );
$testo.= $carat x ( $lungh - length( $testo) );
Usate substr() o unpack(), entrambe documentate in
perlfunc. Se preferite pensare in termini di colonne invece che
di larghezze, potete usare qualcosa del tipo: # determina il formato di unpack necessario per separare l'output
# del 'ps' di Linux
# gli argomenti sono le colonne a cui tagliare i campi
my $fmt = taglia_a_formato(8, 14, 20, 26, 30, 34, 41, 47, 59, 63, 67, 72); sub taglia_a_formato {
my(@posizioni) = @_;
my $template = '';
my $ultimapos = 1;
for my $pos (@posizioni) {
$template .= "A" . ($pos - $ultimapos) . " ";
$ultimapos = $pos;
}
$template .= "A*";
return $template;
}
Usate il modulo standard Text::Soundex che viene distribuito con il
Perl. Prima di fare questo, potreste voler determinare se il 'soundex' sia
effettivamente quello che pensate esso sia. L'algoritmo soundex di Knuth
comprime le parole in un piccolo spazio e quindi non distingue
necessariamente tra due parole che vorreste presentare separatamente. Per
esempio, i cognomi 'Knuth' e 'Kant' sono entrambi mappati al codice
soundex K530. Se Text::Soundex non fa quello che cercate, potreste voler
prendere in considerazione il modulo String::Approx disponibile su
CPAN.
Assumete di avere una stringa come: $testo = 'questa ha contiene un $pippo e un $pluto';
Se queste fossero entrambe variabili globali, allora questo sarebbe
sufficiente: $testo =~ s/\$(\w+)/${$1}/g; # /e non e` necessario
Visto che le variabili sono probabilmente lessicali, o almeno
potrebbero esserlo, dovreste fare così: $testo =~ s/(\$\w+)/$1/eeg; # serve /ee, non solo /e
die if $@;
Probabilmente è meglio, in generale, considerare queste variabili come
elementi di qualche hash apposito. Per esempio: %def_utente = (
pippo => 23,
pluto => 19,
);
$testo =~ s/\$(\w+)/$def_utente{$1}/g;
Si veda anche ``Come espando le chiamate a funzione in una stringa?''
in questa sezione delle FAQ.
Il problema è che questo rendere stringa mediante le virgolette
converte a forza numeri e riferimenti in stringhe, anche quando non volete
che lo siano. Prendetela così: l'espansione con le virgolette viene usata
per produrre nuove stringhe. Se avete già una stringa, perché ne volete
un'altra?
Se siete abituati a scrivere cose strane come queste: print "$var"; # SCORRETTO
$nuovo = "$vecchio"; # SCORRETTO
unafunz("$var"); # SCORRETTO
vi troverete nei guai. Queste dovrebbero (nel 99,8% dei casi) essere le
forme più semplici e dirette: print $var;
$nuovo = $vecchio;
unafunz($var);
D'altronde, oltre a essere più lunghe da scrivere, finirete per
introdurre errori nel codice quando la cosa contenuta nello scalare non è
né una stringa né un numero, ma un riferimento: funz(\@array);
sub funz {
my $arif = shift;
my $orif = "$arif"; # SBAGLIATO
}
Potreste anche andare incontro a problemi sottili con quelle poche
operazioni in Perl che sentono la differenza tra una stringa ed un numero,
quali il magico operatore di auto-incremento ++, oppure la
funzione syscall().
Forzare a stringa distrugge anche gli array. @linee = `comando`;
print "@linee"; # SBAGLIATO - spazi aggiuntivi
print @linee; # giusto
Controllate le seguenti tre condizioni:
- Non
ci devono essere spazi dopo <<.
-
- Deve (probabilmente) esserci un punto e
virgola alla fine.
-
- Non potete (agevolmente) inserire alcuno
spazio davanti all'etichetta.
Se desiderate incolonnare il testo negli here document, potete fare
così: # tutto in uno
($VAR = <<HERE_FINE) =~ s/^\s+//gm;
il vostro testo
va inserito qui
HERE_FINE
Ma la HERE_FINE deve comunque trovarsi al margine. Se desiderate che
anch'essa sia incolonnata, dovete mettere tra apici anche
l'incolonnamento: [la citazione è dal Signore degli Anelli, e si trova
effettivamente nei sorgenti di perl, NdT] ($citazione = <<' FINIS') =~ s/\s+//gm;
...we will have peace, when you and all your works have
perished--and the works of your dark master to whom you
would deliver us. You are a liar, Saruman, and a corrupter
of men's hearts. --Theoden in /usr/src/perl/taint.c
FINIS
$citazione =~ s/\s+--/\n--/;
Di seguito è riportata una funzione generale di ripulitura per gli here
document incolonnati. Essa si aspetta di ricevere uno here document come
argomento. Essa controlla che ciascuna linea inizi con una determinata
sottostringa e, nel caso, la rimuove. Altrimenti, prende il numero di
spazi bianchi all'inizio della prima riga e rimuove tale numero di
caratteri da ciascuna delle linee successive. sub pulisci {
local $_ = shift;
my ($bianco, $inizio); # spazio bianco comune e stringa iniziale comune
if (/^\s*(?:([^\w\s]+)(\s*).*\n)(?:\s*\1\2?.*\n)+$/) {
($bianco, $inizio) = ($2, quotemeta($1));
} else {
($bianco, $inizio) = (/^(\s+)/, '');
}
s/^\s*?$inizio(?:$bianco)?//gm;
return $_;
}
Questa soluzione funziona con stringhe particolari all'inizio, che
vengono determinate dinamicamente: $ricorda_il_main = pulisci<<' MAIN_INTERPRETER_LOOP';
@@@ int
@@@ runops() {
@@@ SAVEI32(runlevel);
@@@ runlevel++;
@@@ while ( op = (*op->op_ppaddr)() );
@@@ TAINT_NOT;
@@@ return 0;
@@@ }
MAIN_INTERPRETER_LOOP
Oppure con uno spazio iniziale fisso, preservando il restante
incolonnamento: [la citazione è tratta dal Signore degli Anelli, e si
trova effettivamente nei sorgenti di perl, NdT] $poesia = pulisci<<EVER_ON_AND_ON;
Now far ahead the Road has gone,
And I must follow, if I can,
Pursuing it with eager feet,
Until it joins some larger way
Where many paths and errands meet.
And whither then? I cannot say.
--Bilbo in /usr/src/perl/pp_ctl.c
EVER_ON_AND_ON
Un array ha una lunghezza modificabile. Una lista no. Un array è
qualcosa su cui si possono usare push e pop,
mentre una lista è una sequenza di valori. Alcune persone fanno questa
distinzione: una lista è un valore mentre un array è una variabile. Alle
subroutine si passano liste e restituiscono liste, si mettono cose in un
contesto di lista, si inizializzano gli array con delle liste, e si fanno
cicli con foreach() su liste. Le variabili @
sono array, gli array anonimi sono array, gli array in un contesto scalare
si comportano come il numero di elementi contenuto in essi, le subroutine
accedono ai loro argomenti attraverso l'array @_ e
push/pop/shift lavorano solo sugli
array.
Come nota a margine, non esistono ``liste in un contesto scalare''.
Quando scrivete: $scalare = (2, 5, 7, 9);
state usando l'operatore virgola in un contesto scalare, per cui viene
usato l'operatore scalare virgola. Non c'è davvero mai stata alcuna lista
qui! Questo causa la restituzione dell'ultimo valore: 9.
Il primo è un valore scalare; il secondo è una slice [porzione, NdT] di
un array, che lo rende una lista con un solo valore (uno scalare). Si
dovrebbe usare $ quando si vuole un valore scalare (quasi sempre) e @
quando si vuole una lista contentente un solo valore scalare (molto, molto
di rado; in pratica, quasi mai).
Talvolta non fa alcuna differenza, ma talvolta la fa. Per esempio,
confrontate: $giusto[0] = `un programma che restituisce in output varie linee`;
con @sbagliato[0] = `stesso programma che restituisce in output varie linee`;
La direttiva use warnings ed il flag -w
vi metteranno in guardia riguardo a queste faccende.
Ci sono diversi modi possibili, a seconda che l'array sia o meno
ordinato e che si desideri o meno mantenere l'ordinamento.
- a)
- Se
@in è ordinato e si vuole che @out sia
ordinato: (con questo si assume che l'array contenga tutti valori veri) $prec = "non uguale a $in[0]";
@out = grep($_ ne $prec && ($prec = $_, 1), @in);
Questo è un buon metodo per il fatto che non usa molta memoria
aggiuntiva, simulando il comportamento di uniq(1) che
rimuove solo i duplicati adiacenti. Il ``, 1'' garantisce che
l'espressione sia vera (in modo che grep prenda l'elemento) anche se
$_ è 0, ``'' oppure undef.
- b)
- Se non si sa se
@in sia ordinato o meno: undef %visto;
@out = grep(!$visto{$_}++, @in);
- c)
- Come (b) ma
@in contiene solo interi piccoli: @out = grep(!$visto[$_]++, @in);
- d)
- Un modo per ottenere (b) senza cicli né grep:
undef %visto;
@visto{@in} = ();
@visto = sort keys %visto; # rimuovere sort se non lo volete
- e)
- Come (d), ma @in contiene solo piccoli interi positivi:
undef @array;
@array[@in] = @in;
@out = grep {defined} @array;
Ma forse avreste dovuto utilizzare un hash fin dall'inizio, eh?
Già sentire la parola ``in'' è un'indicazione che avreste
probabilmente dovuto utilizzare un hash, non una lista o un array, per
memorizzare i vostri dati. Gli hash sono progettati per offrire una
risposta rapida ed efficiente a questa domanda. Gli array no.
Detto questo, ci sono molti modi per risolvere la questione. Se dovete
fare questa operazione molte volte su stringhe arbitrarie, la via più
veloce è probabilmente quella di invertire l'array originale e creare un
hash le cui chiavi sono gli elementi dell'array. @blu = qw/azzurro ceruleo celeste turchese oltremare/;
%un_blu = ();
for (@blu) { $un_blu{$_} = 1 }
Ora potete controllare se è $un_blu{$un_colore}. Potrebbe essere stata
una buona idea quella di memorizzare i blu in un hash sin dall'inizio.
Se i valori sono tutti interi piccoli, potete usare un semplice array
indicizzato. Questo tipo di array utilizzerà meno spazio: @primi = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31);
@un_piccolo_primo = ();
for (@primi) { $un_piccolo_primo[$_] = 1 }
# o semplicemente @un_piccolo_primo[@primi] = (1) x @primi;
Ora potete controllare se è $un_piccolo_primo[$un_numero].
Se i valori in questione sono interi anziché stringhe, potete salvare
molto spazio utilizzando le stringhe di bit: @articoli = ( 1..10, 150..2000, 2017 );
undef $letti;
for (@articoli) { vec($letti,$_,1) = 1 }
Controllate ora se vec($letti,$n,1) è vero per un
determinato $n.
Per favore non usate: ($presente) = grep $_ eq $qualcosa, @array;
o peggio ancora: ($presente) = grep /$qualcosa/, @array;
Queste soluzioni sono lente (controllano tutti gli elementi anche se il
primo corrisponde a quello cercato), inefficienti (stessa ragione) e
potenzialmente sbagliate (cosa succede se ci sono caratteri di espressioni
regolari in $qualcosa?). Se dovete effettuare la ricerca solo
una volta, usate: $presente = 0;
foreach $elem (@array) {
if ($elem eq $elem_da_trovare) {
$presente = 1;
last;
}
}
if ($presente) { ... }
Usate un hash. Di seguito c'è il codice con entrambe le risposte e
oltre. Si assume che in un dato array, ogni elemento sia univoco. @unione = @intersezione = @differenza = ();
%conteggio = ();
foreach $elemento (@array1, @array2) { $conteggio{$elemento}++ }
foreach $elemento (keys %conteggio) {
push @unione, $elemento;
push @{
$conteggio{$elemento} > 1 ?
\@intersezione :
\@differenza
}
, $elemento;
Notate che questa è la differenza simmetrica, il che significa
tutti gli elementi contenuti in A oppure in B, ma non in entrambi. Pensate
ad essa come ad una operazione xor.
Il codice di seguito riportato funziona per array ad un solo livello.
Utilizza un confronto di stringhe e non distingue i valori indefiniti
dalle stringhe vuote ma definite. Modificatelo se avete esigenze
diverse. $sono_uguali = confronta_array(\@rane, \@rospi); sub confronta_array {
my ($primo, $secondo) = @_;
no warnings; # zittisce le proteste di -w sugli undef
return 0 unless @$primo == @$secondo;
for (my $i = 0; $i < @$primo; $i++) {
return 0 if $primo->[$i] ne $secondo->[$i];
}
return 1;
}
Per le strutture multilivello, potreste voler ricorrere ad un approccio
come il seguente. Utilizza il modulo CPAN FreezeThaw: use FreezeThaw qw(cmpStr);
@a = @b = ( "questo", "quello", [ "ancora", "roba" ] ); printf "'a' e 'b' contengono %s array\n",
cmpStr(\@a, \@b) == 0
? "gli stessi"
: "diversi";
Questo sistema funziona anche per il confronto degli hash. Qui di
seguito sono mostrate due diverse risposte: use FreezeThaw qw(cmpStr cmpStrHard); %a = %b = ( "questo" => "quello", "extra" => [ "ancora", "roba" ] );
$a{EXTRA} = \%b;
$b{EXTRA} = \%a; printf "'a' e 'b' contengono %s hash\n",
cmpStr(\%a, \%b) == 0 ? "gli stessi" : "diversi"; printf "'a' e 'b' contengono %s hash\n",
cmpStrHard(\%a, \%b) == 0 ? "gli stessi" : "diversi";
La prima risposta indica che entrambi gli hash contengono gli stessi
dati, mentre la seconda indica il contrario. La scelta di quale sia per
voi preferibile è lasciato come esercizio al lettore.
Per trovare il primo elemento di un array che soddisfa una condizione,
potete utlizzare la funzione first() nel modulo List::Util,
fornita con il Perl 5.8. Questo esempio trova il primo elemento che
contiene ``Perl''. use List::Util qw(first); my $elemento = first { /Perl/ } @array;
Se non potete usare List::Util, potete scrivervi un ciclo per fare la
stessa cosa. Una volta che avete trovato l'elemento, fermate il ciclo con
last. my $trovato;
foreach my $elemento ( @array )
{
if( /Perl/ ) { $trovato = $elemento; last }
}
Se volete l'indice dell'elemento, potete iterare sugli indici e
controllare l'elemento dell'array ad ogni indice fino a che non trovate
quello che verifica la condizione. my( $trovato, $indice ) = ( undef, -1 );
for( $i = 0; $i < @array; $i++ )
{
if( $array[$i] =~ /Perl/ )
{
$trovato = $array[$i];
$indice = $i;
last;
}
}
In generale, non avete bisogno di liste collegate in Perl, poiché con i
normali array potete estrarre ed aggiungere elementi sia dalla testa che
dalla coda (con push, pop, shift e
unshift), oppure potete usare splice per
inserire o rimuovere un numero arbitrario di elementi in punti arbitrari
dell'array. Sia pop che shift sono operazioni
O(1) sugli array dinamici di Perl. In assenza di operazioni
di shift e pop, push in generale
deve riallocare un numero di volte nell'ordine di log(N), e
unshift ha bisogno di copiare i puntatori ogni volta che
viene utilizzata.
Se proprio davvero volete, potete utilizzare le strutture descritte in
perldsc o perltoot e fare esattamente ciò che il libro
di algoritmi vi dice. Per esempio, immaginate un nodo di una lista come il
seguente: $nodo = {
VALORE => 42,
COLLEGAMENTO => undef,
};
Potete scorrere la lista nel seguente modo: print "Lista: ";
for ($nodo = $testa; $nodo; $nodo = $nodo->{COLLEGAMENTO}) {
print $nodo->{VALORE}, " ";
}
print "\n";
Potete aggiungere elementi alla lista così: my ($testa, $coda);
$coda = aggiungi($testa, 1); # crea una nuova testa
for $valore ( 2 .. 10 ) {
$coda = aggiungi($coda, $valore);
} sub aggiungi {
my($lista, $valore) = @_;
my $nodo = { VALORE => $valore };
if ($lista) {
$nodo->{COLLEGAMENTO} = $lista->{COLLEGAMENTO};
$lista->{COLLEGAMENTO} = $nodo;
} else {
$_[0] = $nodo; # sostituisce la versione del chiamante
}
return $nodo;
}
Ma, di nuovo, gli array forniti da Perl sono abbastanza validi
praticamente sempre.
Le liste circolari possono essere trattate nella maniera tradizionale
con liste collegate, oppure potete fare qualcosa di questo tipo con gli
array: unshift(@array, pop(@array)); # l'ultimo sara` il primo
push(@array, shift(@array)); # e vice versa
Se avete installato Perl 5.8.0 o successivo, o avete installato
Scalar-List-Utils 1.03 o successivo, potete scrivere: use List::Util 'shuffle'; @mescolato = shuffle(@lista);
In caso contrario, potete utilizzare l'argoritmo di mescolamento
Fisher-Yates: sub mescola_fisher_yates {
my $mazzo = shift; # $mazzo e` un riferimento ad un array
my $i = @$mazzo;
while ($i--) {
my $j = int rand ($i+1);
@$mazzo[$i,$j] = @$mazzo[$j,$i];
}
} # mescola la mia collezione di mpeg
#
my @mpeg = <audio/*/*.mp3>;
mescola_fisher_yates( \@mpeg ); # mescola @mpeg "sul posto"
print @mpeg;
Notate che l'implementazione sopra indicata mescola un array ``sul
posto'', a differenza di List::Util::shuffle() che prende una lista e ne
restituisce una nuova mescolata.
Avete probabilmente visto algoritmi di mescolamento che funzionano
utilizzando splice, prendendo a caso un elemento da scambiare
con quello corrente. srand;
@nuovo = ();
@vecchio = 1 .. 10; # solo una dimostrazione
while (@vecchio) {
push(@nuovo, splice(@vecchio, rand @nuovo, 1));
}
Questa è una cattiva pratica, poiché splice è già
O(N) e, poiché lo eseguite N volte, avete appena inventato un
algoritmo quadratico; il che significa O(N**2). Esso non scala bene, anche
se Perl è così efficiente che probabilmente non noterete la cosa finché
non avrete array piuttosto grandi.
Usate for/foreach: for (@linee) {
s/pippo/pluto/; # cambia quella parola
y/XZ/XZ/; # scambia quelle lettere
}
Ecco un altro metodo; calcoliamo i volumi sferici: for (@volumi = @raggi) { # @volumi ha parti cambiate
$_ **= 3;
$_ *= (4/3) * 3.14159; # questo calcolo diventera` una costante
}
che può essere fatto anche con map() che è fatto apposta
per trasformare una lista in un'altra: @volumi = map {$_ **3 * (4/3) * 3.14159 } @raggi;
Se volete fare la stessa cosa per modificare i valori di un hash,
potete servirvi della funzione values. Con Perl 5.6 i valori
non vengono copiati, quindi se modificate $orbita (in questo caso),
modificate il valore. for $orbita ( values %orbite ) {
($orbita **= 3) *= (4/3) * 3.14159;
}
Prima di perl 5.6 values restituiva copie dei valori,
dunque il codice perl più vecchio spesso contiene costruzioni come
@orbite{keys %orbite} al posto di values %orbite
quando l'hash deve essere modificato.
Usate la funzione rand() (consultate
perlfunc/rand): # all'inizio del programma:
srand; # non serve per le versioni 5.004 e successive # piu` avanti
$indice = rand @array;
$elemento = $array[$indice];
Assicuratevi di richiamare srand al più una volta nel
programma. Se lo chiamate più di una volta (ad esempio prima di ogni
chiamata a rand), state quasi certamente facendo una cosa
sbagliata.
Usate il modulo List::Permutor su CPAN. Se la lista è in effetti un
array, provate il modulo Algorithm::Permute (anch'esso su CPAN). È scritto
in codice XS ed è molto efficiente. use Algorithm::Permute;
my @array = 'a'..'d';
my $p_iteratore = Algorithm::Permute->new ( \@array );
while (my @perm = $p_iteratore->next) {
print "prossima permutazione: (@perm)\n";
}
Per un'esecuzione ancora più veloce, potreste fare: use Algorithm::Permute;
my @array = 'a'..'d';
Algorithm::Permute::permute {
print "prossima permutazione: (@array)\n";
} @array;
Ecco un piccolo programma che genera tutte le permutazioni di tutte le
parole su ciascuna linea di input. L'algoritmo racchiuso nella funzione
permute() è discusso nel Volume 4 (ancora non pubblicato) di
The Art of Computer Programming [L'Arte della Programmazione dei
Computer, NdT] di Knuth e funzionerà su qualsiasi lista: #!/usr/bin/perl -n
# generatore ordinato di permutazioni Fischer-Kause sub permuta (&@) {
my $codice = shift;
my @ind = 0..$#_;
while ( $codice->(@_[@ind]) ) {
my $p = $#ind;
--$p while $ind[$p-1] > $ind[$p];
my $q = $p or return;
push @ind, reverse splice @ind, $p;
++$q while $ind[$p-1] > $ind[$q];
@ind[$p-1,$q]=@ind[$q,$p-1];
}
} permuta {print"@_\n"} split;
Fornite una funzione di confronto a sort() (descritta in
perlfunc/sort): @lista = sort { $a <=> $b } @lista;
La funzione di ordinamento predefinita è cmp, confronto
tra stringhe, che ordinerebbe (1, 2, 10) in (1, 10,
2). <=>, utilizzato sopra, è l'operatore di
confronto numerico.
Se avete bisogno di una funzione complessa che estragga la parte su cui
desiderate basare l'ordinamento, non scrivetela all'inerno della funzione
di ordinamento. Estraete tale parte prima, poiché il BLOCCO di ordinamento
può essere chiamato molte volte per lo stesso elemento. Ecco un esempio di
come estrarre la prima parola dopo il primo numero da ciascun elemento, e
poi ordinare tali parole senza distinguere lettere maiuscole da lettere
minuscole. @indice = ();
for (@dati) {
($chiave) = /\d+\s*(\S+)/;
push @indice, uc($chiave);
}
@ordinati = @dati[ sort { $indice[$a] cmp $indice[$b] } 0 .. $#indice ];
che potrebbe anche essere scritto come segue, utilizzando un trucco
diventato noto come la Trasformata di Schwartz: @ordinati = map { $_->[0] }
sort { $a->[1] cmp $b->[1] }
map { [ $_, uc( (/\d+\s*(\S+)/)[0]) ] } @dati;
Se avete bisogno di basare l'ordinamento su svariati campi, è utile il
seguente paradigma. @ordinati = sort { campo1($a) <=> campo1($b) ||
campo2($a) cmp campo2($b) ||
campo3($a) cmp campo3($b)
} @data;
Questo può essere convenientemente combinato con il calcolo preventivo
delle chiavi, come descritto sopra.
Per ulteriori informazioni su questo approccio, consultate l'articolo
sort nella collezione ``Far More Than You Ever Wanted To Know''
[``Molto più di quanto avreste mai voluto sapere'', NdT] all'indirizzo http://www.cpan.org/olddoc/FMTEYEWTK.tgz
[in inglese, NdT].
Consultate anche la domanda sull'ordinamento degli hash, riportata
sotto.
Usate pack() e unpack() o anche
vec() e le operazioni a livello di bit.
Per esempio, questo imposta ad 1 i bit di $vet le cui
posizioni sono contenute in @posizioni: $vet = '';
foreach(@posizioni) { vec($vet,$_,1) = 1 }
Ecco come, dato un vettore in $vet, potete ottenere quei
bit nel vostro array @interi: sub vetbit_in_lista {
my $vet = shift;
my @interi;
# Trova la densita` dei byte nulli poi seleziona l'algoritmo migliore
if ($vet =~ tr/\0// / length $vet > 0.95) {
use integer;
my $i;
# Questo metodo e` piu` veloce avendo byte in maggioranza nulli
while($vet =~ /[^\0]/g ) {
$i = -9 + 8 * pos $vet;
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
}
} else {
# Questo metodo e` un algoritmo veloce e generale
use integer;
my $i_bit = unpack "b*", $vet;
push @interi, 0 if $i_bit =~ s/^(\d)// && $1;
push @interi, pos $i_bit while($i_bit =~ /1/g);
}
return \@interi;
}
Questo metodo va tanto più veloce quanto più sparso è il vettore di
bit. (Per gentile concessione di Tim Bunce e Winfried Koenig.)
Potete rendere il ciclo while molto più breve con questo suggerimento
di Benjamin Goldberg: while($vet =~ /[^\0]+/g ) {
push @interi, grep vec($vet, $_, 1), $-[0] * 8 .. $+[0] * 8;
}
Oppure usate il modulo CPAN, Bit::Vector: $vettore = Bit::Vector->new($numero_di_bit);
$vettore->Index_List_Store(@interi);
@interi = $vettore->Index_List_Read();
Bit::Vector fornisce dei metodi efficienti per vettori di bit, insiemi
di piccoli interi e matematica per grandi interi.
Ecco una più estesa illustrazione dell'uso di vec(): # dimostrazione di vec
$vettore = "\xff\x0f\xef\xfe";
print "La stringa di Ilya \\xff\\x0f\\xef\\xfe rappresenta il numero ",
unpack("N", $vettore), "\n";
$settato = vec($vettore, 23, 1);
print "Il suo 23esimo bit vale ", $settato ? "1" : "0", ".\n";
pvet($vettore);
imposta_vet(1,1,1);
imposta_vet(3,1,1);
imposta_vet(23,1,1); imposta_vet(3,1,3);
imposta_vet(3,2,3);
imposta_vet(3,4,3);
imposta_vet(3,4,7);
imposta_vet(3,8,3);
imposta_vet(3,8,7); imposta_vet(0,32,17);
imposta_vet(1,32,17); sub imposta_vet {
my ($posizione, $ampiezza, $valore) = @_;
my $vettore = '';
vec($vettore, $posizione, $ampiezza) = $valore;
print "posizione=$posizione ampiezza=$ampiezza valore=$valore\n";
pvet($vettore);
} sub pvet {
my $vettore = shift;
my $i_bit = unpack("b*", $vettore);
my $i = 0;
my $BASE = 8;
print "lunghezza del vettore in byte: ", length($vettore), "\n";
@i_byte = unpack("A8" x length($vettore), $i_bit);
print "i bit sono: @i_byte\n\n";
}
La risposta breve è che dovreste usare defined solo su
scalari o funzioni, non su aggregati (array e hash). Per maggiori dettagli
si veda perlfunc/defined nella versione del Perl 5.004 o
successiva.
Usate la funzione each() (si veda perlfunc/each)
se l'ordinamento non ha importanza: while ( ($chiave, $valore) = each %hash) {
print "$chiave = $valore\n";
}
Se volete che sia ordinato, dovete usare foreach() sul
risultato dell'ordinamento delle chiavi come mostrato in un quesito
precedente.
Non fatelo. :-)
[lwall] In Perl 4, non era affatto permesso modificare un hash mentre
si strava iterando su di esso. In Perl 5 potete cancellare elementi da
esso, ma non potete aggiungerne, poiché ciò potrebbe causare una
duplicazione della tabella dell'hash, in cui metà delle voci vengono
copiate nella nuova metà alta della tabella; a quel punto avrete
completamente confuso il codice che itera sull'hash. Anche se la tabella
non dovesse duplicarsi, non è possibile determinare se la vostra nuova
voce verrà inserita prima oppure dopo la posizione corrente
dell'iteratore.
La cosa migliore è memorizzare da qualche parte i cambiamenti, e farli
quando l'iterazione finisce, oppure utilizzare keys per
ottenere tutte le vecchie chiavi in un colpo solo, ed iterare sulla lista
delle chiavi.
Create un hash invertito: %per_valore = reverse %per_chiave;
$chiave = $per_valore{$valore};
Questo non è particolarmente efficiente. Sarebbe più efficiente, dal
punto di vista dello spazio, usare: while (($chiave, $valore) = each %per_chiave) {
$per_valore{$valore} = $chiave;
}
Se il vostro hash avesse dei valori ripetuti, i metodi sopra troveranno
solo una delle chiavi associate. Questo potrebbe infastidirvi, o anche no.
Nel caso vi crei problemi, potete sempre invertire l'hash in un hash di
array: while (($chiave, $valore) = each %per_chiave) {
push @{$lista_di_chiavi_per_valore{$valore}}, $chiave;
}
Se volete sapere ``quante chiavi'', allora dovete usare la funzione
keys() in un contesto scalare: $numero_chiavi = keys %hash;
La funzione keys() inoltre riazzera l'iteratore, il che
significa che potreste notare strani risultati quando la utilizzate tra
altri operatori di hash quali ad esempio each().
Internamente, gli hash sono memorizzati in un modo che impedisce
l'imposizione di un ordinamento sulle coppie chiave-valore. Si deve invece
ordinare una lista delle chiavi o dei valori: @chiavi = sort keys %hash; # ordinato per chiave
@chiavi = sort { $hash{$a} cmp $hash{$b}
} keys %hash; # e per valore
Qui sotto facciamo un ordinamento numerico inverso per valore e, se due
chiavi sono identiche, un ordinamento per lunghezza della chiave oppure,
se anch'esso fallisse, per un'esplicita comparazione ASCII delle chiavi
(beh, può darsi venga modificata dal vostro locale, consultate
perllocale). @chiavi = sort {
$hash{$b} <=> $hash{$a}
||
length($b) <=> length($a)
||
$a cmp $b
} keys %hash;
Potete prendere il considerazione l'utilizzo del modulo DB_File e
tie() usando il formato $DB_BTREE come documentato in
DB_File/``In Memory Databases'' [``Database (residenti) in
memoria, NdT]. Anche il modulo Tie::IxHash su CPAN potrebbe essere
istruttivo.
Gli hash contengono coppie di scalari: il primo è la chiave, il secondo
il valore. La chiave sarà convertita in una stringa, sebbene il valore
possa essere qualunque tipo di scalare: stringa, numero o riferimento. Se
una chiave $chiave è presente in %hash,
exists($hash{$chiave}) restituirà vero. Il valore per una
data chiave può essere indefinito, nel qual caso
$hash{$chiave} sarà indefinito mentre exists
$hash{$chiave} restituirà vero. Questo accade nel caso in cui
l'hash contiene ($chiave, undef).
Le figure aiutano... ecco la tabella di %hash: chiavi valori
+------+------+
| a | 3 |
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
E valgono queste condizioni $hash{'a'} e` vero
$hash{'d'} e` falso
defined $hash{'d'} e` vero
defined $hash{'a'} e` vero
exists $hash{'a'} e` vero (solo Perl5)
grep ($_ eq 'a', keys %hash) e` vero
Se ora eseguite undef $hash{'a'}
la vostra tabella diventa: chiavi valori
+------+------+
| a | undef|
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
ed ora valgono queste condizioni; i cambiamenti in maiuscolo: $hash{'a'} e` FALSO
$hash{'d'} e` falso
defined $hash{'d'} e` vero
defined $hash{'a'} e` FALSO
exists $hash{'a'} e` vero (solo Perl5)
grep ($_ eq 'a', keys %hash) e` vero
Notate le ultime due: avete un valore indefinito ma una chiave
definita!
Ora, considerate questo: delete $hash{'a'}
la vostra tabella ora contiene: chiavi valori
+------+------+
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
ed ora valgono queste condizioni; i cambiamenti in maiuscolo: $hash{'a'} e` falso
$hash{'d'} e` falso
defined $hash{'d'} e` vero
defined $hash{'a'} e` falso
exists $hash{'a'} e` FALSO (solo Perl5)
grep ($_ eq 'a', keys %hash) e` FALSO
Guardate, l'intera riga è sparita!
Questo dipende dall'implementazione di EXISTS() dell'hash
legato. Per esempio, non c'è il concetto di indefinito negli hash che sono
legati ai file DBM*. Significa anche che exists() e
defined() fanno la stessa cosa nei file DBM* e quello che
alla fine fanno non è quello che fanno con gli hash ordinari.
Usare keys %hash in un contesto scalare restituisce il
numero di chiavi nell'hash e azzera l'iteratore associato allo
hash. Potreste averne bisogno se usate last per uscire in
anticipo da un loop, in maniera tale che l'iteratore dell'hash sia
azzerato quando rientrate.
Per prima cosa estraete le chiavi dagli hash in liste, poi risolvete il
problema della ``rimozione dei duplicati'' descritto sopra. Per
esempio: %visto = ();
for $elemento (keys(%pippo), keys(%pluto)) {
$visto{$elemento}++;
}
@univ = keys %visto;
oppure in maniera più concisa: @univ = keys %{{%pippo,%pluto}};
Oppure se volete proprio risparmiare spazio: %visto = ();
while (defined ($chiave = each %pippo)) {
$visto{$chiave}++;
}
while (defined ($chiave = each %pluto)) {
$visto{$chiave}++;
}
@univ = keys %visto;
Sia convertendovi a mano la struttura in una stringa (non proprio uno
spasso) sia procurandovi il modulo MLDBM (che usa Data::Dumper) da CPAN e
usandolo sopra DB_File o GDBM_File.
Usate il modulo Tie::IxHash scaricabile da CPAN: use Tie::IxHash;
tie my %miohash, 'Tie::IxHash';
for ($i=0; $i<20; $i++) {
$miohash{$i} = 2*$i;
}
@chiavi = keys %miohash;
# @chiavi = (0,1,2,3,...)
Se eseguite una cosa del genere: unaqualchefunz($hash{"questa chiave non esiste"});
Allora questo elemento si ``autovivifica''; cioè nasce all'improvviso
sia che voi ci memorizziate qualcosa o meno. Il motivo è che le funzioni
ricevono gli scalari passati per riferimento. Se
unaqualchefunz() modifica $_[0], questo deve
esistere già, pronto a essere sovrascritto nella versione del
chiamante.
Questo problema è stato risolto a partire dal Perl5.004.
Di norma, il solo accesso ad un valore di una chiave per una chiave
inesistente, non crea permanentemente la chiave. Questo è
differente dal comportamento di awk.
Di solito con un riferimento ad un hash, probabilmente come questo: $record = {
NOME => "Jason",
NUMIMP => 132,
TITOLO => "povero delegato",
ETA => 23,
STIPENDIO => 37_000,
AMICI => [ "Norbert", "Rhys", "Phineas"],
};
I riferimenti sono documentati in perlref e in
perlreftut. Esempi di strutture dati complesse vengono forniti in
perldsc e perllol. Degli esempi di strutture e classi
orientate agli oggetti sono in perltoot.
Non potete farlo direttamente, ma potete usare il modulo standard
Tie::RefHash distribuito con Perl.
Perl gestisce i dati binari in maniera trasparente, dunque questo non
dovrebbe essere un problema. Per esempio, questo funziona bene (assumendo
che i file vengano trovati): if (`cat /vmunix` =~ /gzip/) {
print "Il vostro kernel contiene GNU-zip!\n";
}
Comunque, su sistemi meno eleganti (leggasi: inutilmente complessi),
dovete fare dei fastidiosi giochi tra file ``di testo'' e ``binari''.
Consultate perlfunc/``binmode'' oppure perlopentut.
Se siete interessati ai dati ASCII ad 8 bit allora consultate
perllocale.
Comunque, se volete avere a che fare con i caratteri multibyte, c'è
qualche questione. Consultate la sezione sulle Espressioni Regolari.
Assumendo che non vi importi delle notazioni IEEE come ``NaN'' o
``Infinity'', probabilmente vi basta usare una espressione regolare. if (/\D/) { print "contiene caratteri non cifra\n" }
if (/^\d+$/) { print "e` un numero naturale\n" }
if (/^-?\d+$/) { print "e` un intero\n" }
if (/^[+-]?\d+$/) { print "e` un intero con +/-\n" }
if (/^-?\d+\.?\d*$/) { print "e` un numero reale\n" }
if (/^-?(?:\d+(?:\.\d*)?|\.\d+)$/) { print "e` un numero decimale\n" }
if (/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/)
{ print "un numero in virgola mobile del C\n" }
Ci sono anche alcuni moduli d'uso comune per questo compito. the Scalar::Util
manpage (distribuito con 5.8) fornisce l'accesso alla funzione interna
di perl looks_like_number per determinare se una variabile
assomiglia ad un numero. the Data::Types
manpage esporta funzioni che validano i tipi di dato utilizzando sia
le precedenti espressioni regolari sia altre. Terzo, c'è
Regexp::Common che ha espressioni regolari per riconoscere
diversi tipi di numeri. Questi tre moduli sono disponibili da CPAN.
Se vi trovate su un sistema POSIX, Perl supporta la funzione
POSIX::strtod. La sue semantica è un po' scomoda, per cui
eccovi prendi_num, una funzione di comodo. Questa funzione
prede una stringa e restituisce il numero che ha trovato, oppure
undef per un input che non è un numero in virgola mobile del
C. La funzione controlla_num è un'interfaccia a
prendi_num se si vuole solo chiedere ``Questo è un numero in
virgola mobile?'' sub prendi_num {
use POSIX qw(strtod);
my $str = shift;
$str =~ s/^\s+//;
$str =~ s/\s+$//;
$! = 0;
my($num, $non_riconosciuto) = strtod($str);
if (($str eq '') || ($non_riconosciuto != 0) || $!) {
return undef;
} else {
return $num;
}
}
sub controlla_num { defined prendi_num($_[0]) }
Potreste invece dare un'occhiata al modulo the String::Scanf
manpage su CPAN. Il modulo POSIX (parte della distribuzione Perl
standard) fornisce strtod e strtol per
convertire stringhe in, rispettivamente, numero in virgola mobile e
interi.
Per alcune specifiche applicazioni, potete usare uno dei moduli DBM. Si
veda AnyDBM_File. In generale dovreste esaminare i moduli
FreezeThaw e Storable da CPAN. A partire dal Perl 5.8, Storable fa parte
della distribuzione standard. Questo è un esempio di uso delle funzioni
store [memorizza, NdT] e retrieve [recupera,
NdT] di Storable: use Storable;
store(\%hash, "nomefile"); # in seguito...
$href = retrieve("nomefile"); # via riferimento
%hash = %{ retrieve("nomefile") }; # direttamente in un hash
Il modulo Data::Dumper su CPAN (oppure nella versione 5.005 di Perl) è
eccellente per stampare strutture dati. Il modulo Storable su CPAN (o la
versione del Perl 5.8), fornisce una funzione chiamata dclone
che copia ricorsivamente il proprio argomento. use Storable qw(dclone);
$r2 = dclone($r1);
dove $r1 può essere un riferimento ad ogni tipo di
struttura dati. Sarà copiata completamente. Dato che dclone
prende e restituisce riferimenti, dovreste aggiungere della punteggiatura
addizionale se avete un hash di array che volete copiare. %nuovohash = %{ dclone(\%vecchiohash) };
Usate la classe UNIVERSAL (si veda UNIVERSAL).
Procuratevi il modulo Business::CreditCard da CPAN.
Il codice kgbpack.c nel modulo PGPLOT su CPAN fa proprio questo. Se
state facendo un sacco di elaborazioni in virgola mobile o in doppia
precisione, prendete invece in considerazione l'uso del modulo PDL da
CPAN, esso rende semplice l'esecuzione di calcoli pesanti.
Copyright (c) 1997-2002 Tom Christiansen e Nathan Torkington. Tutti i
diritti riservati.
La traduzione in italiano è a cura del progetto pod2it ( http://pod2it.sourceforge.net/
)
Questa documentazione è libera; potete ridistribuirla e/o modificarla
secondo gli stessi termini applicati al Perl.
Indipendentemente dalle modalità di distribuzione, tutti gli esempi di
codice in questo file sono rilasciati al pubblico dominio. Potete, e siete
incoraggiati a, utilizzare il presente codice o qualunque forma derivata
da esso nei vostri programmi per divertimento o per profitto. Un semplice
commento nel codice che dia riconoscimento alle FAQ sarebbe cortese ma non
è obbligatorio. ======= =head1 NOME
perlfaq4 - Manipolazione dei dati ($Revision: 1.30 $, $Date: 2005/10/03
18:30:33 $)
Questa sezione delle FAQ risponde a domande relative alle manipolazione
di numeri, date, stringhe, array, hash, ed a varie questioni sui dati.
Internamente, il vostro computer rappresenta i numeri in virgola mobile
in binario. I computer digitali (che lavorano in potenze di due) non
possono memorizzare tutti i numeri in maniera esatta. Alcuni numeri reali
perdono precisione in questo processo. Questo problema è relativo a come i
computer memorizzano i numeri ed incide su tutti i linguaggi di
programmazione, non solo su Perl.
In perlnumber sono mostrati tutti i minimi dettagli della
rappresentazione e conversione dei numeri.
Per limitare il numero di cifre decimali nei numeri, potete usare la
funzione printf o sprintf. Consultate perlop/``Floating-point
Arithmetic'' per maggiori dettagli. printf "%.2f", 10/3; my $numero = sprintf "%.2f", 10/3;
La funzione int() molto probabilmente sta funzionando
bene. Sono i numeri a non essere esattamente quelli che credete.
Per prima cosa, date un'occhiata alla voce sopra ``Perché ottengo una
lunga serie di decimali (es. 19.9499999999999) invece dei numeri che
dovrei ottenere (es. 19.95)?''.
Per esempio, questo print int(0.6/0.2-2), "\n";
nella maggior parte dei computer stamperà 0, non 1, visto che anche
semplici numeri quali 0.6 e 0.2 non possono essere rappresentati
esattamente da numeri in virgola mobile. Quello che pensate essere sopra
un 'tre' è in effetti una cosa più simile a 2.9999999999999995559.
Perl considera tali i numeri ottali ed esadecimali solo quando
compaiono in maniera letterale all'interno del vostro programma. Gli
ottali letterali in perl devono iniziare con uno ``0'', mentre quelli
esadecimali devono essere preceduti da ``0x''. Se i valori vengono letti
da qualche parte e poi assegnati, non viene effettuata alcuna conversione.
Dovete esplicitamente usare oct() oppure hex()
se desiderate che tali valori siano convertiti in decimale.
oct() interpreta sia numeri esadecimali (``0x350'') che
ottali (``0350'' o anche senza lo zero all'inizio, come ``377''), mentre
hex() converte solo gli esadecimali, con o senza ``0x''
all'inizio, come ``0x255'', ``3A'', ``ff'', oppure ``deadbeef''. La
conversione inversa da decimale ad ottale può essere effettuata con i
formati ``%o'' o ``%O'' di sprintf(). Per convertire da decimale ad
esadecimale provate i formati ``%x'' o ``%X'' di sprintf().
Questo problema si presenta spesso quando si cercano di usare chmod(),
mkdir(), umask(), oppure sysopen(), a cui i permessi vengono forniti in
ottale per diffusa tradizione. chmod(644, $file); # SBAGLIATO
chmod(0644, $file); # corretto
Notate che l'errore nella prima linea è stato quello di specificare il
decimale 644, anziché l'ottale 0644. Il problema può essere meglio
osservato così: printf("%#o",644); # stampa 01204
Sicuramente non intendevate eseguire chmod(01204, $file);
- o sì?
Se volete usare valori numerici come argomenti a chmod() e
simili, cercate di esprimerli come ottali letterali, cioè con uno zero
all'inizio e con le cifre successive limitate all'intervallo 0..7.
Ricordate che int() si limita a troncare verso lo 0. Per
arrotondare a un qualche numero di decimali, la via più facile è di solito
sprintf() o printf(). printf("%.3f", 3.1415926535); # stampa 3.142
Il modulo POSIX (parte della distribuzione standard di Perl) implementa
ceil(), floor() e un certo numero di altre funzioni
matematiche e trigonometriche. use POSIX;
$ceil = ceil(3.5); # 4
$floor = floor(3.5); # 3
Nelle versioni dalla 5.000 alla 5.003 di Perl, la trigonometria veniva
fatta dal modulo Math::Complex. Con la versione 5.004, il modulo
Math::Trig (parte della distribuzione standard) implementa le funzioni
trigonometriche. Usa internamente il modulo Math::Complex e alcune
funzioni potrebbero sfuggire dall'asse dei reali verso il piano dei
complessi, ad esempio il seno inverso di 2.
L'arrotondamento può avere serie implicazioni nelle applicazioni
finanziarie, e il metodo di arrotondamento usato dovrebbe essere
specificato con cura. In questi casi, probabilmente è una buona idea non
fidarsi del sistema usato da Perl, qualunque esso sia, ma implementare la
funzione di arrotondamento per conto vostro.
Per vederne il motivo, notate come, anche con printf,
resti un problema di incertezza sui valori intermedi: for ($i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i} 0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
0.8 0.8 0.9 0.9 1.0 1.0
Non prendetevela con Perl. In C è lo stesso. La IEEE dice che va fatto
così. In Perl, i numeri i cui valori assoluti sono interi inferiori a
2**31 (sulle macchine a 32 bit) lavorano abbastanza similmente agli interi
in matematica. Per gli altri tipi di numeri non c'è garanzia.
Come sempre con il Perl, c'è più di un modo di fare le cose. Più sotto
ci sono alcuni esempi di approcci per effettuare delle comuni conversioni
tra rappresentazioni numeriche. Tutto ciò è da intendersi a titolo
esemplificativo piuttosto che esaustivo.
Alcuni degli esempi qui sotto usano il modulo Bit::Vector da CPAN. La
ragione per cui potreste scegliere Bit::Vector rispetto alle funzioni
incorporate in perl è che lavora con numeri di OGNI dimensione, che è
ottimizzato per la velocità su certe operazioni e che, almeno per qualche
programmatore, la notazione usata potrebbe essere familiare.
- Come si convertono esadecimali in decimali
- Usando la conversione incorporata in perl della notazione 0x:
$dec = 0xDEADBEEF;
Usando la funzione hex: $dec = hex("DEADBEEF");
Usando pack: $dec = unpack("N", pack("H8", substr("0" x 8 . "DEADBEEF", -8)));
Usando il modulo Bit::Vector da CPAN: use Bit::Vector;
$vec = Bit::Vector->new_Hex(32, "DEADBEEF");
$dec = $vec->to_Dec();
- Come si converte da decimale ad esadecimale
- Usando sprintf:
$esa = sprintf("%X", 3735928559); # maiuscole A-F
$esa = sprintf("%x", 3735928559); # minuscole a-f
Usando unpack: $esa = unpack("H*", pack("N", 3735928559));
Usando Bit::Vector: use Bit::Vector;
$vet = Bit::Vector->new_Dec(32, -559038737);
$esa = $vet->to_Hex();
Bit::Vector supporta conteggi di bit arbitrari: use Bit::Vector;
$vet = Bit::Vector->new_Dec(33, 3735928559);
$vet->Resize(32); # elimina gli 0 iniziali se non sono voluti
$esa = $vet->to_Hex();
- Come si converte da ottale a decimale
- Usando la conversione incorporata in perl di numeri con 0 iniziali:
$dec = 033653337357; # notate lo 0 iniziale!
Usando la funzione oct: $dec = oct("33653337357");
Usando Bit::Vector: use Bit::Vector;
$vet = Bit::Vector->new(32);
$vet->Chunk_List_Store(3, split(//, reverse "33653337357"));
$dec = $vec->to_Dec();
- Come si converte da decimale ad ottale
- Usando sprintf:
$ott = sprintf("%o", 3735928559);
Usando Bit::Vector: use Bit::Vector;
$vet = Bit::Vector->new_Dec(32, -559038737);
$ott = reverse join('', $vet->Chunk_List_Read(3));
- Come si converte da binario a decimale
- Il Perl 5.6 permette di scrivere numeri binari direttamente con la
notazione 0b:
$numero = 0b10110110;
Usando oct: my $input = "10110110";
$decimale = oct( "0b$input" );
Usando pack e ord: $decimale = ord(pack('B8', '10110110'));
Usando pack e unpack per stringhe di grandi dimensioni: $int = unpack("N", pack("B32",
substr("0" x 32 . "11110101011011011111011101111", -32)));
$dec = sprintf("%d", $int); # substr() e` usata per allungare la stringa con degli zeri
# a sinistra per portarla a 32 caratteri
Usando Bit::Vector: $vet = Bit::Vector->new_Bin(32, "11011110101011011011111011101111");
$dec = $vet->to_Dec();
- Come si converte da decimale a binario
- Usando sprintf (perl 5.6+):
$bin = sprintf("%b", 3735928559);
Usando unpack: $bin = unpack("B*", pack("N", 3735928559));
Usando Bit::Vector: use Bit::Vector;
$vet = Bit::Vector->new_Dec(32, -559038737);
$bin = $vet->to_Bin();
Le rimanenti trasformazioni (ad es. esa -> ott, bin -> esa,
ecc.) sono lasciate come esercizio al lettore volenteroso.
Il comportamento degli operatori artimetici binari varia a seconda che
vengano utilizzati su numeri o stringhe. Gli operatori trattano una
stringa come una serie di bit e lavorano su di essi (la stringa
"3" è la sequenza di bit 00110011). Gli
operatori lavorano con la forma binaria di un numero (il numero
3 è la sequenza di bit 00000011).
Dunque, con 11 & 3 si esegue l'operazione ``and'' su
numeri (produce 3). Con "11" & "3" si compie
l'operazione ``and'' su stringhe (produce "1").
La maggior parte dei problemi con & e |
nasce poiché i programmatori pensano di avere in mano dei numeri, ma in
realtà hanno delle stringhe. I rimanenti problemi nascono dal fatto che i
programmatori scrivono: if ("\020\020" & "\101\101") {
# ...
}
ma una stringa contenente due byte nulli (il risultato di
"\020\020" & "\101\101") non rappresenta un valore falso
in Perl. Dovete scrivere: if ( ("\020\020" & "\101\101") !~ /[^\000]/) {
# ...
}
Usate i moduli Math::Matrix o Math::MatrixReal (disponibili su CPAN)
oppure l'estensione PDL (anch'essa disponibile su CPAN).
Per chiamare una funzione su ciascun elemento di un array e
collezionarne i risultati, usate: @risultati = map { la_mia_funz($_) } @array;
Per esempio: @triplo = map { 3 * $_ } @singolo;
Per chiamare una funzione su ciascun elemento di un array senza
prenderne in considerazione i risultati: foreach $iteratore (@array) {
una_qualche_funz($iteratore);
}
Per chiamare una funzione su ciascun intero in un (breve) intervallo,
potete usare: @risultati = map { una_qualche_funz($_) } (5 .. 25);
ma dovete essere consapevoli che l'operatore .. crea un
array di tutti gli interi nell'intervallo. Per grandi intervalli, questo
potrebbe portar via molta memoria. Usate invece: @risultati = ();
for ($i=5; $i < 500_005; $i++) {
push(@risultati, una_qualche_funz($i));
}
Questa situazione è stata risolta nel Perl 5.005. L'uso di
.. in un ciclo for itererà sull'intervallo senza
crearlo tutto. for my $i (5 .. 500_005) {
push(@risultati, una_qualche_funz($i));
}
non creerà una lista di 500.000 interi.
Procuratevi il modulo http://www.cpan.org/modules/by-module/Roman
.
Se state usando una versione di Perl antecedente alla 5.004, dovete
chiamare srand una volta, all'inizio del vostro programma,
per inizializzare il generatore di numeri casuali. BEGIN { srand() if $] < 5.004 }
La versione 5.004 e le successive chiamano automaticamente
srand all'avvio. Non chiamate srand più di una
volta -- rendereste i vostri numeri meno casuali, non di più.
I calcolatori sono bravi ad essere prevedibili, ma non nell'essere
casuali (malgrado le apparenze causate dagli errori nei vostri programmi
:-). Fate riferimento all'articolo random della collezione ``Far
More Than You Ever Wanted To Know'' [``Molto più di quanto avreste mai
voluto sapere'', NdT], cortesia di Tom Phoenix, che parla di questo
argomento. John Von Neumann disse ``Chiunque tenti di generare numeri
casuali con metodi deterministici vive, ovviamente, nel peccato''.
Se volete numeri casuali più casuali di quanto rand
(assieme a srand) possa fare, dovreste provare anche il
modulo Math::TrulyRandom, disponibile su CPAN. Fa uso delle imperfezioni
dell'orologio di sistema per generare numeri casuali, ma ci vuole un po'
di tempo. Se volete un generatore di numeri pseudocasuali migliore di
quello che il vostro sistema operativo mette a disposizione, consultate
``Numerical Recipes in C'' all'indirizzo http://www.nr.com/.
Usate la semplice funzione che segue. Essa seleziona un intero a caso
tra (e possibilmente includendo!) i due interi dati, ad es.,
intero_a_caso_tra(50,120) sub intero_a_caso_tra ($$) {
my($min, $max) = @_;
# Si assume che i due argomenti siano essi stessi interi!
return $min if $min == $max;
($min, $max) = ($max, $min) if $min > $max;
return $min + int rand(1 + $max - $min);
}
La funzione localtime restituisce il giorno dell'anno. Senza alcun
argomento, localtime utilizza l'orario attuale. $giorno_dell_anno = (localtime)[7];
Il modulo POSIX può anche dare un formato ad una data usando il giorno
dell'anno o la settimana dell'anno. use POSIX qw/strftime/;
my $giorno_dell_anno = strftime "%j", localtime;
my $settimana_dell_anno = strftime "%W", localtime;
Per ottenere il giorni dell'anno per qualsiasi data, utilizzate il
modulo Time::Local per convertire un orario in secondi dall'epoch [data di
riferimento; nella cultura Unix il 1/1/1970 00:00:00, NdT] da passare a
localtime. use POSIX qw/strftime/;
use Time::Local;
my $settimana_dell_anno = strftime "%W",
localtime( timelocal( 0, 0, 0, 18, 11, 1987 ) );
Il modulo Date::Calc fornisce due funzioni per calcolare questi
valori. use Date::Calc;
my $giorno_dell_anno = Day_of_Year( 1987, 12, 18 );
my $settimana_dell_anno = Week_of_Year( 1987, 12, 18 );
Usate le seguenti semplici funzioni: sub secolo {
return int((((localtime(shift || time))[5] + 1999))/100);
} sub millennio {
return 1+int((((localtime(shift || time))[5] + 1899))/1000);
}
Potete anche utilizzare la funzione di POSIX strftime()
che può essere un po' lenta ma che è facile da leggere e da
manutenere. use POSIX qw/strftime/; my $settimana_dell_anno = strftime "%W", localtime;
my $giorno_dell_anno = strftime "%j", localtime;
Su alcuni sistemi, si noterà che la funzione strftime()
del modulo POSIX è stata estesa in maniera non standard per usare il
formato %C, che a volte viene indicato come ``secolo''. Non
lo è, poiché sulla maggior parte di quei sistemi, esso rappresenta solo le
prime due cifre dell'anno a quattro cifre, e quindi non può essere
utilizzato per determinare in maniera affidabile il secolo oppure il
millennio correnti.
Se state memorizzando la data come numero di secondi dall'epoch [data
di riferimento; nella cultura Unix il 1/1/1970 00:00:00, NdT], potete
semplicemente sottrarre una data dall'altra. Se avete in mano una data
strutturata (anno, giorno, mese, ora, minuto e secondo sono cioè valori
ben distinti) allora, per motivi di accessibilità, semplicità, ed
efficienza, usate timelocal o timegm (dal modulo
Time::Local incluso nella distribuzione standard del Perl) per convertire
le date strutturate in secondi dall'epoch. Se non conoscete il formato
preciso delle vostre date, dovreste probabilmente usare i moduli
Date::Manip o Date::Calc da CPAN prima di iniziare a scrivere la vostra
routine che permetta di gestire formati di data arbitrari.
Se la stringa è sufficientemente regolare da avere sempre lo stesso
formato, si può dividerla e passarne le parti a timelocal nel
modulo standard Time::Local. Altrimenti, sarà necessario cercare nei
moduli Date::Calc e Date::Manip dal CPAN.
(*) NdT: data di riferimento; nella cultura Unix il 1/1/1970
00:00:00
(contributo di brian d foy e Dave Cross)
Potete usare il modulo Time::JulianDay disponibile su CPAN.
Assicuratevi tuttavia di voler davvero trovare un giorno Giuliano, visto
che molte persone hanno idee differenti riguardo ai giorni Giuliani. Per
esempio consultate http://www.hermetic.ch/cal_stud/jdn.htm
.
Potete anche provare il modulo DateTime che converte una data/istante
in un Giorno Giuliano. $ perl -MDateTime -le'print DateTime->today->jd'
2453401.5
Oppure il Giorno Giuliano modificato $ perl -MDateTime -le'print DateTime->today->mjd'
53401
Oppure anche il giorno dell'anno (che è la cosa che alcune persone
pensano sia un Giorno Giuliano) $ perl -MDateTime -le'print DateTime->today->doy'
31
Se volete solamente trovare la data (e non il medesimo istante), potete
usare il modulo Date::Calc. use Date::Calc qw(Today Add_Delta_Days); my @data = Add_Delta_Days( Today(), -1 ); print "@data\n";
La maggior parte delle persone provano ad usare l'istante di tempo
piuttosto che il calendario per calcolare le date, ma questo presuppone
che i giorni siano di ventiquattro ore. Per la maggior parte delle
persone, ci sono due giorni all'anno che non lo sono: i giorni del
cambiamento da e verso l'ora estiva (ora legale). Russ Albery offre questa
soluzione. sub ieri {
my $adesso = defined $_[0] ? $_[0] : time;
my $prima = $adesso - 60 * 60 * 24;
my $adesso_legale = (localtime $adesso)[8] > 0;
my $prima_legale = (localtime $prima)[8] > 0;
$prima - ($prima_legale - $adesso_legale) * 60 * 60;
}
Dovrebbe fornire ``quest'ora, ieri'' in secondi dall'epoch [data di
riferimento; nella cultura Unix il 1/1/1970 00:00:00, NdT], relativa al
primo argomento oppure all'ora corrente se non viene specificato alcun
argomento, adatta ad essere passata a localtime() o per
qualsiasi altra cosa abbiate bisogno di farci. $adesso_legale indica se
siamo o meno in ora legale; $prima_legale indica se il punto di 24 ore
prima era in ora legale. Se $adesso_legale e $prima_legale sono uguali,
non è stato passato alcun confine, e la correzione sottrarrà 0. Se
$prima_legale è 1 e $adesso_legale è 0, viene sottratta un'ora in più
dall'ora di ieri, poiché è stata guadagnata un'ora extra mentre si passava
all'ora legale. Se $prima_legale è 0 e $adesso_legale è 1, viene sottratta
un'ora negativa (cioè aggiunta un'ora) all'ora di ieri, poiché è stata
persa un'ora.
Tutto questo accade perché durante quei giorni, quando si esce o entra
da/nell'ora legale, un ``giorno'' non dura 24 ore: o è di 23 o di 25.
L'impostazione esplicita di $adesso_legale e $prima_legale è necessaria
poiché localtime restituisce la struttura tm di sistema, e la
struttura tm di sistema, almeno su Solaris, non garantisce
alcun particolare valore positivo (come, ad esempio, 1) per il campo
isdst, ma solo un valore positivo. E quel valore può anche
essere negativo, se le informazioni sull'ora legale (DST) non sono
disponibili (questa sub tratta quei casi semplicemente come se non ci
fosse l'ora legale).
Notate che tra le 2 e le 3 del mattino del giorno dopo l'uscita della
zona dall'ora legale, l'ora esatta di ``ieri'' corrispondente all'ora
corrente non è chiaramente definita. Osservate inoltre che, se usato tra
le 2 e le 3 del mattino del giorno dopo l'entrata della zona nell'ora
legale, il risultato sarà tra le 3 e le 4 del mattino del giorno
precedente; la correttezza di ciò è discutibile.
Questa sub non tenta di tenere conto dei leap seconds [secondi che
occasionalmente vengono inseriti per correggere l'ora UTC rispetto a
quella basata sulla rotazione terrestre, NdT] (molte cose non fanno
questo).
Risposta breve: No, Perl non ha un problema per quanto riguarda l'anno
2000. Si, Perl è conforme a Y2K (qualsiasi cosa ciò significhi). I
programmatori che avete assunto per usarlo, tuttavia, probabilmente non lo
sono.
Risposta lunga: La domanda impedisce una reale comprensione della
questione. Perl è conforme a Y2K esattamente come la vostra matita--non di
più, e non di meno. Potete usare la vostra matita per scrivere una nota
non conforme a Y2K? Certo che potete. è colpa della matita? Ovviamente
no.
Le funzioni per la data e l'ora fornite con il Perl (gmtime e
localtime) forniscono un'informazione adeguata per determinare l'anno ben
oltre il 2000 (per le macchine a 32 bit i problemi arriveranno nel 2038).
L'anno restituito da queste funzioni quando sono usate in contesto di
lista è l'anno meno 1900. Per gli anni tra il 1910 ed il 1999
capita che esso sia un numero decimale di due cifre. Per evitare
il problema dell'anno 2000 evitate semplicemente di trattare quel numero
come un numero a due cifre. Non lo è.
Quando gmtime() e localtime() sono usate in
contesto scalare, esse restituiscono una stringa ``timestamp'' contenente
il numero completo dell'anno. Per esempio, $timestamp =
gmtime(1005613200) imposta $timestamp a ``Tue Nov 13 01:00:00
2001''. Non c'è alcun problema con l'anno 2000 in questo caso.
Ciò non significa che il Perl non può essere usato per creare programmi
non conformi a Y2K. Può. Ma così può anche la vostra matita. È colpa
dell'utente, non del linguaggio. Rischiando di offendere l'NRA: ``Perl non
viola Y2K, la gente lo fa''. Consultate http://www.perl.org/about/y2k.html
per un'esposizione più lunga.
La risposta a questa domanda è di solito un'espressione regolare,
possibilmente con della logica ausiliaria. Per i dettagli consultate le
domande più specifiche (numeri, indirizzi email, ecc.).
Dipende da cosa si intende con 'escape'. Gli escape delle URL sono
trattati in perlfaq9. Gli escape con il carattere backslash
(``\'') si rimuovono con: s/\\(.)/$1/g;
Questo non espanderà "\n" o "\t" o qualsiasi
altro escape speciale.
Per trasformare "abbcccd" in "abccd": s/(.)\1/$1/g; # aggiungete /s per includere gli 'a capo'
Questa soluzione trasforma "abbcccd" in
"abcd": y///cs; # y == tr, ma e` piu` corta :-)
Questo è documentato in perlref. In generale, la cosa presenta
molti problemi di quoting e di leggibilità, ma è possibile. Per
interpolare una chiamata a subroutine (in contesto di lista) in una
stringa: print "La mia sub quella volta ha restituito @{[miasub(1,2,3)]} .";
Questa non è una cosa che può essere risolta con una sola espressione
regolare, indipendentemente da quanto complessa essa sia. Per trovare
qualcosa compreso tra due caratteri singoli, uno schema come
/x([^x]*)x/ memorizzerà in $1 i caratteri contenuti nel
mezzo. In caso di caratteri multipli, sarà necessario qualcosa come
/alpha(.*?)omega/. Tuttavia, nessuna di queste soluzioni sarà
in grado di gestire gli annidamenti. Per espressioni bilanciate che usano
(, {, [ o < come
delimitatori, usate il modulo Regexp::Common da CPAN, o consultate
perlre/(??{ code }). Negli altri casi, dovrete scrivervi un
parser.
Se siete seriamente intenzionati a scrivere un parser, esiste un certo
numero di moduli e strumenti che vi renderanno la vita molto più facile.
Ci sono i moduli CPAN Parse::RecDescent, Parse::Yapp, e Text::Balanced; ed
il programma byacc. A partire da perl 5.8, Text::Balanced fa parte della
distribuzione standard.
Un approccio semplice, dall'interno e distruttivo che potreste voler
provare, consiste nel tentare di estrarre le parti più piccole una alla
volta: while (s/BEGIN((?:(?!BEGIN)(?!END).)*)END//gs) {
# fate qualcosa con $1
}
Un approccio più complesso e tortuoso consiste nel far fare il lavoro
alle espressioni regolari del perl al posto vostro. Il seguente codice è
di Dean Inada, e sembra partecipare all'Obfuscated Perl Contest, ma
funziona davvero: # $_ contiene la stringa da analizzare
# BEGIN ed END sono i delimitatori di apertura e chiusura per il
# testo tra essi compreso. @( = ('(','');
@) = (')','');
($re=$_)=~s/((BEGIN)|(END)|.)/$)[!$3]\Q$1\E$([!$2]/gs;
@$ = (eval{/$re/},$@!~/unmatched/i);
print join("\n",@$[0..$#$]) if( $$[-1] );
Utilizzate reverse() in contesto scalare, come documentato
in perlfunc/``reverse''. $invertita = reverse $stringa;
Lo si può fare da soli: 1 while $stringa =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
Oppure potete usare il modulo Text::Tabs (che fa parte della
distribuzione standard del Perl). use Text::Tabs;
@linee_espanse = expand(@linee_con_tab);
Utilizzate Text::Wrap (parte della distribuzione standard del
Perl): use Text::Wrap;
print wrap("\t", ' ', @paragrafi);
I paragrafi che si passano a Text::Wrap non devono contenere 'a capo'.
Text::Wrap non giustifica le linee (renderle tutte della stessa
lunghezza).
Oppure utilizzate il modulo CPAN Text::Autoformat. La formattazione di
file può essere fatta semplicemente creando un alias nella shell, in
questo modo: alias fmt="perl -i -MText::Autoformat -n0777 \n
-e 'print autoformat $_, {all=>1}' $*"
Consultate la documentazione di Text::Autoformat per apprezzarne le
molte possibilità.
Potete accedere ai primi caratteri di una stringa con substr(). Per
ottenere il primo carattere, ad esempio, partite dalla posizione 0 e
catturate la stringa di lunghezza 1. $stringa = "Just another Perl Hacker";
$primo_car = substr( $string, 0, 1 ); # 'J'
Per cambiare parte di una stringa, potete usare il quarto parametro
(opzionale), che è la stringa di sostituzione. substr( $stringa, 13, 4, "Perl 5.8.0" );
Potete anche usare substr() a sinistra di un
assegnamento. substr( $stringa, 13, 4 ) = "Perl 5.8.0";
Dovete tenere il conto di N da soli. Ad esempio, diciamo che volete
modificare la quinta occorrenza di "whoever" o
"whomever" in "whosoever" o
"whomsoever", senza preoccuparvi di lettere maiuscole o
minuscole. Ogni esempio assume che $_ contenga la stringa da
modificare. $conto = 0;
s{((whom?)ever)}{
++$conto == 5 # e` la quinta occorrenza?
? "${2}soever" # si`, scambiala
: $1 # lasciala li`
}ige;
Nel caso più generale, potete usare il modificatore /g in
un ciclo while, tenendo il conto delle corrispondenze. $VOGLIO = 3;
$conto = 0;
$_ = "Pesce uno pesce due pesce rosso pesce blu";
while (/\bpesce\s+(\w+)/gi) {
if (++$conto == $VOGLIO) {
print "Il terzo pesce e` $1.\n";
}
}
Che stampa: "Il terzo pesce e` rosso". Potete anche usare
un contatore per le ripetizioni e poi ripetere il pattern, come qui: /(?:\s*pesce\s+\w+){2}\s+pesce\s+(\w+)/i;
Ci sono varie strade, con un diverso grado di efficienza. Se ciò che
desiderate è il conto delle occorrenze di un determinato carattere singolo
(X) all'interno di una stringa, potete utilizzare la funzione
"tr///" in questo modo: $stringa = "QuestaXlineaXhaXalcuneXxXalXsuoXinterno";
$conto = ($stringa =~ tr/X//);
print "Ci sono $conto caratteri X nella stringa";
Questo funziona benissimo quando state cercando un singolo cattere. Se
state cercando di contare le occorrenze di una sottostringa composta da
più caratteri all'interno di una stringa, "tr///" non
funziona. Quello che potete fare è inserire la ricerca globale di un
pattern [schema, NdT] all'interno di un while(). Per esempio, contiamo gli
interi negativi: $stringa = "-9 55 48 -2 23 -76 4 14 -44";
while ($stringa =~ /-\d+/g) { $conto++ }
print "Ci sono $conto interi negativi nella stringa";
Un'altra versione usa una ricerca globale in contesto di lista,
assegnandone poi il risultato ad uno scalare, generando un conto del
numero di sottostringhe trovate. $conto = () = $stringa =~ /-\d+/g;
Per rendere maiuscola la prima lettera di ogni parola: $riga =~ s/\b(\w)/\U$1/g;
Questo codice ha lo strano effetto di convertire "l'ombra
rinfresca" in "L'Ombra Rinfresca". A volte, magari, è
proprio ciò che desiderate. Altre volte potreste aver bisogno di una
soluzione più completa (suggerita da brian d foy): $stringa =~ s/ (
(^\w) #all'inizio della riga
| # o
(\s\w) #preceduto da spazio bianco
)
/\U$1/xg;
$stringa =~ s/([\w']+)/\u\L$1/g;
Per rendere un'intera riga maiuscola: $riga = uc($riga);
Per forzare ciascuna parola ad essere minuscola, con la prima lettera
maiuscola: $riga =~ s/(\w+)/\u\L$1/g;
Potete (e probabilmente dovreste) abilitare la gestione del locale per
i caratteri che necessitano di essa, aggiungendo un use
locale al vostro programma. Consultate perllocale per una
serie infinita di dettagli sul locale.
L'operazione sopra descritta è a volte indicata come il porre qualcosa
in ``title case'' [impostare cioè maiscole e minuscole come nei titoli,
NdT], ma ciò non è del tutto corretto. Prendete ad esempio in
considerazione la corretta scelta delle maiuscole per il film Dottor
Stranamore, Ovvero Come Imparai a Non Preoccuparmi e ad Amare la
Bomba.
Il modulo di Damian Conway the
Text::Autoformat manpage fornisce alcune astute trasformazioni tra
maiuscole e minuscole: use Text::Autoformat;
my $x = "Dottor Stranamore, Ovvero Come Imparai a Non ".
"Preoccuparmi e ad Amare la Bomba"; print $x, "\n";
for my $stile (qw( sentence title highlight ))
{
print autoformat($x, { case => $stile }), "\n";
}
Diversi moduli possono gestire questo tipo di analisi sintattica ---
Text::Balanced, Text::CSV, Text::CSV_XS e Text::ParseWords, tra gli
altri.
Prendete l'esempio di tentare di estrarre da una stringa i singoli
campi, che sono separati da virgole. Non potete utilizzare
split(/,/), poiché non dovete dividere se la virgola si trova
tra virgolette. Per esempio, considerate una linea come la seguente: SAR001,"","Cimetrix, Inc","Bob Smith","CAM",N,8,1,0,7,"Error, Core Dumped"
A causa della restrizione per quanto riguarda i caratteri tra
virgolette, il problema è piuttosto complesso. Fortunatamente, abbiamo
Jeffrey Friedl, autore di Mastering Regular Expressions, che si
occupa della cosa per noi. Suggerisce (ponendo che la vostra stringa sia
contenuta in $testo): @nuovo = ();
push(@nuovo, $+) while $testo =~ m{
"([^\"\\]*(?:\\.[^\"\\]*)*)",? # raggruppa la frase all'interno delle virgolette
| ([^,]+),?
| ,
}gx;
push(@nuovo, undef) if substr($testo,-1,1) eq ',';
Se desiderate inserire delle virgolette all'interno di un campo
delimitato da virgolette, usate dei backslash come escape (ad es.
"in \"questo\" modo".
In alternativa, il modulo Text::ParseWords (contenuto nella
distribuzione standard di Perl) vi permette di scrivere: use Text::ParseWords;
@new = quotewords(",", 0, $text);
C'è anche un modulo TEXT::CSV (Comma-Separated Values [valori separati
da virgola, NdT]) su CPAN.
Sebbene l'approccio più semplice sembrerebbe essere $stringa =~ s/^\s*(.*?)\s*$/$1/;
Ciò non è solo inutilmente lento e distruttivo, ma ha anche problemi se
la stringa contiede degli 'a capo'. é molto più veloce fare questa
operazione in due passi: $stringa =~ s/^\s+//;
$stringa =~ s/\s+$//;
Oppure in maniera più elegante: for ($stringa) {
s/^\s+//;
s/\s+$//;
}
Questa espressione idiomatica sfrutta il comportamento con alias [il $_
, NdT] di foreach per raccogliere il codice a fattor comune.
Si può fare questo su più righe alla volta, su array o anche sui valori di
un hash se si usa una slice [selettore per parte di una lista, NdT]: # toglie gli spazi dallo scalare, dagli elementi dell'array
# ed in tutti i valori dell'hash
foreach ($scalare, @array, @hash{keys %hash}) {
s/^\s+//;
s/\s+$//;
}
Nei seguenti esempi, $lungh è la lunghezza cui vogliamo
portare la stringa, $testo o $num contengono la
stringa da allungare, e $carat contiene il carattere con cui
riempire. Se si conosce questo carattere in anticipo, si può usare una
costante composta da una stringa di un solo carattere invece della
variabile $carat. E allo stesso modo potete usare un intero
al posto di $lungh se conoscete già la lunghezza.
Il metodo più semplice utilizza la funzione sprintf. Può
riempire sulla sinistra o sulla destra con spazi, sulla sinistra con zeri
e non troncherà il risultato. La funzione pack può solo
riempire le stringhe con degli spazi sulla destra e troncherà il risultato
fino ad una lunghezza massima di $lungh. # Riempimento di una stringa a sinistra con spazi (nessun troncamento):
$riempito = sprintf("%${lungh}s", $testo);
$riempito = sprintf("%*s", $lungh, $testo); # stessa cosa # Riempimento di una stringa a destra con spazi (nessun troncamento):
$riempito = sprintf("%-${lungh}s", $testo);
$riempito = sprintf("%-*s", $lungh, $testo); # stessa cosa # Riempimento di un numero a sinistra con zeri (nessun troncamento):
$riempito = sprintf("%0${lungh}d", $num);
$riempito = sprintf("%0*d", $lungh, $num); # stessa cosa # Riempimento di una stringa a destra con spazi usando pack (verra` troncata):
$riempito = pack("A$lungh",$testo);
Se si ha la necessità di riempire con un carattere che non sia lo
spazio o lo zero, si può usare uno dei metodi seguenti. Generano tutti una
stringa di riempimente usando l'operatore x e la combinano
con $testo. Questi metodi non troncano
$testo.
Riempimento a sinistra e a destra con qualsiasi carattere, creando una
nuova stringa: $riempito = $carat x ( $lungh - length( $testo) ) . $testo;
$riempito = $testo. $carat x ( $lungh - length( $testo) );
Riempimento a sinistra e a destra con qualsiasi carattere, modificando
direttamente $testo: substr( testo, 0, 0 ) = $carat x ( $lungh - length( $testo) );
$testo.= $carat x ( $lungh - length( $testo) );
Usate substr() o unpack(), entrambe documentate in
perlfunc. Se preferite pensare in termini di colonne invece che
di larghezze, potete usare qualcosa del tipo: # determina il formato di unpack necessario per separare l'output
# del 'ps' di Linux
# gli argomenti sono le colonne a cui tagliare i campi
my $fmt = taglia_a_formato(8, 14, 20, 26, 30, 34, 41, 47, 59, 63, 67, 72); sub taglia_a_formato {
my(@posizioni) = @_;
my $template = '';
my $ultimapos = 1;
for my $pos (@posizioni) {
$template .= "A" . ($pos - $ultimapos) . " ";
$ultimapos = $pos;
}
$template .= "A*";
return $template;
}
(contributo di brian d foy)
Potete usare il modulo Text::Soundex. Se volete fare un match fuzzy o
preciso, potreste provare i moduli String::Approx, Text::Metaphone e
Text::DoubleMetaphone.
Assumete di avere una stringa che contengono delle variabili
segnaposto: $testo = 'questa contiene un $pippo e un $pluto';
Potete usare una sostituzione con una doppia valutazione. Il primo /e
converte $1 in $pippo e il secondo /e converte
$pippo nel suo valore. Potreste volerlo racchiudere in un
eval: se provate ad ottenere il valore di una variabile non
dichiarata mentre si è in esecuzione sotto use strict,
otterrete un errore bloccante. eval { $testo =~ s/(\$\w+)/$1/eeg };
die if $@;
Probabilmente è meglio, in generale, considerare queste variabili come
elementi di qualche hash apposito. Per esempio: %def_utente = (
pippo => 23,
pluto => 19,
);
$testo =~ s/\$(\w+)/$def_utente{$1}/g;
Il problema è che questo rendere stringa mediante le virgolette
converte a forza numeri e riferimenti in stringhe, anche quando non volete
che lo siano. Prendetela così: l'espansione con le virgolette viene usata
per produrre nuove stringhe. Se avete già una stringa, perché ne volete
un'altra?
Se siete abituati a scrivere cose strane come queste: print "$var"; # SCORRETTO
$nuovo = "$vecchio"; # SCORRETTO
unafunz("$var"); # SCORRETTO
vi troverete nei guai. Queste dovrebbero (nel 99,8% dei casi) essere le
forme più semplici e dirette: print $var;
$nuovo = $vecchio;
unafunz($var);
D'altronde, oltre a essere più lunghe da scrivere, finirete per
introdurre errori nel codice quando la cosa contenuta nello scalare non è
né una stringa né un numero, ma un riferimento: funz(\@array);
sub funz {
my $arif = shift;
my $orif = "$arif"; # SBAGLIATO
}
Potreste anche andare incontro a problemi sottili con quelle poche
operazioni in Perl che sentono la differenza tra una stringa ed un numero,
quali il magico operatore di auto-incremento ++, oppure la
funzione syscall().
Forzare a stringa distrugge anche gli array. @linee = `comando`;
print "@linee"; # SBAGLIATO - spazi aggiuntivi
print @linee; # giusto
Controllate le seguenti tre condizioni:
- Non ci devono essere spazi dopo <<.
-
- Deve (probabilmente) esserci un punto e virgola alla
fine.
-
- Non potete (agevolmente) inserire alcuno spazio davanti
all'etichetta.
Se desiderate incolonnare il testo negli here document, potete fare
così: # tutto in uno
($VAR = <<HERE_FINE) =~ s/^\s+//gm;
il vostro testo
va inserito qui
HERE_FINE
Ma la HERE_FINE deve comunque trovarsi al margine. Se desiderate che
anch'essa sia incolonnata, dovete mettere tra apici anche
l'incolonnamento: [la citazione è dal Signore degli Anelli, e si trova
effettivamente nei sorgenti di perl, NdT] ($citazione = <<' FINIS') =~ s/\s+//gm;
...we will have peace, when you and all your works have
perished--and the works of your dark master to whom you
would deliver us. You are a liar, Saruman, and a corrupter
of men's hearts. --Theoden in /usr/src/perl/taint.c
FINIS
$citazione =~ s/\s+--/\n--/;
Di seguito è riportata una funzione generale di ripulitura per gli here
document incolonnati. Essa si aspetta di ricevere uno here document come
argomento. Essa controlla che ciascuna linea inizi con una determinata
sottostringa e, nel caso, la rimuove. Altrimenti, prende il numero di
spazi bianchi all'inizio della prima riga e rimuove tale numero di
caratteri da ciascuna delle linee successive. sub pulisci {
local $_ = shift;
my ($bianco, $inizio); # spazio bianco comune e stringa iniziale comune
if (/^\s*(?:([^\w\s]+)(\s*).*\n)(?:\s*\1\2?.*\n)+$/) {
($bianco, $inizio) = ($2, quotemeta($1));
} else {
($bianco, $inizio) = (/^(\s+)/, '');
}
s/^\s*?$inizio(?:$bianco)?//gm;
return $_;
}
Questa soluzione funziona con stringhe particolari all'inizio, che
vengono determinate dinamicamente: $ricorda_il_main = pulisci<<' MAIN_INTERPRETER_LOOP';
@@@ int
@@@ runops() {
@@@ SAVEI32(runlevel);
@@@ runlevel++;
@@@ while ( op = (*op->op_ppaddr)() );
@@@ TAINT_NOT;
@@@ return 0;
@@@ }
MAIN_INTERPRETER_LOOP
Oppure con uno spazio iniziale fisso, preservando il restante
incolonnamento: [la citazione è tratta dal Signore degli Anelli, e si
trova effettivamente nei sorgenti di perl, NdT] $poesia = pulisci<<EVER_ON_AND_ON;
Now far ahead the Road has gone,
And I must follow, if I can,
Pursuing it with eager feet,
Until it joins some larger way
Where many paths and errands meet.
And whither then? I cannot say.
--Bilbo in /usr/src/perl/pp_ctl.c
EVER_ON_AND_ON
Un array ha una lunghezza modificabile. Una lista no. Un array è
qualcosa su cui si possono usare push e pop,
mentre una lista è una sequenza di valori. Alcune persone fanno questa
distinzione: una lista è un valore mentre un array è una variabile. Alle
subroutine si passano liste e restituiscono liste, si mettono cose in un
contesto di lista, si inizializzano gli array con delle liste, e si fanno
cicli con foreach() su liste. Le variabili @
sono array, gli array anonimi sono array, gli array in un contesto scalare
si comportano come il numero di elementi contenuto in essi, le subroutine
accedono ai loro argomenti attraverso l'array @_ e
push/pop/shift lavorano solo sugli
array.
Come nota a margine, non esistono ``liste in un contesto scalare''.
Quando scrivete: $scalare = (2, 5, 7, 9);
state usando l'operatore virgola in un contesto scalare, per cui viene
usato l'operatore scalare virgola. Non c'è davvero mai stata alcuna lista
qui! Questo causa la restituzione dell'ultimo valore: 9.
Il primo è un valore scalare; il secondo è una slice [porzione, NdT] di
un array, che lo rende una lista con un solo valore (uno scalare). Si
dovrebbe usare $ quando si vuole un valore scalare (quasi sempre) e @
quando si vuole una lista contentente un solo valore scalare (molto, molto
di rado; in pratica, quasi mai).
Talvolta non fa alcuna differenza, ma talvolta la fa. Per esempio,
confrontate: $giusto[0] = `un programma che restituisce in output varie linee`;
con @sbagliato[0] = `stesso programma che restituisce in output varie linee`;
La direttiva use warnings ed il flag -w
vi metteranno in guardia riguardo a queste faccende.
Ci sono diversi modi possibili, a seconda che l'array sia o meno
ordinato e che si desideri o meno mantenere l'ordinamento.
- a)
- Se
@in è ordinato e si vuole che @out sia
ordinato: (con questo si assume che l'array contenga tutti valori veri) $prec = "non uguale a $in[0]";
@out = grep($_ ne $prec && ($prec = $_, 1), @in);
Questo è un buon metodo per il fatto che non usa molta memoria
aggiuntiva, simulando il comportamento di uniq(1) che
rimuove solo i duplicati adiacenti. Il ``, 1'' garantisce che
l'espressione sia vera (in modo che grep prenda l'elemento) anche se
$_ è 0, ``'' oppure undef.
- b)
- Se non si sa se
@in sia ordinato o meno: undef %visto;
@out = grep(!$visto{$_}++, @in);
- c)
- Come (b) ma
@in contiene solo interi piccoli: @out = grep(!$visto[$_]++, @in);
- d)
- Un modo per ottenere (b) senza cicli né grep:
undef %visto;
@visto{@in} = ();
@visto = sort keys %visto; # rimuovere sort se non lo volete
- e)
- Come (d), ma @in contiene solo piccoli interi positivi:
undef @array;
@array[@in] = @in;
@out = grep {defined} @array;
Ma forse avreste dovuto utilizzare un hash fin dall'inizio, eh?
Già sentire la parola ``in'' è un'indicazione che avreste
probabilmente dovuto utilizzare un hash, non una lista o un array, per
memorizzare i vostri dati. Gli hash sono progettati per offrire una
risposta rapida ed efficiente a questa domanda. Gli array no.
Detto questo, ci sono molti modi per risolvere la questione. Se dovete
fare questa operazione molte volte su stringhe arbitrarie, la via più
veloce è probabilmente quella di invertire l'array originale e creare un
hash le cui chiavi sono gli elementi dell'array. @blu = qw/azzurro ceruleo celeste turchese oltremare/;
%un_blu = ();
for (@blu) { $un_blu{$_} = 1 }
Ora potete controllare se è $un_blu{$un_colore}. Potrebbe essere stata
una buona idea quella di memorizzare i blu in un hash sin dall'inizio.
Se i valori sono tutti interi piccoli, potete usare un semplice array
indicizzato. Questo tipo di array utilizzerà meno spazio: @primi = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31);
@un_piccolo_primo = ();
for (@primi) { $un_piccolo_primo[$_] = 1 }
# o semplicemente @un_piccolo_primo[@primi] = (1) x @primi;
Ora potete controllare se è $un_piccolo_primo[$un_numero].
Se i valori in questione sono interi anziché stringhe, potete salvare
molto spazio utilizzando le stringhe di bit: @articoli = ( 1..10, 150..2000, 2017 );
undef $letti;
for (@articoli) { vec($letti,$_,1) = 1 }
Controllate ora se vec($letti,$n,1) è vero per un
determinato $n.
Per favore non usate: ($presente) = grep $_ eq $qualcosa, @array;
o peggio ancora: ($presente) = grep /$qualcosa/, @array;
Queste soluzioni sono lente (controllano tutti gli elementi anche se il
primo corrisponde a quello cercato), inefficienti (stessa ragione) e
potenzialmente sbagliate (cosa succede se ci sono caratteri di espressioni
regolari in $qualcosa?). Se dovete effettuare la ricerca solo
una volta, usate: $presente = 0;
foreach $elem (@array) {
if ($elem eq $elem_da_trovare) {
$presente = 1;
last;
}
}
if ($presente) { ... }
Usate un hash. Di seguito c'è il codice con entrambe le risposte e
oltre. Si assume che in un dato array, ogni elemento sia univoco. @unione = @intersezione = @differenza = ();
%conteggio = ();
foreach $elemento (@array1, @array2) { $conteggio{$elemento}++ }
foreach $elemento (keys %conteggio) {
push @unione, $elemento;
push @{
$conteggio{$elemento} > 1 ?
\@intersezione :
\@differenza
}
, $elemento;
Notate che questa è la differenza simmetrica, il che significa
tutti gli elementi contenuti in A oppure in B, ma non in entrambi. Pensate
ad essa come ad una operazione xor.
Il codice di seguito riportato funziona per array ad un solo livello.
Utilizza un confronto di stringhe e non distingue i valori indefiniti
dalle stringhe vuote ma definite. Modificatelo se avete esigenze
diverse. $sono_uguali = confronta_array(\@rane, \@rospi); sub confronta_array {
my ($primo, $secondo) = @_;
no warnings; # zittisce le proteste di -w sugli undef
return 0 unless @$primo == @$secondo;
for (my $i = 0; $i < @$primo; $i++) {
return 0 if $primo->[$i] ne $secondo->[$i];
}
return 1;
}
Per le strutture multilivello, potreste voler ricorrere ad un approccio
come il seguente. Utilizza il modulo CPAN FreezeThaw: use FreezeThaw qw(cmpStr);
@a = @b = ( "questo", "quello", [ "ancora", "roba" ] ); printf "'a' e 'b' contengono %s array\n",
cmpStr(\@a, \@b) == 0
? "gli stessi"
: "diversi";
Questo sistema funziona anche per il confronto degli hash. Qui di
seguito sono mostrate due diverse risposte: use FreezeThaw qw(cmpStr cmpStrHard); %a = %b = ( "questo" => "quello", "extra" => [ "ancora", "roba" ] );
$a{EXTRA} = \%b;
$b{EXTRA} = \%a; printf "'a' e 'b' contengono %s hash\n",
cmpStr(\%a, \%b) == 0 ? "gli stessi" : "diversi"; printf "'a' e 'b' contengono %s hash\n",
cmpStrHard(\%a, \%b) == 0 ? "gli stessi" : "diversi";
La prima risposta indica che entrambi gli hash contengono gli stessi
dati, mentre la seconda indica il contrario. La scelta di quale sia per
voi preferibile è lasciato come esercizio al lettore.
Per trovare il primo elemento di un array che soddisfa una condizione,
potete utlizzare la funzione first() nel modulo List::Util,
fornita con il Perl 5.8. Questo esempio trova il primo elemento che
contiene ``Perl''. use List::Util qw(first); my $elemento = first { /Perl/ } @array;
Se non potete usare List::Util, potete scrivervi un ciclo per fare la
stessa cosa. Una volta che avete trovato l'elemento, fermate il ciclo con
last. my $trovato;
foreach my $elemento ( @array )
{
if( /Perl/ ) { $trovato = $elemento; last }
}
Se volete l'indice dell'elemento, potete iterare sugli indici e
controllare l'elemento dell'array ad ogni indice fino a che non trovate
quello che verifica la condizione. my( $trovato, $indice ) = ( undef, -1 );
for( $i = 0; $i < @array; $i++ )
{
if( $array[$i] =~ /Perl/ )
{
$trovato = $array[$i];
$indice = $i;
last;
}
}
In generale, non avete bisogno di liste collegate in Perl, poiché con i
normali array potete estrarre ed aggiungere elementi sia dalla testa che
dalla coda (con push, pop, shift e
unshift), oppure potete usare splice per
inserire o rimuovere un numero arbitrario di elementi in punti arbitrari
dell'array. Sia pop che shift sono operazioni
O(1) sugli array dinamici di Perl. In assenza di operazioni
di shift e pop, push in generale
deve riallocare un numero di volte nell'ordine di log(N), e
unshift ha bisogno di copiare i puntatori ogni volta che
viene utilizzata.
Se proprio davvero volete, potete utilizzare le strutture descritte in
perldsc o perltoot e fare esattamente ciò che il libro
di algoritmi vi dice. Per esempio, immaginate un nodo di una lista come il
seguente: $nodo = {
VALORE => 42,
COLLEGAMENTO => undef,
};
Potete scorrere la lista nel seguente modo: print "Lista: ";
for ($nodo = $testa; $nodo; $nodo = $nodo->{COLLEGAMENTO}) {
print $nodo->{VALORE}, " ";
}
print "\n";
Potete aggiungere elementi alla lista così: my ($testa, $coda);
$coda = aggiungi($testa, 1); # crea una nuova testa
for $valore ( 2 .. 10 ) {
$coda = aggiungi($coda, $valore);
} sub aggiungi {
my($lista, $valore) = @_;
my $nodo = { VALORE => $valore };
if ($lista) {
$nodo->{COLLEGAMENTO} = $lista->{COLLEGAMENTO};
$lista->{COLLEGAMENTO} = $nodo;
} else {
$_[0] = $nodo; # sostituisce la versione del chiamante
}
return $nodo;
}
Ma, di nuovo, gli array forniti da Perl sono abbastanza validi
praticamente sempre.
Le liste circolari possono essere trattate nella maniera tradizionale
con liste collegate, oppure potete fare qualcosa di questo tipo con gli
array: unshift(@array, pop(@array)); # l'ultimo sara` il primo
push(@array, shift(@array)); # e vice versa
Se avete installato Perl 5.8.0 o successivo, o avete installato
Scalar-List-Utils 1.03 o successivo, potete scrivere: use List::Util 'shuffle'; @mescolato = shuffle(@lista);
In caso contrario, potete utilizzare l'argoritmo di mescolamento
Fisher-Yates: sub mescola_fisher_yates {
my $mazzo = shift; # $mazzo e` un riferimento ad un array
my $i = @$mazzo;
while ($i--) {
my $j = int rand ($i+1);
@$mazzo[$i,$j] = @$mazzo[$j,$i];
}
} # mescola la mia collezione di mpeg
#
my @mpeg = <audio/*/*.mp3>;
mescola_fisher_yates( \@mpeg ); # mescola @mpeg "sul posto"
print @mpeg;
Notate che l'implementazione sopra indicata mescola un array ``sul
posto'', a differenza di List::Util::shuffle() che prende una lista e ne
restituisce una nuova mescolata.
Avete probabilmente visto algoritmi di mescolamento che funzionano
utilizzando splice, prendendo a caso un elemento da scambiare
con quello corrente. srand;
@nuovo = ();
@vecchio = 1 .. 10; # solo una dimostrazione
while (@vecchio) {
push(@nuovo, splice(@vecchio, rand @nuovo, 1));
}
Questa è una cattiva pratica, poiché splice è già
O(N) e, poiché lo eseguite N volte, avete appena inventato un
algoritmo quadratico; il che significa O(N**2). Esso non scala bene, anche
se Perl è così efficiente che probabilmente non noterete la cosa finché
non avrete array piuttosto grandi.
Usate for/foreach: for (@linee) {
s/pippo/pluto/; # cambia quella parola
y/XZ/XZ/; # scambia quelle lettere
}
Ecco un altro metodo; calcoliamo i volumi sferici: for (@volumi = @raggi) { # @volumi ha parti cambiate
$_ **= 3;
$_ *= (4/3) * 3.14159; # questo calcolo diventera` una costante
}
che può essere fatto anche con map() che è fatto apposta
per trasformare una lista in un'altra: @volumi = map {$_ **3 * (4/3) * 3.14159 } @raggi;
Se volete fare la stessa cosa per modificare i valori di un hash,
potete servirvi della funzione values. Con Perl 5.6 i valori
non vengono copiati, quindi se modificate $orbita (in questo caso),
modificate il valore. for $orbita ( values %orbite ) {
($orbita **= 3) *= (4/3) * 3.14159;
}
Prima di perl 5.6 values restituiva copie dei valori,
dunque il codice perl più vecchio spesso contiene costruzioni come
@orbite{keys %orbite} al posto di values %orbite
quando l'hash deve essere modificato.
Usate la funzione rand() (consultate
perlfunc/rand): $indice = rand @array;
$elemento = $array[$indice];
O semplicemente: my $elemento = $array[ rand @array ];
Usate il modulo List::Permutor su CPAN. Se la lista è in effetti un
array, provate il modulo Algorithm::Permute (anch'esso su CPAN). È scritto
in codice XS ed è molto efficiente. use Algorithm::Permute;
my @array = 'a'..'d';
my $p_iteratore = Algorithm::Permute->new ( \@array );
while (my @perm = $p_iteratore->next) {
print "prossima permutazione: (@perm)\n";
}
Per un'esecuzione ancora più veloce, potreste fare: use Algorithm::Permute;
my @array = 'a'..'d';
Algorithm::Permute::permute {
print "prossima permutazione: (@array)\n";
} @array;
Ecco un piccolo programma che genera tutte le permutazioni di tutte le
parole su ciascuna linea di input. L'algoritmo racchiuso nella funzione
permute() è discusso nel Volume 4 (ancora non pubblicato) di
The Art of Computer Programming [L'Arte della Programmazione dei
Computer, NdT] di Knuth e funzionerà su qualsiasi lista: #!/usr/bin/perl -n
# generatore ordinato di permutazioni Fischer-Kause sub permuta (&@) {
my $codice = shift;
my @ind = 0..$#_;
while ( $codice->(@_[@ind]) ) {
my $p = $#ind;
--$p while $ind[$p-1] > $ind[$p];
my $q = $p or return;
push @ind, reverse splice @ind, $p;
++$q while $ind[$p-1] > $ind[$q];
@ind[$p-1,$q]=@ind[$q,$p-1];
}
} permuta {print"@_\n"} split;
Fornite una funzione di confronto a sort() (descritta in
perlfunc/sort): @lista = sort { $a <=> $b } @lista;
La funzione di ordinamento predefinita è cmp, confronto
tra stringhe, che ordinerebbe (1, 2, 10) in (1, 10,
2). <=>, utilizzato sopra, è l'operatore di
confronto numerico.
Se avete bisogno di una funzione complessa che estragga la parte su cui
desiderate basare l'ordinamento, non scrivetela all'inerno della funzione
di ordinamento. Estraete tale parte prima, poiché il BLOCCO di ordinamento
può essere chiamato molte volte per lo stesso elemento. Ecco un esempio di
come estrarre la prima parola dopo il primo numero da ciascun elemento, e
poi ordinare tali parole senza distinguere lettere maiuscole da lettere
minuscole. @indice = ();
for (@dati) {
($chiave) = /\d+\s*(\S+)/;
push @indice, uc($chiave);
}
@ordinati = @dati[ sort { $indice[$a] cmp $indice[$b] } 0 .. $#indice ];
che potrebbe anche essere scritto come segue, utilizzando un trucco
diventato noto come la Trasformata di Schwartz: @ordinati = map { $_->[0] }
sort { $a->[1] cmp $b->[1] }
map { [ $_, uc( (/\d+\s*(\S+)/)[0]) ] } @dati;
Se avete bisogno di basare l'ordinamento su svariati campi, è utile il
seguente paradigma. @ordinati = sort { campo1($a) <=> campo1($b) ||
campo2($a) cmp campo2($b) ||
campo3($a) cmp campo3($b)
} @data;
Questo può essere convenientemente combinato con il calcolo preventivo
delle chiavi, come descritto sopra.
Per ulteriori informazioni su questo approccio, consultate l'articolo
sort nella collezione ``Far More Than You Ever Wanted To Know''
[``Molto più di quanto avreste mai voluto sapere'', NdT] all'indirizzo http://www.cpan.org/olddoc/FMTEYEWTK.tgz
[in inglese, NdT].
Consultate anche la domanda sull'ordinamento degli hash, riportata
sotto.
Usate pack() e unpack() o anche
vec() e le operazioni a livello di bit.
Per esempio, questo imposta ad 1 i bit di $vet le cui
posizioni sono contenute in @posizioni: $vet = '';
foreach(@posizioni) { vec($vet,$_,1) = 1 }
Ecco come, dato un vettore in $vet, potete ottenere quei
bit nel vostro array @interi: sub vetbit_in_lista {
my $vet = shift;
my @interi;
# Trova la densita` dei byte nulli poi seleziona l'algoritmo migliore
if ($vet =~ tr/\0// / length $vet > 0.95) {
use integer;
my $i;
# Questo metodo e` piu` veloce avendo byte in maggioranza nulli
while($vet =~ /[^\0]/g ) {
$i = -9 + 8 * pos $vet;
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
push @interi, $i if vec($vet, ++$i, 1);
}
} else {
# Questo metodo e` un algoritmo veloce e generale
use integer;
my $i_bit = unpack "b*", $vet;
push @interi, 0 if $i_bit =~ s/^(\d)// && $1;
push @interi, pos $i_bit while($i_bit =~ /1/g);
}
return \@interi;
}
Questo metodo va tanto più veloce quanto più sparso è il vettore di
bit. (Per gentile concessione di Tim Bunce e Winfried Koenig.)
Potete rendere il ciclo while molto più breve con questo suggerimento
di Benjamin Goldberg: while($vet =~ /[^\0]+/g ) {
push @interi, grep vec($vet, $_, 1), $-[0] * 8 .. $+[0] * 8;
}
Oppure usate il modulo CPAN, Bit::Vector: $vettore = Bit::Vector->new($numero_di_bit);
$vettore->Index_List_Store(@interi);
@interi = $vettore->Index_List_Read();
Bit::Vector fornisce dei metodi efficienti per vettori di bit, insiemi
di piccoli interi e matematica per grandi interi.
Ecco una più estesa illustrazione dell'uso di vec(): # dimostrazione di vec
$vettore = "\xff\x0f\xef\xfe";
print "La stringa di Ilya \\xff\\x0f\\xef\\xfe rappresenta il numero ",
unpack("N", $vettore), "\n";
$settato = vec($vettore, 23, 1);
print "Il suo 23esimo bit vale ", $settato ? "1" : "0", ".\n";
pvet($vettore);
imposta_vet(1,1,1);
imposta_vet(3,1,1);
imposta_vet(23,1,1); imposta_vet(3,1,3);
imposta_vet(3,2,3);
imposta_vet(3,4,3);
imposta_vet(3,4,7);
imposta_vet(3,8,3);
imposta_vet(3,8,7); imposta_vet(0,32,17);
imposta_vet(1,32,17); sub imposta_vet {
my ($posizione, $ampiezza, $valore) = @_;
my $vettore = '';
vec($vettore, $posizione, $ampiezza) = $valore;
print "posizione=$posizione ampiezza=$ampiezza valore=$valore\n";
pvet($vettore);
} sub pvet {
my $vettore = shift;
my $i_bit = unpack("b*", $vettore);
my $i = 0;
my $BASE = 8;
print "lunghezza del vettore in byte: ", length($vettore), "\n";
@i_byte = unpack("A8" x length($vettore), $i_bit);
print "i bit sono: @i_byte\n\n";
}
La risposta breve è che dovreste usare defined solo su
scalari o funzioni, non su aggregati (array e hash). Per maggiori dettagli
si veda perlfunc/defined nella versione del Perl 5.004 o
successiva.
Usate la funzione each() (si veda perlfunc/each)
se l'ordinamento non ha importanza: while ( ($chiave, $valore) = each %hash) {
print "$chiave = $valore\n";
}
Se volete che sia ordinato, dovete usare foreach() sul
risultato dell'ordinamento delle chiavi come mostrato in un quesito
precedente.
(contributo di brian d foy)
La risposta facile è ``Non fatelo!''
Se iterate attraverso l'hash con each(), potete tranquillamente
cancellare la chiave restituita più recentemente. Se cancellate o
aggiungete altre chiavi, l'iteratore potrebbe saltarle o passarci due
volte poiché perl può riarrangiare la tabella dell'hash. Consultate la
voce each() in perlfunc.
Create un hash invertito: %per_valore = reverse %per_chiave;
$chiave = $per_valore{$valore};
Questo non è particolarmente efficiente. Sarebbe più efficiente, dal
punto di vista dello spazio, usare: while (($chiave, $valore) = each %per_chiave) {
$per_valore{$valore} = $chiave;
}
Se il vostro hash avesse dei valori ripetuti, i metodi qui sopra
troveranno solo una delle chiavi associate. Questo potrebbe infastidirvi,
o anche no. Nel caso vi crei problemi, potete sempre invertire l'hash in
un hash di array: while (($chiave, $valore) = each %per_chiave) {
push @{$lista_di_chiavi_per_valore{$valore}}, $chiave;
}
Se volete sapere ``quante chiavi'', allora dovete usare la funzione
keys() in un contesto scalare: $numero_chiavi = keys %hash;
La funzione keys() inoltre riazzera l'iteratore, il che
significa che potreste notare strani risultati quando la utilizzate tra
altri operatori di hash quali ad esempio each().
Internamente, gli hash sono memorizzati in un modo che impedisce
l'imposizione di un ordinamento sulle coppie chiave-valore. Si deve invece
ordinare una lista delle chiavi o dei valori: @chiavi = sort keys %hash; # ordinato per chiave
@chiavi = sort { $hash{$a} cmp $hash{$b}
} keys %hash; # e per valore
Qui sotto facciamo un ordinamento numerico inverso per valore e, se due
chiavi sono identiche, un ordinamento per lunghezza della chiave oppure,
se anch'esso fallisse, per un'esplicita comparazione ASCII delle chiavi
(beh, può darsi venga modificata dal vostro locale, consultate
perllocale). @chiavi = sort {
$hash{$b} <=> $hash{$a}
||
length($b) <=> length($a)
||
$a cmp $b
} keys %hash;
Potete prendere il considerazione l'utilizzo del modulo DB_File e
tie() usando il formato $DB_BTREE come documentato in
DB_File/``In Memory Databases'' [``Database (residenti) in
memoria, NdT]. Anche il modulo Tie::IxHash su CPAN potrebbe essere
istruttivo.
Gli hash contengono coppie di scalari: il primo è la chiave, il secondo
il valore. La chiave sarà convertita in una stringa, sebbene il valore
possa essere qualunque tipo di scalare: stringa, numero o riferimento. Se
una chiave $chiave è presente in %hash,
exists($hash{$chiave}) restituirà vero. Il valore per una
data chiave può essere indefinito, nel qual caso
$hash{$chiave} sarà indefinito mentre exists
$hash{$chiave} restituirà vero. Questo accade nel caso in cui
l'hash contiene ($chiave, undef).
Le figure aiutano... ecco la tabella di %hash: chiavi valori
+------+------+
| a | 3 |
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
E valgono queste condizioni $hash{'a'} e` vero
$hash{'d'} e` falso
defined $hash{'d'} e` vero
defined $hash{'a'} e` vero
exists $hash{'a'} e` vero (solo Perl5)
grep ($_ eq 'a', keys %hash) e` vero
Se ora eseguite undef $hash{'a'}
la vostra tabella diventa: chiavi valori
+------+------+
| a | undef|
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
ed ora valgono queste condizioni; i cambiamenti in maiuscolo: $hash{'a'} e` FALSO
$hash{'d'} e` falso
defined $hash{'d'} e` vero
defined $hash{'a'} e` FALSO
exists $hash{'a'} e` vero (solo Perl5)
grep ($_ eq 'a', keys %hash) e` vero
Notate le ultime due: avete un valore indefinito ma una chiave
definita!
Ora, considerate questo: delete $hash{'a'}
la vostra tabella ora contiene: chiavi valori
+------+------+
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
ed ora valgono queste condizioni; i cambiamenti in maiuscolo: $hash{'a'} e` falso
$hash{'d'} e` falso
defined $hash{'d'} e` vero
defined $hash{'a'} e` falso
exists $hash{'a'} e` FALSO (solo Perl5)
grep ($_ eq 'a', keys %hash) e` FALSO
Guardate, l'intera riga è sparita!
Questo dipende dall'implementazione di EXISTS() dell'hash
legato. Per esempio, non c'è il concetto di indefinito negli hash che sono
legati ai file DBM*. Significa anche che exists() e
defined() fanno la stessa cosa nei file DBM* e quello che
alla fine fanno non è quello che fanno con gli hash ordinari.
Usare keys %hash in un contesto scalare restituisce il
numero di chiavi nell'hash e azzera l'iteratore associato allo
hash. Potreste averne bisogno se usate last per uscire in
anticipo da un loop, in maniera tale che l'iteratore dell'hash sia
azzerato quando rientrate.
Per prima cosa estraete le chiavi dagli hash in liste, poi risolvete il
problema della ``rimozione dei duplicati'' descritto sopra. Per
esempio: %visto = ();
for $elemento (keys(%pippo), keys(%pluto)) {
$visto{$elemento}++;
}
@univ = keys %visto;
oppure in maniera più concisa: @univ = keys %{{%pippo,%pluto}};
Oppure se volete proprio risparmiare spazio: %visto = ();
while (defined ($chiave = each %pippo)) {
$visto{$chiave}++;
}
while (defined ($chiave = each %pluto)) {
$visto{$chiave}++;
}
@univ = keys %visto;
Sia convertendovi a mano la struttura in una stringa (non proprio uno
spasso) sia procurandovi il modulo MLDBM (che usa Data::Dumper) da CPAN e
usandolo sopra DB_File o GDBM_File.
Usate il modulo Tie::IxHash scaricabile da CPAN: use Tie::IxHash;
tie my %miohash, 'Tie::IxHash';
for ($i=0; $i<20; $i++) {
$miohash{$i} = 2*$i;
}
@chiavi = keys %miohash;
# @chiavi = (0,1,2,3,...)
Se eseguite una cosa del genere: unaqualchefunz($hash{"questa chiave non esiste"});
Allora questo elemento si ``autovivifica''; cioè nasce all'improvviso
sia che voi ci memorizziate qualcosa o meno. Il motivo è che le funzioni
ricevono gli scalari passati per riferimento. Se
unaqualchefunz() modifica $_[0], questo deve
esistere già, pronto a essere sovrascritto nella versione del
chiamante.
Questo problema è stato risolto a partire dal Perl5.004.
Di norma, il solo accesso ad un valore di una chiave per una chiave
inesistente, non crea permanentemente la chiave. Questo è
differente dal comportamento di awk.
Di solito con un riferimento ad un hash, probabilmente come questo: $record = {
NOME => "Jason",
NUMIMP => 132,
TITOLO => "povero delegato",
ETA => 23,
STIPENDIO => 37_000,
AMICI => [ "Norbert", "Rhys", "Phineas"],
};
I riferimenti sono documentati in perlref e in
perlreftut. Esempi di strutture dati complesse vengono forniti in
perldsc e perllol. Degli esempi di strutture e classi
orientate agli oggetti sono in perltoot.
Non potete farlo direttamente, ma potete usare il modulo standard
Tie::RefHash distribuito con Perl.
Perl gestisce i dati binari in maniera trasparente, dunque questo non
dovrebbe essere un problema. Per esempio, questo funziona bene (assumendo
che i file vengano trovati): if (`cat /vmunix` =~ /gzip/) {
print "Il vostro kernel contiene GNU-zip!\n";
}
Comunque, su sistemi meno eleganti (leggasi: inutilmente complessi),
dovete fare dei fastidiosi giochi tra file ``di testo'' e ``binari''.
Consultate perlfunc/``binmode'' oppure perlopentut.
Se siete interessati ai dati ASCII ad 8 bit allora consultate
perllocale.
Comunque, se volete avere a che fare con i caratteri multibyte, c'è
qualche questione. Consultate la sezione sulle Espressioni Regolari.
Assumendo che non vi importi delle notazioni IEEE come ``NaN'' o
``Infinity'', probabilmente vi basta usare una espressione regolare. if (/\D/) { print "contiene caratteri non cifra\n" }
if (/^\d+$/) { print "e` un numero naturale\n" }
if (/^-?\d+$/) { print "e` un intero\n" }
if (/^[+-]?\d+$/) { print "e` un intero con +/-\n" }
if (/^-?\d+\.?\d*$/) { print "e` un numero reale\n" }
if (/^-?(?:\d+(?:\.\d*)?|\.\d+)$/) { print "e` un numero decimale\n" }
if (/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/)
{ print "un numero in virgola mobile del C\n" }
Ci sono anche alcuni moduli d'uso comune per questo compito. the Scalar::Util
manpage (distribuito con 5.8) fornisce l'accesso alla funzione interna
di perl looks_like_number per determinare se una variabile
assomiglia ad un numero. the Data::Types
manpage esporta funzioni che validano i tipi di dato utilizzando sia
le precedenti espressioni regolari sia altre. Terzo, c'è
Regexp::Common che ha espressioni regolari per riconoscere
diversi tipi di numeri. Questi tre moduli sono disponibili da CPAN.
Se vi trovate su un sistema POSIX, Perl supporta la funzione
POSIX::strtod. La sue semantica è un po' scomoda, per cui
eccovi prendi_num, una funzione di comodo. Questa funzione
prede una stringa e restituisce il numero che ha trovato, oppure
undef per un input che non è un numero in virgola mobile del
C. La funzione controlla_num è un'interfaccia a
prendi_num se si vuole solo chiedere ``Questo è un numero in
virgola mobile?'' sub prendi_num {
use POSIX qw(strtod);
my $str = shift;
$str =~ s/^\s+//;
$str =~ s/\s+$//;
$! = 0;
my($num, $non_riconosciuto) = strtod($str);
if (($str eq '') || ($non_riconosciuto != 0) || $!) {
return undef;
} else {
return $num;
}
}
sub controlla_num { defined prendi_num($_[0]) }
Potreste invece dare un'occhiata al modulo the String::Scanf
manpage su CPAN. Il modulo POSIX (parte della distribuzione Perl
standard) fornisce strtod e strtol per
convertire stringhe in, rispettivamente, numero in virgola mobile e
interi.
Per alcune specifiche applicazioni, potete usare uno dei moduli DBM. Si
veda AnyDBM_File. In generale dovreste esaminare i moduli
FreezeThaw e Storable da CPAN. A partire dal Perl 5.8, Storable fa parte
della distribuzione standard. Questo è un esempio di uso delle funzioni
store [memorizza, NdT] e retrieve [recupera,
NdT] di Storable: use Storable;
store(\%hash, "nomefile"); # in seguito...
$href = retrieve("nomefile"); # via riferimento
%hash = %{ retrieve("nomefile") }; # direttamente in un hash
Il modulo Data::Dumper su CPAN (oppure nella versione 5.005 di Perl) è
eccellente per stampare strutture dati. Il modulo Storable su CPAN (o la
versione del Perl 5.8), fornisce una funzione chiamata dclone
che copia ricorsivamente il proprio argomento. use Storable qw(dclone);
$r2 = dclone($r1);
dove $r1 può essere un riferimento ad ogni tipo di
struttura dati. Sarà copiata completamente. Dato che dclone
prende e restituisce riferimenti, dovreste aggiungere della punteggiatura
addizionale se avete un hash di array che volete copiare. %nuovohash = %{ dclone(\%vecchiohash) };
Usate la classe UNIVERSAL (si veda UNIVERSAL).
Procuratevi il modulo Business::CreditCard da CPAN.
Il codice kgbpack.c nel modulo PGPLOT su CPAN fa proprio questo. Se
state facendo un sacco di elaborazioni in virgola mobile o in doppia
precisione, prendete invece in considerazione l'uso del modulo PDL da
CPAN, esso rende semplice l'esecuzione di calcoli pesanti.
Copyright (c) 1997-2005 Tom Christiansen, Nathan Torkington e altri
autori menzionati. Tutti i diritti riservati.
La traduzione in italiano è a cura del progetto pod2it ( http://pod2it.sourceforge.net/
)
Questa documentazione è libera; potete ridistribuirla e/o modificarla
secondo gli stessi termini applicati al Perl.
Indipendentemente dalle modalità di distribuzione, tutti gli esempi di
codice in questo file sono rilasciati al pubblico dominio. Potete, e siete
incoraggiati a, utilizzare il presente codice o qualunque forma derivata
da esso nei vostri programmi per divertimento o per profitto. Un semplice
commento nel codice che dia riconoscimento alle FAQ sarebbe cortese ma non
è obbligatorio. >>>>>>> 1.28
Mon Oct 24 04:01:47 2005 |