Archive for the ‘Algoritmi’ Category

Suonare note in Pascal

Venerdì, Giugno 5th, 2009

In (Free) Pascal abbiamo una funzione che ci permette di emettere un suono dall’altoparlantino di sistema. In questo articolo ci baseremo su questa funzione. I computer moderni, sprovvisti di altoparlantino interno, a mio parere sono difettosi e devono essere sostituiti e rimborsati. Non è possibile giocare a un gioco stile Pacman senza sentire BIP ogni volta che mangiamo un puntino bianco.

Le funzioni

La funzione che attiva l’altoparlantino è sound(int). Il parametro che accetta è la frequenza del suono che intendiamo emettere.

Non esiste un parametro che indichi la durata del suono. Per questo motivo a un certo punto dovremo chiamare la funzione che disattiva l’altoparlante: nosound().

Tra la chiamata a sound() e la chiamata nosound() dovrà ovviamente trascorrere un lasso di tempo, durante il quale il suono verrà emesso. La funzione che fa trascorrere il tempo è delay(int), che accetta come parametro la durata dell’inattività in microsecondi.

La frequenza

E’ chiaro che, per suonare una nota con sound(), dobbiamo conoscerne la frequenza. Più questa è alta, più il suono emesso sarà alto. Ma ogni nota ha la sua frequenza particolare.

Il sistema all’interno del quale si trovano le note, con le loro particolari frequenze, è detto scala. Non esiste una sola scala: nel corso della storia se ne sono usate diverse. Quella che utilizziamo oggi è la scala temperata.

Una breve spiegazione su come conoscere la frequenza di ogni nota è a questo indirizzo:
http://ulisse.sissa.it/chiediAUlisse/domanda/2004/Ucau040418d001

Una spiegazione più approfondita si trova qui:
http://www.soloclassica.it/scalatemperata.htm

Codice

Ecco una funzione scritta in Pascal per suonare una nota:

procedure note(tone:integer; duration:integer);
begin
  sound(round(440 * (pow(sqrt(2, 12), tone))));
  delay(duration * unit_duration);
end;

Il primo parametro è il numero corrispondente alla nota. La nota numero 0 è il LA centrale, la 1 sarà il LA#, etc. Il secondo parametro è la durata in “unità”. Il programma non sa se l’unità sia un quarto, un sedicesimo e qualcos’altro, nè ci tiene a saperlo. E’ importante però che sia definita, a livello di programma, la costante unit_duration, che indica la durata dell’unità in microsecondi.

Un banalissimo esempio di utilizzo:

note(1, 1);
note(3, 1);
note(4, 2);
nosound;

LA SI DOOO

Implementare la potenza e la radice quadrata

Giovedì, Giugno 4th, 2009

Vediamo come implementare gli algoritmi dell’elevazione a potenza (pow) e della radice quadrata (sqrt) implementandoli in Pascal. Testati ovviamente con Free Pascal.

function pow(a:real; n:integer):real;
var
  i:integer;
  x:real;
begin
  x:=1;
  if n<>0 then
    for i:=1 to abs(n) do
      x:=x*a;
   if n<0 then
    x:=1/x;
  pow:=x;
end;

function sqrt(a:real;n:integer):real;
const
  precision_max=5;
var
   i,precision:integer;
   x:real;
begin
  x:=0;
  for precision:=0 downto - precision_max do
  begin
    for i:=0 to 9 do
      if pow(x + pow(10, precision)*i,n) > a then break;
    dec(i);
    x := x + pow(10, precision)*i;
  end;
  sqrt:=x;
end;

Algoritmi di ricerca in un array con PHP

Venerdì, Marzo 27th, 2009

Ho già pubblicato una lezione di base sugli array in PHP, dedicata in particolare ai web developers o agli smanettoni che non hanno solide basi di programmazione. Vediamo qui come implementare gli algoritmi di ricerca di un elemento all’interno di un vettore. Useremo PHP perchè è molto diffuso, ma in realtà, per varie ragioni, questi algoritmi risultano molto più utili se implementati in altri linguaggi.

Ricerca sequenziale semplice

