The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NOME

perlfaq4 - Manipolazione dei dati ($Revision: 1.33 $, $Date: 2006/12/04 16:45:26 $)

DESCRIZIONE

Questa sezione delle FAQ risponde a domande relative alle manipolazione di numeri, date, stringhe, array, hash, ed a varie questioni sui dati.

Dati: Numeri

Perché ottengo una lunga serie di decimali (es. 19.9499999999999) invece dei numeri che dovrei ottenere (es. 19.95)?

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 "Floating-point Arithmetic" in perlop per maggiori dettagli.

        printf "%.2f", 10/3;

        my $numero = sprintf "%.2f", 10/3;

Come mai int() non funziona?

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.

Perché i miei dati ottali non vengono interpretati correttamente?

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.

Perl ha una funzione round()? E ceil() e floor()? E le funzioni trigonometriche?

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 si effettuano le conversioni tra rappresentazioni numeriche?

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.

Perché & non funziona come voglio io?

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]/) {
        # ...
    }

Come si moltiplicano le matrici?

Usate i moduli Math::Matrix o Math::MatrixReal (disponibili su CPAN) oppure l'estensione PDL (anch'essa disponibile su CPAN).

Come si effettuano delle operazioni su una serie di interi?

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.

Come posso mostrare in output i numeri romani?

Procuratevi il modulo http://www.cpan.org/modules/by-module/Roman .

Perché i miei numeri casuali non sono casuali?

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/.

Come posso ottenere un numero a caso tra X e Y?

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);
    }

Dati: Date

Come ottengo la settimana o il giorno dell'anno?

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 );

Come ottengo il secolo oppure il millennio correnti?

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.

Come confronto due date per trovarne la distanza?

(contributo di brian d foy)

Potete semplicemente immagazzinare le vostre date come un numero e poi fare una sottrazione. Tuttavia, la vita non è mai stata così semplice. Se volete lavorare con le date formattate, possono aiutarvi i moduli Date::Manip, Date::Calc, oppure DateTime.

Come posso prendere una stringa e convertirla in secondi dall'epoch (*)?

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

Come posso trovare il Giorno Giuliano?

