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



 

Versione stampabile.


Luca Dante Ortolani
Responsabile commerciale di una PMI nel campo ambientale e sviluppatore per passione. Da anni alla ricerca di soluzioni Open Source per le aziende è il creatore di IGSuite, Integrated Groupware Suite http://www.igsuite.org

Capita a volte che partendo da una semplice domanda, si scopre che la risposta poi tanto semplice non è. E' il caso di qualche giorno fa quando ho letto sul canale irc #nordest.pm una domanda di un utente che nel caso specifico aveva la necessità di scrivere del codice per confrontare due numeri di versione di un applicativo antivirus, per rilevare quale fosse il più recente ed aggiornato.

Come esempio di seguito vengono riportati alcuni possibili numeri di versione di questo tipo. A parte un fantomatico simbolo divisorio che passa da un punto '.' ad un underscore '_' sussiste il problema della diversa "profondità dei valori, ma soprattutto della possibilità che contengano degli zeri iniziali all'interno dei vari valori:

1.02.34
23_342_8
1.0.4
1.08
1.65.24.67

E' subito chiaro quindi, che non potremo fare un confronto diretto, ad esempio trattando i numeri di versione come delle normali stringhe.

In questo articolo proveremo a risolvere il problema sfruttando alcune metodologie molto ben illustrate in HOP, Higher Order Perl di Mark Jason Dominus e più precisamente: le "ricorsioni" e cioè la possibilità offerta dal Perl di poter richiamare in modo ricorsivo una sub; e gli "iteratori" una tecnica che sfruttando le closure del Perl offre la possibilità di creare delle iterazioni personalizzate.

Iniziamo definendo la nostra strategia. Possiamo innanzitutto affermare che a prescindere dalla "profondità" delle versioni, leggendo i numeri da sinistra a destra il valore maggiore si determina immediatamente prendendo i vari valori uno ad uno, e facendo un confronto "numerico".

Per semplicità diciamo che chiameremo i due numeri di versione del nostro fantomatico antivirus rispettivamente 'A' e 'B'. Quindi l'algoritmo che utilizzeremo sarà il seguente:

  • Step 1. Prendiamo a turno il valore più a sinistra della serie dei valori di 'A' che chiameremo 'a1'; se non lo troviamo possiamo affermare che 'A <= B';

  • Step 2. Prendiamo a turno il valore più a sinistra della serie dei valori di 'B' che chiameremo 'b1'; se non lo troviamo possiamo affermare che 'A > B';

  • Step 3. Confrontiamo a1 con b1. Se a1 è maggiore di b1 affermiamo che 'A > B', se a1 è minore di b1 affermiamo che 'A <= B' altrimenti torniamo allo Step 1.;


Iteriamo


Iniziamo con affrontare il problema di "prendere" il valore più a sinistra della serie dei valori sia di A che di B. In questo problema ci vengono in aiuto gli iteratori.

Possiamo attraverso il metodo HOP creare due iteratori che rispettivamente ad ogni chiamata ci restituiscono o meglio "iterano" sui valori da sinistra a destra di A e di B. Nel nostro esempio abbiamo ipotizzato che l'utente passi i valori di A e di B attraverso la riga di comando, valorizzando quindi $ARGV[0] e $ARGV[1].


$iterator_A = mk_iterator( $ARGV[0] );
$iterator_B = mk_iterator( $ARGV[1] );

sub mk_iterator
 {
  my @values = split /\D/, shift;
  return sub { shift @values };
 }


Nell'esempio abbiamo voluto dividere la serie dei valori con '\D' cioè qualsiasi valore non numerico. Questo ci darà la possibilità di poter confrontare sia valori di tipo '1.2.3' che '1_2_3' o addirittura '1-2-3'.

Il "trucco" su cui ruota la tecnica degli iteratori è nelle closure, e la possibilità che offrono nel mantenere il valore di variabili lessicali definite all'interno dello scope in cui sono state create, anche in chiamate operate in un diverso scope grazie al passaggio di 'code reference'.

Ricorriamo


Passiamo ora all'algoritmo che abbiamo definito dallo step 1 allo step 3. Come si legge nello step 3, qualora il confronto dei valori non ci dice chi dei due è maggiore o meglio qualora a1 sia uguale a b1 dobbiamo tornare allo step 1, per prelevare nuovi valori e rielaborare ricorsivamente gli step da 1 a 3. Ecco quindi la soluzione con il metodo HOP che sfrutta la ricorsione.


sub compare
 {
  ## Prendiamo i valori a sx di A e di B
  ## sfruttando l'iteratore creato in precedenza
  my $a1 = $iterator_A->();
  my $b1 = $iterator_B->();

  return 0 if $a1 < $b1 || ! defined $a1;
  return 1 if $a1 > $b1 || ! defined $b1;

  compare();
 }


La nostra ricorsione percorrerà ciclicamente i valori di A e di B risolvendosi in 4 casi:

  1. nel caso rileva che uno dei valori di A è maggiore del rispettivo valore di B e quindi potremo affermare che A > B;
  2. nel caso rileva che uno dei valori di A è minore del rispettivo valore di B e quindi potremo affermare che A <= B;
  3. nel caso in cui non trova più nessun valore di A e quindi potremo affermare che A <= B;
  4. nel caso in cui non trova più nessun valore di B e quindi potremo affermare che A > B;


Interessante è notare il motivo dell'uso di "defined". Infatti il nostro iteratore operando uno shift su un array, affronterà tre tipi di dati: 1..9; '0' e undef o meglio la fine dell'array. Siamo obbligati quindi ad utilizzare 'defined' proprio per intercettare la fine dell'array piuttosto che un valore numerico uguale a '0'.

Ecco quindi la soluzione proposta nella sua interezza:


#! /usr/bin/perl
use strict;
my $iterator_A = mk_iterator( $ARGV[0] );
my $iterator_B = mk_iterator( $ARGV[1] );

print compare() ? 'A > B' : 'A <= B';

sub compare
 {
  my $a1 = $iterator_A->();
  my $b1 = $iterator_B->();

  return 0 if $a1 < $b1 || ! defined $a1;
  return 1 if $a1 > $b1 || ! defined $b1;

  compare();
 }

sub mk_iterator
 {
  my @values = split /\D/, shift;
  return sub { shift @values };
 }


Bug!


Riesaminando i 4 casi in cui la ricorsione termina il lavoro e facendo qualche test, possiamo trovare che nel confronto di questi due valori '2.4.0' e '2.4' il nostro algoritmo fallisce nel caso (4). In effetti le due versioni sono uguali ma il nostro algoritmo considera quel '0' finale come un possibile nuovo rilascio.

Dobbiamo applicare quindi una piccola patch.

sostituire

  return 1 if $a1 > $b1 || ! defined $b1;
con
  return 1 if $a1 > $b1 || (! defined $b1 && $a1);


Ti è piaciuto questo articolo? Iscriviti al feed!











Devo ricordare i dati personali?






D:
Annunci Google