(english version here)

Se leggete GoTo 10 (o reddit o mezza blogosfera) avrete visto lo spell corrector in 20 righe di python di peter norvig.
Siccome sembra che il mio LoTY, almeno per ora, sia perl6, ho provato a riscriverlo.

L’idea è semplice: sio costruisce un database di parole esistenti e della loro frequenza, poi correct(”word”) verifica se “word” è nel database, o se c’è una variazione della parola, o una variazione della variazione.

Se c’è più di una variazione si restituisce quella più comune (”cinera” -> “cinema” piuttosto che “cinerea”).

Se non si trova niente, viene restituita la parola (volevi veramente dire “1337″).

La variazioni possibili sono 4,

  • inserimento: ‘cio’ -> ‘ciao’
  • sostituzione: ‘fipo’ -> ‘filo’
  • eliminazione: ’sessso’ -> ’sesso’
  • scambio: ‘maer’ -> ‘mare’

Il codice python usa list comprehension per iterare e manipola le stringhe come array per creare le variazioni. Ogni variazione viene immagazzinata all’interno di un oggetto Set, che è un oggetto iterabile che non ammette duplicati.

In perl6 non c’è una classe Set builtin (ma c’e n’è una come estensione in pugs), quindi invece di usare gli insiemi ho usato le Junction.
Una Junction è una superposizione di vari oggetti: any(1,2,3) crea un oggetto che multiplexa le operazioni su tutti gli oggetti contenuti, e che quindi ad esempio in un’operazione di confronto “if any(1,2,3) >2” farebbe eseguire il blocco if, perché uno degli elementi rispetta la condizione (alternativamente potremmo usare junction all, none o one).

Invece di trafficare con le stringhe come fa norvig in perl6 possiamo implementare le operazioni più facilmente con Str.subst(regexp,replacement), ed usare la forma gather/for per l’iterazione e l’estrazione dei risultati.

L’implementazione delle 4 operazioni sarà dunque:

# 'abc' -> 'ac'
sub deletion($w) {
    gather take $w.subst(/./,'’) for ^$w.chars
}

gather crea uno scope dove agirà take. Una chiamata a quet’ultima inserità l’argomento in un array implicito che verrà poi restituito da
gather.
il for statement modifer itera su di un oggetto iterabile, in questo caso si tratta di un range 0..numero_caratteri che viene creato tramite l’operatore ^ detto upto operator.

L’espressione regolare è semplice: at(N) è una zero-width assertion che ha successo all’ N-esimo elemento, che poi catturiamo con “.” e rimpiazziamo con una stringa vuota, cancellandolo.

Le sostituzioni sono altrettanto semplici:

# 'abc' -> 'adc'
sub substitution($w) {
    gather take $w.subst(/./,$_[1]) for ^$w.chars X @ALPHA
}

l’unica differenza rilveante è che usiamo il cross operatorX“. Il cross operator genera un prodotto cartesiano di due oggetti iterabili, quindi ‘a’..b’ X 1..2 genera ((’a',1),(’a',2),(’b',1),(’b',2)).
Il codice all’interno del for è: rimpiazza il carattere N-esimo con tutti quelli nell’alfabeto (@ALPHA=’a’..’z’).

L’inserimento fa la stessa cosa senza eliminare un carattere, ed effettua un’iterazione in più per aggiungere un carattere anche a fine stringa:

# ‘abc’ -> ‘abbc’
sub insertion($w) {
gather take $w.subst(//,$_[1]) for ^($w.chars+1) X @ALPHA
}

Infine, lo scambio usa una subst leggermente differente, dove il rimpiazzo è un blocco, che accederà agli elementi catturati:

# 'abc' -> 'acb'
sub transposition($w) {
    gather take $w.subst(/(.)(.)/,{”$1$0″})  for ^$w.chars
}

Semplicemente prende i caratteri N e N+1, e li scambia.

Un’implementazione semplice e pulita, imo. Peccato che in realtà non funzioni, perché pugs non permette l’interpolazione di variabili in una regexp, per ora.

Per l’implementazione funzionante aspettate la seconda parte o pensateci da voi :)