(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

Come trovo la data di ieri?

(contributo di brian d foy)

Usate uno dei moduli della gerarchia Date. Il modulo DateTime rende le cose facili, e vi fornisce lo stesso orario del giorno, solamente del giorno prima.

        use DateTime;

        my $ieri = DateTime->now->subtract( days => 1 );

        print "Ieri era $ieri\n";

Potete anche usare il modulo Date::Calc usando la sua funzione Today_and_Now [Oggi_e_Ora, NdT].

        use Date::Calc qw( Today_and_Now Add_Delta_DHMS );

        my @data_ora = Add_Delta_DHMS( Today_and_Now(), -1, 0, 0, 0 );

        print "@date\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, NdT]. Lasciamo fare il lavoro ai moduli.

Il Perl ha un problema per l'anno 2000? Il Perl è conforme a Y2K?

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.

Dati: Stringhe

Come si controlla la validità di un input?

(contributo di brian d foy)

Ci sono molti modi per assicurarsi che i valori siano quello che vi aspettate o che volete accettare. In aggiunta agli specifici esempi che trattiamo nella perlfaq, potete anche dare un'occhiata ai moduli che hanno "Assert" e "Validate" [Asserire e Convalidare, NdT] nei loro nomi, insieme ad altri moduli quali Regexp::Common.

Alcuni moduli hanno delle validazioni per particolari tipi di input, quali Business::ISBN, Business::CreditCard, Email::Valid e Data::Validate::IP.

Come rimuovo gli escape da una stringa?

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.

Come rimuovo coppie consecutive di caratteri?

(contributo di brian d foy)

Potete usare l'operatore di sostituzione per trovare le coppie di caratteri (oppure sequenze di caratteri) e sostituirli con una singola istanza. In questa sostituzione, troviamo un carattere in (.). Le parentesi di cattura permettono di riferirsi con la back-reference [riferimento a qualcosa di precedente, NdT] \1 al carattere corrispendente, e questo fatto si usa per imporre che tale carattere sia seguito da un carattere uguale. Sostituiamo questa parte della stringa con il carattere in $1.

        s/(.)\1/$1/g;

Possiamo anche usare l'operatore di trasliettarazione, tr///. In questo esempio, la parte con l'elenco di ricerca del nostro tr/// non contiene nulla. Anche l'elenco di sostituzione non contiene nulla, dunque la traslitterazione è quasi una non-operazione visto che non farà alcuna sostituzione (o più esattamente, sostituisce un carattere con se stesso). Ad ogni modo, l'opzione s schiaccia nella stringa i caratteri duplicati e consecutivi così che un carattere non appaia due volte di fila.

        my $str = 'Haarlem';   # nei Paesi Bassi
        $str =~ tr///cs;       # Ora Harlem, come a New York

Come espando le chiamate a funzione in una stringa?

(contributo di brian d foy)

Questo è documentato in perlref e sebbene non sia la cosa piè semplice da leggere, funziona davvero. In ognuno di questi esempi, chiamiamo la funzione all'interno delle parentesi utilizzate per deferenziare un riferimento. Se abbiamo più di un valore restituito, possiamo costruire e deferenziare un array anonimo. In questo caso, chiamiamo la funzione in contesto di lista.

        print "I valori dell'istante di tempo sono @{ [localtime] }.\n";

Se vogliamo chiamare la funzione in contesto scalare, dobbiamo fare un po' più di lavoro. All'interno delle parentesi possiamo avere davvero qualsiasi tipo di codice, dunque si deve semplicemente terminare con il riferimento scalare, sebbene come questo si debba fare sia lasciato a voi, e potete usare del codice all'interno delle parentesi.

        print "L'istante di tempo e` ${\(scalar localtime)}.\n"

        print "L'istante di tempo e` ${ my $x = localtime; \$x }.\n";

Se la vostra funzione restituisce già un riferimento, non è necessario che vi creiate il riferimento.

        sub marcatemporale { my $t = localtime; \$t }

        print "L'istante di tempo e` ${ marcatemporale() }.\n";

Anche il modulo Interpolation può fare un sacco di cose magiche per voi. Potete specificare il nome di una variabile, in questo caso E, per impostare un hash sottoposto a tie che vi faccia l'interpolazione. Possiede anche diversi altri metodi per farlo.

        use Interpolation E => 'eval';
        print "I valori dell'istante di tempo sono $E{localtime()}.\n";

Nella maggior parte dei casi, è probabilmente più semplice utilizzare la concatenazione di stringhe, che inoltre forza un contesto scalare.

        print "L'istante di tempo e` " . localtime . ".\n";

Come trovo coppie corrispondenti/annidate di qualcosa?

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 "(??{ code })" in perlre. 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] );

Come inverto una stringa?

Utilizzate reverse() in contesto scalare, come documentato in "reverse" in perlfunc.

    $invertita = reverse $stringa;

Come espando i tab in una 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);

Come si riformatta un paragrafo?

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à.

Come posso accedere o cambiare N caratteri di una stringa?

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";

Come si fa a modificare la N-esima occorrenza di qualcosa?

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;

Come conto il numero di occorrenze di una sottostringa all'interno di una stringa?

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;

Come rendo maiuscola la prima lettera di tutte le parole di una riga?

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 Text::Autoformat 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";
    }

Come posso dividere una stringa delimitata da [carattere], tranne quando mi trovo tra [carattere]?

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.

Come si possono togliere gli spazi dall'inizio/fine di una stringa?

(contributo di brian d foy)

Una sostituzione può farlo per voi. Per una singola linea, volete sostituire tutti gli spazi ad inizio/fine stringa con nulla. Potete farlo con un paio di sostituzioni.

        s/^\s+//;
        s/\s+$//;

Potete anche scriverlo come una singola sostituzione, ma anche se funziona, l'istruzione congiunta è più lenta di quelle separate. Tuttavia per voi questo potrebbe non avere importanza.

        s/^\s+|\s+$//g;