Questo algoritmo è semplicissimo. Il procedimento consiste nello scorrere tutti gli elementi dell’array dal primo all’ultimo, fino a quando si trova quello desiderato o fino a quando si giunge alla fine.

function seq_search($needle, $haystack, $start=0, $end=null)
{
  if (is_null($end))
  $end = count($haystack);
  for ($i=$start; $i <= $end; $i++)
    if ($haystack[$i]==$needle)
      return $i;
  return null;
}

Nell’esempio, si effettua un ciclo in cui si scorrono tutti gli elementi. Ogni volta viene controllato l’elemento corrente, per sapere se corrisponde a quello cercato. Se viene trovato, si restituisce il suo indice, uscendo dalla funzione. Se non viene trovato, si termina il ciclo e si restituisce null.

Anche se di solito non viene fatto, ho scelto di implementare due parametri opzionali: $start e $end. Per default $start corrisponde a 0 (il primo elemento del vettore) e $end corrisponde all’indice dell’ultimo elemento (il numero degli elementi-1, visto che il conteggio inizia da 0). Detto questo, se sappiamo che l’elemento desiderato si trova, poniamo, dopo l’elemento 20 e prima dell’elemento 30, è possibile rendere la ricerca più breve specificando i .

Ricerca sequenziale su array ordinati

L’algoritmo sopra esposto non comprende nessun tipo di ottimizzazione. Se si cerca un elemento inesistente in un array di mille elementi, vengono eseguiti mille cicli. Se però sappiamo che l’array con il quale stiamo lavorando è ordinato (dall’elemento più basso a quello più alto) è possibile implementare qualche ottimizzazione.

function ord_search($needle, $haystack, $start=0, $end=null)
{
  if (is_null($end))
  $end = count($haystack);
  for ($i=$start; $haystack[$i] <= $needle && $i <= $end; $i++)
    if ($haystack[$i]==$needle)
      return $i;
  return null;
}

In questa variante, per ogni elemento si controlla non solo se esso è quello desiderato, ma anche se è minore. Nel caso sia maggiore, significa che l’elemento da noi desiderato non è presente nell’array.

Ricerca sequenziale con sentinella

L’algoritmo precedente presenta un problema: ad ogni ciclo vengono eseguiti due controlli. Questo significa che, se l’elemento che cerchiamo è maggiore di tutti quelli presenti nell’array, non abbiamo ottimizzato proprio nulla, anzi abbiamo aumentato il carico di lavoro.

E’ più efficiente, quindi, la ricerca con sentinella. Essa consiste nel porre l’elemento desiderato in coda all’array e poi, ad ogni ciclo, controllare semplicemente se l’elemento corrente è quello desiderato. Se lo è, controlleremo se è anche l’ultimo elemento; in tal caso è quello che abbiamo aggiunto noi, di conseguenza nell’array originale l’elemento non c’era. Altrimenti verrà trovato l’elemento originale.

function sen_search($needle, $haystack)
{
  $sentry = count($haystack);
  $haystack[$sentry] = $needle;
  for ($i=$start; true; $i++)
    if ($haystack[$i]==$needle)
      if ($i==$sentry)
        return null;
      else
        return $i;
}

Ricerca binaria (o dicotomica)

La ricerca binaria è in un certo senso abbastanza “umano”. Quando noi cerchiamo una parola sul dizionario non effettuiamo una ricerca sequenziale, ma dicotomica. Ossia: apriamo il dizionario a un certo punto, se la parola che cerchiamo si trova più avanti lo apriamo più avanti tenendo il vecchio segno, se siamo andati troppo avanti torniamo indietro…

Formalizzato, funziona così: ricordiamo due indici, che sono i due estremi della nostra ricerca: $low e $high.Inizialmente questi indici corrispondono al primo elemento dell’array e all’ultimo. Da questi calcoliamo la metà: $mid = ($low + $high) / 2. Controlliamo se l’elemento che corrisponde a $mid è quello che cerchiamo. Se lo è, la ricerca è terminata. Se invece $mid è minore dell’elemento che cerchiamo, sposteremo $low nel punto in cui si trova $mid; se invece è maggiore, sposteremo $high dove si trova $mid. A questo punto ricominceremo daccapo ricalcolando il nuovo valore di $mid. E così via finchè troveremo l’elemento che cerchiamo o, se manca, fino a quando $low sarà maggior di $high.

