Daniele Ludovici
Mettiamoli a confronto
http://www.perl.it/documenti/articoli/2008/11/mettiamoli-a-co.html
© Perl Mongers Italia. Tutti i diritti riservati.

Perl offre un supporto alla manipolazione degli array molto comodo: possiamo allungarli ed accorciarli a nostro piacimento a run-time senza preoccuparci di troppe cose, crearli in svariate maniere, ciclarci sopra in modi diversi... Ma avete mai provato a confrontare una coppia di array?

In questo articolo cercheremo di approfondire alcune nostre conoscenze sulle operazioni possibili su una coppia di array, infatti in taluni casi può esser molto utile confrontare due collezioni di dati per vedere dove e in cosa differiscono. Ci viene in aiuto per questi compiti il comodissimo Array::Compare.

Facciamo subito un piccolo esempio: creiamo due array che contengono i numeri da 0 a 10

my @arr1 = 0 .. 10;
my @arr2 = 0 .. 10;

istanziamo un oggetto della classe Array::Compare tramite il costruttore new

my $comp = Array::Compare->new;

su questo oggetto andiamo a chiamare il metodo 'compare', che accetta due riferimenti ad array e restituisce - ci credereste - vero o falso a seconda dell'esito del confronto:

if ($comp->compare(\@arr1, \@arr2)) {
  print "Array identici\n";
} else {
  print "Array differenti\n";
}

Ma come fa il nostro modulo a far tutto ciò? Internamente quello che viene fatto per confrontare i due array non è nient'altro che un join tra tutti gli elementi di ciascun array: questo "trasforma" i due array in altrettante stringhe che poi vengono confrontate con 'eq'. Il separatore interposto tra ciascun elemento dell'array dalla funzione join sono i caratteri "^G" che potrebbero portare a dei problemi se fossero presenti tra gli elementi dei vostri array. Diamo un'occhiata a questi due array:

my @array1 = ('ciao^G', 'ola', 'hello', 'bueno');
my @array2 = ('ciao', '^Gola', 'hello', 'bueno');

all'apparenza sembrerebbero diversi, ma per il motivo detto sopra il nostro comparatore cade nel tranello e crea due stringhe così:

$stringa1 = 'ciao^G^Gola^Ghello^Gbueno';
$stringa2 = 'ciao^G^Gola^Ghello^Gbueno';

per cui quando va a confrontarle, per lui sono uguali e ci dice che gli array hanno gli stessi elementi! Per aggirare questo problema è possibile ridefinire il separatore andando a passare al costruttore dell'oggetto (quindi mentre si sta instanziando l'oggetto stesso) il parametro 'Sep':

my $comp = Array::Compare->new(Sep => '|');

oppure possiamo anche cambiare il separatore su un oggetto che abbiamo precedentemente creato:

$comp->Sep('|');

È ovvio che conviene scegliere un separatore che non compare nei nostri dati; se non si è certi se si ha o meno quel separatore nell'array si potrebbe pensare di utilizzare la funzione any contenuta nel package List::Moreutils in questo modo:

print "cambia separatore!" if any { m/mioseparatore/ } @arr1;

in maniera tale da fare un controllo al volo ed esser certi di non cadere in tranelli subdoli.

Possiamo anche controllare se gli spazi devono esser significativi per il nostro confronto, ricorrendo al metodo WhiteSpace(). Di default tutti gli spazi sono significativi, in alternativa si può far in modo che più spazi consecutivi vengano convertiti in un solo spazio passando al costruttore il seguente parametro:

my $comp = Array::Compare->new(WhiteSpace => 0);

oppure come al solito:

$comp->WhiteSpace(0);

La stessa identica cosa può esser fatta per rendere il confronto case sensitive o meno passando al costruttore l'opzione "Case":

my $comp = Array::Compare->new(Case => 0);

l'altro modo non ve lo scrivo tanto l'avete già capito... Fino a qui è un modulino carino che fa qualche cosina che alla fin fine ci saremmo potuti scrivere in qualche riga anche noi, vero? Questo modulo però ci permette di fare qualcosa di più potente con poco sforzo, tipo confrontare due array ed avere una lista degli indici che differiscono. Questo tipo di confronto è detto "confronto completo" ed in contesto scalare restituisce la lunghezza della lista di elementi che differiscono. Vediamo un piccolo esempio:

  
my @verdura_da_comprare = qw( zucchine pomodori asparagi insalata );
my @verdura_comprata = qw( zucchine melanzane peperoni insalata );