In questa espressione regolare, l'alternativa [|, NdT] effettua un match o all'inizio o alla fine della stringa dato che le ancore [^ e $, NdT] hanno una precedenza più bassa rispetto all'alternativa. Con l'opzione /g, la sostituzione effettua tutti i possibili match, dunque li ottiene entrambi. Ricordate, i ritorni a capo a fine linea trovano corrispondenza in \s+ e l'ancora $ può effettuare il match sulla parte fisica della fine della stringa, dunque anche il ritorno a capo scompare. Aggiungete semplicemente il ritorno a capo all'output, che ha l'ulteriore vantaggio di preservare le linee "vuote" (formate interamente da spazi) che verrebbero rimosse tutte dalla la parte ^\s+.

        while( <> )
                {
                s/^\s+|\s+$//g;
                print "$_\n";
                }

Per una stringa su più linee, potete applicare l'espressione regolae ad ogni linea logica della stringa aggiungendo l'opzione /m (che sta per "linee multiple"). Con l'opzione /m, $ effettua un match prima di un ritorno a capo interno e dunque non lo rimuove. Continua però a rimuovere il ritorno a capo alla fine della stringa.

        $stringa =~ s/^\s+|\s+$//gm;

Ricordate che le linee che consistono interamente di spazi spariranno, visto che la prima parte dell'alternativa può effettuare il match dell'intera stringa e sostituirla con nulla. Se servisse mantenere le linee vuote interne, dovreste fare un po' di lavoro in più. Invece di effettuare il match su ogni spazio (visto che questo include i ritorni a capo), effettuate il match semplicemente sull'altro spazio.

        $stringa =~ s/^[\t\f ]+|[\t\f ]+$//mg;

Come porto a lunghezza una stringa con spazi oppure un numero con zeri?

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) );

Come estraggo determinate colonne da una stringa?

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

Come trovo il valore soundex di una stringa?

(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.

Come si espandono le variabili nelle stringhe di testo?

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;

Cosa c'è di sbagliato nel racchiudere sempre le "$varabili" tra virgolette?

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

Come mai i miei <<HERE document non funzionano?

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

Dati: Array

Qual è la differenza tra una lista ed un array?

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.

Qual è la differenza tra $array[1] e @array[1]?

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.

Come posso rimuovere gli elementi duplicati da una lista o da un array?

(contributo di brian d foy)

Usate un hash. Quando state pensando alle parole "unico" oppure "duplicato", state pensando alle "chiavi di un hash".

Se non vi importa dell'ordine degli elementi, potreste semplicemente creare l'hash e poi estrarre le chiavi. Non è importante come create quell'hash: ma è importante che usiate la funzione keys per ottenere gli elementi unici.

   my %hash   = map { $_, 1 } @array;
   # oppure uno slice di hash: @hash{ @array } = ();
   # oppure un foreach: $hash{$_} = 1 foreach ( @array );

   my @unici = keys %hash;

Potete anche iterare per ogni elemento e tralasciare quelli che avete già incontrato. Utilizzate un hash per tenerne traccia. La prima volta che nel loop si incontra un elemento, questo elemento non ha alcuna chiave in %visti. L'istruzione next crea la chiave ed usa immediatamente il suo valore, che è undef, di modo che il loop vada avanti alla push ed incrementi il valore per quella chiave. La volta successiva che nel loop si incontra lo stesso elemento, la sua chiave esiste già nell'hash e il valore per quella chiave è vero (visto che non è né 0 né undef), dunque il next salta l'iterazione e nel loop si va all'elemento successivo.

        my @unici = ();
        my %visti   = ();

        foreach my $elemento ( @array )
                {
                next if $visti{ $elemento }++;
                push @unici, $elemento;
                }

Potete scriverlo in maniera più concisa usando un grep, che fa la medesima cosa.

   my %visti = ();
   my @unici = grep { ! $visti{ $_ }++ } @array;

Come posso determinare se un certo elemento è contenuto in una lista o in un array?

(parti di questa risposta sono un contributo di Anno Siegel)

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 }

Ora controllate se vec($letti,$n,1) è vero per un determinato $n.