function bin_search($needle, $haystack, $low=0, $high=null)
{
// default last value
  if (is_null($high))
  $high = count($haystack) - 1;

  // search
  while ($low <= $high) {
    $mid = floor(($low + $high) / 2);
    if ($haystack[$mid]==$needle)
      // found; return current position
      return $mid;
    if ($haystack[$mid] < $needle)
      // search in higher values
      $low = $mid + 1;
    else
      // search in lower values
      $high = $mid - 1;
  }

  // not found
  return null;
}

Varianti

La ricerca sequenziale ha un’utile variante: la ricerca sequenziale inversa. Sebbene nella maggior parte dei casi sia indifferente utilizzare l’una o l’altra, in alcuni casi potremmo sapere che l’elemento desiderato si trova verso la fine dell’array, e non verso l’inizio. In questo caso la ricerca sequenziale inversa risulta migliore, in quanto probabilmente effettuerà meno cicli. Inoltre, se non si utilizzano i parametri $start e $end, la ricerca inversa comporta l’uso di una variabile in meno rispetto a quella ordinaria (perchè l’ultimo elemento sarà sempre 0, mentre in una ricerca normale varia a seconda della lunghezza dell’array e quindi deve essere calcolato).

Per quanto riguarda PHP…

PHP comprende una funzione nativa per ricercare un elemento in array: in_array(). Dunque, come si diceva all’inizio, PHP è stato utilizzato come linguaggio per gli esempi perchè è molto diffuso, non perchè questi algoritmi siano particolarmente utili in PHP.

Va comunque detto che in_array() effettua una ricerca sequenziale su array che si suppone non siano ordinati. Nel caso l’array con cui si sta lavorando sia ordinato e di grosse dimensioni, è probabile che una ricerca binaria implementata in PHP - per quanto non particolarmente performante - risulterà comunque un’utile ottimizzazione.

L’ideale sarebbe comunque implementare la ricerca binaria in C come modulo per PHP, soluzione che però ci è negata se non possiamo amministrare il server sul quale gli script verranno eseguiti.

La formula di Luhn

Mercoledì, Febbraio 18th, 2009

La formula di Luhn (detta anche algoritmo di Luhn o mod(10)) serve a verificare la validità di alcuni identificativi numerici, come i numeri delle carte di credito.

Il procedimento è il seguente:

  • Partiamo dall’ultima cifra, che per noi sarà la prima, e ci spostiamo verso sinistra. Moltiplichiamo per 2 tutte le cifre che si trovano in posizioni pari (2, 4, 6 etc). Se il risultato è composto da due cifre, le addizioniamo tra loro, ottenendo così un risultato composto da una cifra sola (14 diventa: 1+4=5)
  • Sommiamo tra loro tutte le cifre, sia che si trovino in posizione pari, sia che siano in posizione dispari.
  • Se il risultato ottenuto è divisibile per 10 (il risultato è un numero intero e non c’è resto), il numero è valido.

Si noti che questa formula non verifica di quanti numeri è composto il numero. Per controllare che sia un numero di carta di credito bisognerebbe almeno controllare che sia composto da 16 cifre. Inoltre la formula di Luhn non effettua alcun controllo riguardo la data di scadenza della carta nè altri dati importanti.

Deve essere quindi considerata come una formula generica atta a verificare che non ci siano stati errori di digitazione, nient’altro.

Ora vediamo le implementazioni in linguaggio PHP e in Gambas.

Formula di Luhn in Gambas