my $comp = Array::Compare->new(DefFull => 1);

my @lista_sbadato = $comp->compare(\@verdura_da_comprare, \@verdura_comprata);

print "Ho comprato $verdura_comprata[$_] anziche' $verdura_da_comprare[$_]\n" for (@lista_sbadato);

In questo frammento di codice andiamo a confrontare due array che contengono ciò che volevamo comprare al banco di frutta e verdura e ciò che invece abbiamo poi comprato perché ci siamo dimenticati la lista della spesa a casa. In @lista_sbadato abbiamo la lista degli indici i cui relativi elementi differiscono nel confronto e la utilizziamo comodamente per stampare gli elementi incriminati dei due array.

Dato che il modulo rispetta a pieno la filosofia Perl, c'è anche un altro modo per fare questa cosa, chiamando direttamente la funzione "full_compare":

$comp->full_compare(\@arr1, \@arr2);

per simmetria si può invocare anche il confronto semplice in questo modo:

$comp->simple_compare(\@arr1, \@arr2);

Abbiamo un po' di verdura in pentola, siamo partiti dal semplice confronto e ora abbiamo già un bel po' di opzioni per poter estrarre al volo molte informazioni sui nostri array. Andiamo avanti! Avete mai pensato di confrontare il podio delle gare di motociclismo? Quello della seconda fila si sta facendo una risata perché dice: "Cosa ti vuoi confrontare... vince sempre Valentino!". Beh, l'obiezione è buona ma noi partendo dal presupposto che nelle due gare in esame abbia vinto sempre Valentino, vogliamo confrontare i secondi e terzi posti. Possiamo indicare al nostro modulo di fregarsene del contenuto del primo elemento di ciascun array e andare a vedere se gli altri due sono uguali:

my %skip = (1 => 1); #il primo è sempre lo stesso

my @mugello = ('valentino','max','loris');
my @brno = ('valentino','loris','max');

Ora confrontiamo questi due podi del motomondiale:

my $podio = Array::Compare->new(Skip => \%skip);
$podio->compare(\@mugello, \@brno);
Ovviamente ci restituirà falso (in contesto booleano) perché si vede subito che non sono uguali ma possiamo notare che i piloti che stanno sul podio sono stati gli stessi!! Non vi stuzzica neanche un po' questa cosa? Beh lo sviluppatore del modulo c'aveva pensato: possiamo facilmente controllare se un array è la permutazione di un altro, cioè contiene gli stessi elementi ma in ordine diverso:
if ($podio->perm(\@mugello, \@brno) {
  print "Stesse persone sul podio\n";
} else {
  print "No. Sul podio ci sono andate persone diverse\n";
}

Per resettare il comparatore in maniera tale da non far saltare alcun elemento nell'array possiamo usare:

$podio->Skip({});

A dire il vero il discorso su quali elementi saltare nel confronto di due array può anche esser ribaltato vedendolo così: dico al comparatore di ignorare degli elementi che sicuramente so esser diversi, ed andare così a controllare se gli altri differiscono.

Cala il sipario siori e siore ma io ho ancora qualcosa da suggerirvi e cioè qualche modulo che potete andare a sbirciare e che può tornarvi utile quando avete a che fare con array e liste:

* List::Util <-- una selezione di utility per liste e array.

* List::Moreutils <-- tutto ciò che volevate in List::Util ma che il suo autore si รจ "ostinatamente" rifiutato di includere :D

* Tie::File <-- associa le linee di un file con gli elementi di un array, è un modulo comodissimo, un'occhiata è obbligatoria.

Pecche :-(

Se alla fine di questo articolo vi state domandando se sia possibile confrontare array multilivello, siete molto arguti ed avete scovato la falla di questo modulo. Purtroppo il confronto di strutture più complicate di un array non è effettuabile tramite questo modulo, vi segnalo qualcosa che potrà esservi utile per assolvere questo compito:

* Data::Compare - compara strutture dati Perl

Questo modulo non vi permette però tutte le opzioni con le quali potevate controllare il confronto su due array come con Array::Compare.