Questi metodi garantiscono che i test individuali siano eseguiti rapidamente ma richiedono una riorganizzazione dell'originale lista o array. Sono redditizzi solo se dovete testare valori multipli sullo stesso array.

Se ne state testando solo uno, il modulo standard List::Util esporta, a questo scopo, la funzione first. Funziona fermandosi una volta che ha trovato l'elemento. È scritta in C per la velocità ed il suo equivalente in Perl assomiglia a questa subroutine:

        sub first (&@) {
                my $codice = shift;
                foreach (@_) {
                        return $_ if &{$codice}();
                }
                undef;
        }

Se la velocità è una cosa di poco interesse, lo stile più diffuso è quello di utilizzare grep in contesto scalare (che restituisce il numero di elementi che hanno verificato la sua condizione) per traversare l'intera lista. Tuttavia, questo ha il sicuro vantaggio di rivelarvi quante corrispondenze ha trovato.

        my $presente = grep $_ eq $qualsiasicosa, @array;

Se in realtà volete estrarre gli elementi che effettuano il match, usate semplicemente grep in contesto di lista.

        my @match = grep $_ eq $qualsiasicosa, @array;

Come si calcola la differenza di due array? Come si calcola l'intersezione tra due array?

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.

Come stabilisco se due array o hash sono uguali?

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.

Come trovo il primo elemento di un array per il quale sia vera una determinata condizione?

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 ( @array )
                {
                if( /Perl/ ) { $trovato = $_; 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;
                        }
                }

Come gestico le liste collegate?

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.

Come si trattano le liste circolari?

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

Come mescolo a caso gli elementi di un array?

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.

Come tratto/modifico ciascun elemento di un array?

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.

Come si fa a selezionare a caso un elemento da un array?

Usate la funzione rand() (consultate "rand" in perlfunc):

     $indice   = rand @array;
     $elemento = $array[$indice];

O semplicemente:

     my $elemento = $array[ rand @array ];

Come permuto N elementi di una lista?

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;

Come ordino un array per (qualcosa)?

Fornite una funzione di confronto a sort() (descritta in "sort" in perlfunc):

    @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.

Come si manipolano gli array di bit?

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";
    }

Perché defined() restituisce vero su array e hash vuoti?

La risposta breve è che dovreste usare defined solo su scalari o funzioni, non su aggregati (array e hash). Per maggiori dettagli si veda "defined" in perlfunc nella versione del Perl 5.004 o successiva.

Dati: Hash (Array Associativi)

Come si compie un'elaborazione su un intero hash?

Usate la funzione each() (si veda "each" in perlfunc) 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.

Cosa succede se aggiungo o rimuovo chiavi da un hash mentre sto iterando su di esso?