PUBLIC FUNCTION Luhn(StrInput AS String) AS Boolean

  DIM i AS Integer                ' digit index
  DIM CurrentDigit AS Integer     ' current digit being processed
  DIM isEven AS Boolean           ' if true, the digit must be processed
  DIM IntTotal AS Integer         ' total for the last computation

  isEven = FALSE
  i = Len(StrInput) - 1

  WHILE i >= 0
    CurrentDigit = CInt(CharAt(StrInput, i))
    IF isEven THEN
      CurrentDigit *= 2
      IF CurrentDigit > 9 THEN
        CurrentDigit -= 9
      ENDIF
    ENDIF
    IntTotal += CurrentDigit
    isEven = NOT isEven
    i -= 1
  WEND

  IF (IntTotal MOD 10) = 0 THEN
    RETURN TRUE
  ELSE
    RETURN FALSE
  ENDIF

END

PUBLIC FUNCTION IsCreditCard(StrInput AS String) AS Boolean
  IF Len(StrInput) <> 16 THEN
    RETURN FALSE
  ENDIF
  RETURN Luhn(StrInput)
END

Formula di Luhn in PHP

function mod_10($code)
{
  setType($code, 'string');
  $isEven = false;
  $total = 0;

  for ($i=strlen($code)-1; $i>=0; $i--) {
    $digit = substr($code, $i, 1);
    if ($isEven) {
      $digit *= 2;
      if ($digit>9)
        $digit -= 9;
    }
    $total += $digit;
    $isEven = !$isEven;
  }

  return (bool) !($total % 10);
}

function is_credit_card($code)
{
  return (strlen($code)==16) && mod_10($code);
}

Note

Il procedimento è lo stesso in entrambi i linguaggi.

Per motivi di performance, ho scritto un unico ciclo nel quale viene eseguita la moltiplicazione per due quando è il caso, per poi sommare il risultato (o il numero non moltiplicato) al totale.

Ho semplificato anche la somma delle due cifre che compongono il risultato della moltiplicazione. Ho potuto farlo perchè so che la cifra di sinistra è sempre 1. Se la togliessimo bisognerebbe sottrarre 10. Siccome dopo bisognerebbe aggiungere 1, è meglio fin dall’inizio sottrarre 9 al numero a due cifre.

Per controllare se un risultato è divisibile per 10 si utilizza l’operatore modulus (’MOD’ in Gambas e ‘%’ in PHP), che restituisce il resto di una divisione.

Generare numeri validi

Abbiamo visto che, perchè un numero sia valido, è sufficiente che il procedimento sopra spiegato generi un totale divisibile per 10. Si può quindi generare un ID valido (UNO: mai 0, mai 2), di n cifre, partendo da una qualsiasi combinazione di cifre lunga n-1.

Il procedimento per ottenerlo è molto semplice: aggiungiamo uno 0 a destra e applichiamo all’id la formula di Luhn. Se il risultato è divisibile per 10 abbiamo già ottenuto ciò che vogliamo. Altrimenti occorre sostituire lo 0 con la cifra corretta. Prendiamo la cifra più a destra del risultato ottenuto (per 64 è 4); la chiameremo d. Eseguiamo 10 - d. Il risultato è la cifra che dovrà sostituire lo 0 che abbiamo inserito.

Essendo la formula molto semplice, non è il caso di riscriverla in entrambi i linguaggi.

Generare id di Luhn validi in PHP

function luhn_generate($code)
{
  setType($code, 'string');
  $code .= '0';
  $isEven = false;
  $total = 0;

  for ($i=strlen($code)-1; $i>=0; $i--) {
  $digit = substr($code, $i, 1);
  if ($isEven) {
    $digit *= 2;
    if ($digit>9)
      $digit -= 9;
  }
  $total += $digit;
  $isEven = !$isEven;
  }

  if ($total % 10) {
    $digit = $total % 10;
    $digit = 10 - $digit;
    $code = substr($code, 0, strlen($code)-1) . $digit;
  }

  return $code;
}

Si potrebbero apportare un paio di ottimizzazioni ma, siccome non sono fondamentali e renderebbero il codice un po’ meno chiaro, lascio al lettore volenteroso il compito di trovarle. Il chè mi porta a dare un consiglio generale: se volete aggiungere modifiche che riducono la leggibilità documentate il tutto (come io non ho fatto in questi esempi) e apportatele solo dopo aver scritto un codice un po’ meno ottimizzato ma ben funzionante.

Dimostrazione

