๐ con rango 19. Il suffisso TAC in #๐ $๐ค risulta essere preceduto da T quando occorre come
suffisso di #GATTAC โ ๐ท, il quale ha un rango 0 in ๐ท, e da A quando occorre come suffisso di T!GATACโ ๐ท, il quale ha un rango 3 in ๐ท. Applicando il Lemma 5.7 e attraverso lโordinamento lessicografico dei suffissi di ๐ = [0,1,3,1,4,2] riportato in Figura 5.2, si ha che il suffisso โ1 3 1 4 2โ successivo a โ0โ `e lessicograficamente minore al suffisso โ1 4 2โ successivo a โ3โ, quindi T precede A nella BWT di ๐
'
che alla fine risulta: ATTTTTTCCGGGGAAA!$!AAATATAA.#GATTACAT!GATACAT!GATTAGATA$$ ACAT!GATACAT!GATTAGATA$$ ACAT!GATTAGATA$$ AGATA$$ T!GATACAT!GATTAGATA$$ T!GATTAGATA$$ 0 1 3 1 4 2 1 3 1 4 2 1 4 2 2 3 1 4 2 4 2
Figura 5.2: Suffissi del parser๐ = [0,1,3,1,4,2] ordinati lessicograficamente.
Poichยดe `e necessario solo il dizionario ๐ท e le frequenze dei suoi elementi in ๐ per costruire e me- morizzare, attraverso il Lemma 5.6, le sottostringhe della BWT di ๐
'
composte da tutti i caratteri mappati agli elementi di ๐ per cui ๐ฝ ha prodotto un solo carattere distinto; `e richiesto uno spazio proporzionale alla lunghezza totale degli elementi di ๐ท. Sebbene poi, le sottostringhe dei caratteri mancanti nellโordine in cui appaiono nella BWT, costruite mediante il Lemma 5.7, possono occu- pare pi`u spazio della combinazione di ๐ท e ๐; man mano che vengono generate possono essere con- catenate alle sequenze del passaggio precedente, utilizzando quindi uno workspace proporzionale alla somma della lunghezza di ๐ e gli elementi di ๐ท.Supponendo quindi di poter riconoscere rapidamente le stringhe in ๐ธ, possiamo calcolare rapi- damente ๐ท e ๐ con una scansione su ๐ . Attraverso ๐ท e ๐, con i Lemmi 5.6 e 5.7, possiamo calcolare la BWT di ๐
'
= ๐ $ ordinando i suffissi degli elementi di ๐ท e i suffissi di ๐. In quanto ad oggi esisto- no algoritmi in grado di eseguire lโordinamento in tempo e spazio lineare quando si lavora nella memoria interna, vale il risultato teorico espresso nel Teorema 5.1.5.2
Implementazione del Prefix-Free Parsing
Fornita la dimensione ๐ค della finestra ๐ e selezionato un numero primo ๐, lโinsieme ๐ธ, descritto nella sezione precedente, viene definito come lโinsieme delle stringhe ๐ tali che |๐| = ๐ค e per cui ๐ป(๐) mod ๐ = 0, dove ๐ป `e la funzione hash denominata come Karp-Rabin fingerprint(17). I para-
๐ ๐
๐
๐
frase precedente frase successiva
Figura 5.3: Rappresentazione dello scorrimento della finestra๐ sulla stringa ๐ . La frase attuale ๐ terminata intercorre tra lโinizio della precedente occorrenza di ๐ โ ๐ธ e la fine di ๐ .
(17)La fingerprint `e una sequenza alfanumerica, o una stringa di bit, di lunghezza prefissata che identifica un certo file con le
caratteristiche intrinseche stesse del file. La (Karp-)Rabin fingerprint, proposta da Rabin [48] nel 1981, `e una funzione di rolling hash utilizzata come fingerprint e definita tramite polinomi su un campo finito. La Karp-Rabin fingerprint, ad esempio, viene utilizzata per lโorganizzazione dei filesystem oppure per il pattern-matching.
CAPITOLO 5. Prefix-Free Parsing 5.2. Implementazione del Prefix-Free Parsing metri ๐ค e ๐ influenzano la dimensione del dizionario di frasi distinte e il numero di frasi nel parser. Man mano che ๐ scorre sulla stringa ๐ (vedi Figura 5.3) la frase attuale, qui indicata con ๐, corri- sponde alla sottostringa che intercorre tra lโinizio della precedente occorrenza di una stringa ๐ โ ๐ธ e la fine della stringa di ๐ . I risultati dellโapplicazione di ๐ป sulla sottostringa coperta da ๐ (๐ป(๐ )) e sullโintera frase attuale che `e appena stata processata (๐ป(๐)) vengono memorizzati. Ogni volta che risulta ๐ป(๐ ) = 0 mod ๐, la frase attuale termina e la frase successiva inizia dallโinizio della finestra ๐ . Inoltre, si antepone un carattere null (che pu`o essere univoco senza la distinzione fatta nella teoria della sezione precedente) alla prima frase (precedentemente indicato con โ#โ) e si appendono ๐ค copie del carattere null allโultima frase (ossia la precedente sequenza $๐ค).
Lโalgoritmo tiene traccia, sia dei risultati della funzione hash calcolati sulle frasi contenute nel dizionario ๐ท attraverso lโinsieme ๐ท
'
, che della loro frequenza. Pi`u precisamente, quando la frase attuale ๐ termina, il risultato della funzione hash ๐ป(๐) viene appeso alla lista ๐, e viene verificato se ๐ป(๐) `e presente nel dizionario ๐ท'
. Se ๐ป(๐) โ ๐ท'
allora ๐ viene aggiunta a ๐ท e la sua frequenza impostata a 1; se invece ๐ป(๐) = ๐ป(๐'
) โ ๐ท'
viene verifico che le due frasi siano effettivamente le stesse, ๐ = ๐'
โ ๐ท, e si incrementa la sua frequenza.Al termine dello scorrimento della finestra ๐ sullโintera stringa ๐ e della funzione di parsing, sono stati generati il dizionario ๐ท e il parser ๐ = ๐1, ๐2, โฆ , ๐โ, dove ogni frase ๐๐, con 1 โค ๐ โค โ, `e
rappresentata dal proprio risultato della funzione hash. Il passaggio successivo consiste nellโordi- namento lessicografico di ๐ท e nella sostituzione di ciascuna frase ๐๐con il rango lessicografico di ๐๐
in ๐ท. Cos`ฤฑ facendo si ottiene il parser come una sequenza di interi di 4 byte ciascuno. La procedura sviluppata da Boucher et al. scrive il dizionario sul disco frase dopo frase nellโordine lessicografico e utilizzando un terminatore alla fine di ciascuna frase, e memorizza, su un file separato, la frequen- za di ciascuna frase con un intero di 4 byte. Lโutilizzo di 4 byte per ogni intero memorizzano non garantisce la migliore compressione possibile del file, ma permette di processare pi`u rapidamente i valori nei passaggi successivi. Infine, per elaborare gli elementi di ๐ che sono anche elementi di ๐ท, viene memorizzato un array ๐ด di lunghezza โ = | ๐ | tale che, per 1 โค ๐ โค โ, ๐ด[๐] corrisponde al (๐ค + 1)-esimo carattere di ๐๐a partire dalla fine, ossia ๐ด[๐] = ๐๐[| ๐๐| โ (๐ค + 1)].
Successivamente viene calcolata la BWT del parser ๐ (in cui gli elementi sono interi da 4 byte) attraverso lโalgoritmo SACA-K [49], che tra gli algoritmi che operano in tempo lineare, `e quello che utilizza uno workspace minore. Invece di memorizzare la stringa BWT(๐) = ๐ต = ๐1, ๐2, โฆ , ๐โ, per
ogni frase ๐๐ contenuta nel dizionario ๐ท lessicograficamente ordinato, si memorizza la lista delle
posizioni degli elementi di ๐ต in cui appare ๐๐; tale lista viene anche denominata inverted-list della
frase ๐๐(vedere Figura 5.4(a)). S osservi che, la lunghezza della inverted-list di una frase corrisponde
alla sua frequenza, ed `e possibile quindi memorizzare la concatenazione delle inverted-list per un totale di 4โ byte. Considerata ๐
'
๐la frase che precede ๐๐in ๐, in questo passaggio vengono permutatii valori di ๐ด cos`ฤฑ che da adesso ๐ด[๐] = ๐
'
๐[| ๐'
๐| โ (๐ค + 1)] (vedere Figura 5.4(b)).Lโultima fase dellโalgoritmo calcola la BWT di ๐ . Anche in questo caso, la pratica si discosta un poโ del Prefix-Free Parsing: invece di ordinare lessicograficamente i suffissi ๐ง di ๐ท per cui | ๐ง | โฅ ๐ค ven- gono ordinati tutti i suffissi ๐ง escludendo successivamente quelli con lunghezza inferiore o uguale a ๐ค. Lโalgoritmo utilizzato da Boucher et al. nellโimplementazione `e gSACAK [43], che calcola il SA e lโarray LCP dellโinsieme delle frasi contenute nel dizionario ๐ท. Procedendo poi come descritto nella sezione precedente, se durante la scansione dellโinsieme ordinato ๐ si incontra ๐ง