(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.

Come si cerca un elemento di un hash per valore?

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

Come si può sapere quanti elementi ci sono in un hash?

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().

Come si ordina un hash (opzionalmente per valore invece che per chiave)?

(contributo di brian d foy)

Per ordinare un hash, partite dalle chiavi. In questo esempio forniamo la lista delle chiavi alla funzione che le ordina, che le compara ASCIIbeticalmente (questo potrebbe essere influenzato dalle vostre impostazioni del locale). La lista in output ha le chiavi in ordine ASCIIbetico. Una volta che avete le chiavi, le si potrebbe esaminare per creare un resoconto che faccia un elenco delle chiavi in ordine ASCIIbetico.

        my @chiavi = sort { $a cmp $b } keys %hash;

        foreach my $chiave ( @chiavi )
                {
                printf "%-20s %6d\n", $chiave, $hash{$chiave};
                }

Tuttavia, potremmo avere un blocco sort() più stravagante. Invece di fare un confronto delle chiavi, con queste si può calcolare un valore ed usarlo come confronto.

Per esempio, per rendere il nostro resoconto non dipendente dalle maiuscole/minuscole, in una stringa racchiusa tra virgolette doppie si usa la sequenza \L per rendere tutto minuscolo. Il blocco sort() dunque fa il confronto dei valori resi minuscoli per determinare in quale ordine mettere le chiavi.

        my @chiavi = sort { "\L$a" cmp "\L$b" } keys %hash;

Nota: se il calcolo è dispendioso oppure l'hash ha molti elementi, potreste voler dare un'occhiata alla Schwartzian Transform [Trasformata di Schwartz, NdT] per mettere in cache i risultati del calcolo.

Se invece si vuole ordinare per valore dell'hash, si usa la chiave dell'hash per recuperare il valore. Si ottiene ancora una lista di chiavi, ma stavolta sono ordinate per il loro valore.

        my @chiavi = sort { $hash{$a} <=> $hash{$b} } keys %hash;

Da qui si possono ottenere cose più complesse. Se i valori dell'hash sono gli stessi, si può fornire un ordinamento secondario sulla chiave dell'hash.

        my @chiavi = sort {
                $hash{$a} <=> $hash{$b}
                        or
                "\L$a" cmp "\L$b"
                } keys %hash;

Come posso mantenere sempre ordinato il mio hash?

Potete prendere il considerazione l'utilizzo del modulo DB_File e tie() usando il formato $DB_BTREE come documentato in "In Memory Databases" in DB_File ["Database (residenti) in memoria, NdT]. Anche il modulo Tie::IxHash su CPAN potrebbe essere istruttivo.

Qual è la differenza tra "delete" e "undef" con gli hash?

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!

Perché i miei hash legati fanno distinzione tra defined ed exists?

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.

Come azzero un'operazione each() parzialmente eseguita?

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.

Come posso ottenere le chiavi univoche da due hash?

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;

Come posso memorizzare un array multidimensionale in un file DBM?

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.

Come posso fare in modo che il mio hash ricordi l'ordine in cui ho inserito gli elementi al suo interno?

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,...)

Perché passare ad una subroutine un elemento indefinito in un hash, lo crea?

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.

Come posso costruire l'equivalente Perl di una struttura C/classe C++/hash o array di hash o array?

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.

Come posso usare un riferimento come una chiave di un hash?

(contributo di brian d foy)

Le chiavi di un hash sono stringhe, dunque non potete proprio usare un riferimento come chiave. Quando tentate di farlo, perl converte il riferimento nella sua forma di stringa (per esempio, HASH(0xDEADBEEF)). Da qui non potete riottenere il riferimento dalla forma di stringa, almeno non senza che voi facciate del lavoro in più. Ricordatevi anche che le chiavi di un hash devono essere univoche ma che due differenti variabili possono memorizzare lo stesso riferimento (e in seguito queste variabili possono cambiare).

Il modulo Tie::RefHash, che è distribuito con perl, potrebbe essere quello di cui avete bisogno. Gestisce quel lavoro in più.

Data: Varie

Come si gestiscono correttamente i dati binari?

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 "binmode" in perlfunc 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.

Come si fa a determinare se uno scalare è un numero/naturale/intero/in virgola mobile?

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. Scalar::Util (distribuito con 5.8) fornisce l'accesso alla funzione interna di perl looks_like_number per determinare se una variabile assomiglia ad un numero. Data::Types 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 String::Scanf 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.

Come posso mantenere la persistenza dei dati tra le diverse invocazioni di un programma?

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

Come si stampa o si copia una struttura dati ricorsiva?

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) };

Come si definiscono i metodi per ogni classe/oggetto?

Usate la classe UNIVERSAL (si veda UNIVERSAL).

Come si verifica il valore di controllo di una carta di credito?

Procuratevi il modulo Business::CreditCard da CPAN.

Come si impacchettano gli array di numeri in doppia precisione o in virgola mobile, per codice XS?

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.

AUTORE E COPYRIGHT

Copyright (c) 1997-2006 Tom Christiansen, Nathan Torkington e altri autori menzionati. Tutti i diritti riservati.

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.

TRADUZIONE

Versione

La versione su cui si basa questa traduzione è ottenibile con:

   perl -MPOD2::IT -e print_pod perlfaq4

Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/ .

Traduttore

Traduzione a cura di Michele Beltrame.

Revisore

Revisione a cura di Gianni Ceccarelli e dree.