Vedi il mio demo per la verifica e la generazione di id validi. Utilizza le funzioni PHP esposte in questa pagina:

Buon divertimento e restate liberi.

Convertire unità di tempo

Lunedì, Febbraio 16th, 2009

In questo articolo si spiega come convertire, in PHP, un intervallo temporale da un’unità ad un’altra. Per esempio: come convertire 10 minuti in secondi. In effetti il caso più comune è appunto convertire in secondi, sia perchè il timestamp di Unix è espresso in secondi, sia perchè la durata dei cookie è espressa in secondi, sia perchè è un’unità di misura piccola comoda anche per calcolare quanto tempo è trascorso tra due determinati orari.

Per la conversione ho scritto una funzione chiamata time_convert(), che poi andremo a esaminare nel dettaglio:

function time_convert($interval, $new_unit='S')
{
// parse $interval
for ($i=0; $i<strlen($interval); $i++) {
if (ctype_alpha(substr($interval, $i, 1))) {
$old = substr($interval, 0, $i); // numeric part
$old = trim($old);
$old_unit = substr($interval, $i); // time unit
$old_unit = trim($old_unit);
$old_unit = strtoupper($old_unit);
break;
}
}

// format $new_unit
$new_unit = trim($new_unit);
$new_unit = strtoupper($new_unit);

// define time units and how many seconds they contain
$units = array (
'NNS' => 0.000000001, // nanoseconds
'MCS' => 0.000001, // microseconds
'MLS' => 0.001, // milliseconds
'M' => 60, // minutes
'H' => 3600, // hours
'D' => 86400, // days
'W' => 604800 // weeks
);

// convert $old to seconds, if needed
if ($old_unit!='S')
$old *= $units[$old_unit]);

// convert $old to $new_unit, if needed
if ($new_unit!='S')
$old /= $units[$new_unit];

return $old;
}

Spiegazione

La funzione accetta due parametri: intervallo di tempo da convertire (es: 1 ora), unità di tempo nella quale si vuole convertire l’intervallo (es: secondi). L’intervallo è una stringa che deve essere espressa così: “1H”. La parte che si occupa del parser è piuttosto flessibile, per cui accetta anche stringhe come “1h”, “1 H”, ” 1 H “. Sono anche ammessi i numeri decimali. E’ però obbligatorio specificare un numero e un’unità. Il secondo parametro, $new_unit, è semplicemente il codice dell’unità. Ad esempio “H” per le ore. Anche in questo caso la funzione è abbastanza flessibile, perciò è in grado di capire anche “h” e ” h “. Inoltre il secondo parametro è opzionale: per default vale “S”, secondi.

Il primo ciclo for esegue un parsing sul primo parametro. In pratica scorre la stringa carattere dopo carattere con la funzione substr(). Quando trova una lettera prende tutto ciò che si trova prima di quel punto e lo mette in una variabile $old, mentre tutto ciò che si trova dopo va in una variabile $old_unit.

Seguono due istruzioni che formattano il secondo parametro, eseguendo un trim() e trasformandolo in maiuscolo. Sono operazioni che si sono già svolte per il primo parametro e che aumentano, appunto, la flessibilità della funzione.

A questo punto viene definito l’array $units, che è l’array delle unità temporali. Si tratta di un array associativo: le chiavi sono il codice dell’unità (”H”), mentre i valori sono il numero di secondi contenuti in detta unità (3600). Come si vede, ho voluto comprendere anche i nanosecondi (miliardesimi di secondo), i microsecondi (milionesimi) e i millisecondi; oltre ovviamente ai secondi (che sono sottintesi, non presenti nell’array), minuti, ore, giorni, settimane. Qui mi sono fermato, perchè non tutti i mesi contengono la stessa quantità di secondi e lo stesso dicasi per gli anni (a causa della bisestilità e non solo). Aggiungere nuove unità è comunque estremamente semplice, perciò potrete inserire nell’array ciò che vi serve.

Dopo questi preliminari si passa alle conversioni. Prima di tutto, se l’intervallo di tempo è espresso in altra unità di misura, deve essere convertito in secondi. Farlo è semplice: deve essere moltiplicato per il numero di secondi contenuto nella sua unità di misura (3600 per le ore). Questo numero è rappresentato da $units[$old_unit].

Se è stato specificato il secondo parametro e non è uguale a “S”, occorre poi convertire il risultato nella nuova unità di misura. Questa volta l’operazione da eseguire è una divisione e il divisore è rappresentato da $units[$new_unit].

Infine il risultato viene restituito.

Limiti della funzione

La funzione non controlla che un’unità temporale sia stata effettivamente passata nel primo parametro. Se è stata specificata, e se è passato anche il secondo parametro, non controlla che le unità esistano realmente. Pur essendo flessibile lascia questi controlli al programmatore.

La funzione accetta di lavorare con numeri decimali estremamente precisi, ma una perdita di precisione è sempre possibile.

Infine non controlla se il risultato della conversione sono numeri troppo grandi o troppo piccoli, che superano i limiti di PHP. Non mi è sembrato utile in questo caso.

Esempio

Un cookie che rimane attivo per due settimane:

setcookie('answer', 42, time() + time_convert('2W'));

La distanza Levenshtein

Martedì, Gennaio 13th, 2009

La distanza Levenshtein tra due stringhe, dal nome di Vladimir Levenshtein, è detta anche distanza edit. Si tratta del numero minimo di modifiche elementari che deve essere compiuto su una delle due stringhe per renderla uguale all’altra. Per modifiche elementari si intende:

  • l’inserimento di un carattere;
  • la cancellazione di un carattere;
  • la sostituzione di un carattere con un altro carattere.

Questo algoritmo è presente in una funzione PHP chiamata appunto levenshtein(). Ecco un esempio di implementazione in Gambas 2.0:

PUBLIC FUNCTION Levenshtein(Str1 AS String, Str2 AS String) AS Integer

  DIM Index1 AS Integer ' for Str1
  DIM Index2 AS Integer ' for Str2
  DIM Grid AS NEW Integer[Len(Str1) + 1, Len(Str2) + 1]
  DIM Cost AS Integer ‘ edit cost

  ' initialize grid
  Grid[0, 0] = 0
  FOR Index1 = 1 TO Len(str1)
    Grid[Index1, 0] = Index1
  NEXT
  FOR Index2 = 1 TO Len(str2)
    Grid[0, Index2] = Index2
  NEXT

  ' compare all chars
  FOR Index1 = 1 TO Len(str1)
    FOR Index2 = 1 TO Len(str2)
      IF CharAt(Str1, Index1 - 1) = CharAt(Str2, Index2 - 1) THEN
        Cost = 0 ' no edit
      ELSE
        Cost = 1 ' edit
      ENDIF
      Grid[Index1, Index2] = Min3(Grid[Index1 - 1, Index2] + 1, Grid[Index1, Index2 - 1] + 1, Grid[Index1 - 1, Index2 - 1] + Cost)
    NEXT
  NEXT

  RETURN Grid[Len(Str1), Len(Str2)]

END

L’algoritmo Soundex

Giovedì, Ottobre 23rd, 2008

Soundex è un algoritmo fonetico pensato per la lingua inglese. Il suo scopo è fornire una rappresentazione approssimativa di come un termine inglese viene pronunciato. E’ stato pensato e provato soprattutto sui cognomi, perchè è stato inizialmente sviluppato per il primo censimento della popolazione USA; ad ogni modo funziona abbastanza bene con ogni tipo di parola e mi sembra funzionare discretamente anche con lo spagnolo. Soundex è ben lungi dall’essere perfetto, ma nella maggior parte dei casi è adatto allo scopo. Si ricordi però di utilizzarlo con le singole parole e non con intere frasi, avendo cura inoltre di eliminare la punteggiatura del caso.

L’algoritmo

Ecco come funziona. L’output è presentato in lettere maiuscole, in ogni caso ovviamente Soundex non è case-sensitive. La prima lettera dell’output corrisponde sempre alla prima lettera della parola presa in input. Successivamente, si continua a scorrere i caratteri che compongono la parola. Si ignorano le vocali. Le consonanti vengono tradotte in un numero, secondo il seguente schema:

B,F,P,V => 1
C,G,J,K,Q,S,X,Z => 2
D,T => 3
L =>
4
M,N => 5
R => 6

Come si vede, le consonanti dal suono simile corrispondono allo stesso numero, mentre le lettere H e W vengono ignorate.Nella “traduzione” però bisogna evitare di affiancare numeri simili. Se si trova una C seguita da una vocale e poi da una G, ad esempio, il numero 2 deve comparire una sola sola volta. La stringa restituita deve essere di 4 caratteri. Nel caso sia più breve, vengono aggiunti degli 0 fino ad arrivare a quattro caratteri. Nel caro la parola sia lunga, l’output viene comunque troncato.

L’algoritmo è estremamente semplice da implementare e molto diffuso.

Varianti

Il Reverse Soundex è identico all’algoritmo originale, eccetto che la prima lettera dell’output è l’ULTIMA lettera dell’input. Esiste un certo numero di altre varianti, ma per quanto ne so sono tutte pensate per la lingua inglese.

Molte implementazioni, come quella usata da MySQL, possono restituire più di 4 caratteri nel caso la parola contenga un maggior numero di consonanti con suoni differenti. 4 caratteri sembra essere comunque il numero minimo utilizzato in ogni implementazione.

Adattarlo alla lingua italiana richiederebbe un certo studio, accompagnato da test su migliaia di termini. Innanzitutto bisognerebbe impostare una diversa tabella di associazioni tra consonanti e numeri. Inoltre, bisognerebbe pensare se è il caso di ignorare le consonanti doppia (in inglese questo problema non esiste, ma noi dovremmo distinguere tra copia e coppia…). Inoltre le vocali dovrebbero influenzare il modo in cui le consonanti vicine vengono tradotte in numero e probabilmente anche gli accenti dovrebbero in qualche modo influire.

In Gambas, ragazzis!

Perdonate la battuta penosa, ultimamente ho riletto un po’ di vecchi Dylan Dog e visto un film dei fratelli Marx.

Riporto qui, a titolo di esempio, un’implementazione in Gambas di Soundex. Essa può restituire più di quattro caratteri ma non meno, è pertanto pienamente compatibile con MySQL. Aggiungere il limite di 4 caratteri è estremamente semplice anche per chi dovesse conoscere poco questo linguaggio libero basato su BASIC. Buon divertimento!

‘—–
‘ Returns the soundex representation of a string. Output may be longer than 4
‘ Requires: CharAt

‘ @strInput : String : Input

‘ @RETURN : String : Soundex string

PUBLIC FUNCTION Soundex(strInput AS String) AS String

  DIM strOutput AS String
  DIM Char AS String
  DIM PrevChar AS Integer ‘ previously examined char
  DIM LastChar AS Integer ‘ position of the string’s last char
  DIM IndexChar AS Integer
  DIM IndexCode AS Integer
  DIM CharCode AS NEW String[7]

  ’ initialize character codes
  CharCode[1] = “BPFV”
  CharCode[2] = “CSGJKQXZ”
  CharCode[3] = “DT”
  CharCode[4] = “L”
  CharCode[5] = “MN”
  CharCode[6] = “R”

  strInput = UCase(strInput)

  Char = CharAt(strInput, 0)
  ’ first char code (to avoid duplicates)
  FOR IndexCode = 1 TO 6
    IF InStr(CharCode[IndexCode], Char) THEN
      PrevChar = IndexCode
      BREAK
    ENDIF
  NEXT
  strOutput = Char

  LastChar = Len(strInput)
  FOR IndexChar = 1 TO LastChar ‘ first char is already done
    Char = CharAt(strInput, IndexChar)
    FOR IndexCode = 1 TO 6
      IF InStr(CharCode[IndexCode], Char) AND IndexCode <> PrevChar THEN
        strOutput &= IndexCode
        PrevChar = IndexCode
        BREAK
      ENDIF
    NEXT
  NEXT

   WHILE Len(strOutput) < 4
    strOutput &= “0″
  WEND

  RETURN strOutput

END