TP 9

DUT Informatique 1 – 2014/2015
Programmation Python
Travaux Dirigés n o9
Listes, chaînes, dictionnaires : performances (suite)
Au menu : La Transformée de Burrows-Wheeler.
Dans ce TP nous allons mettre en œuvre les méthodes vues précédemment (histogramme, rotation) sur un algorithme fameux : la transformée de Burrows-Wheeler (BWT). Cet algo intervient, entre autres, dans plusieurs méthodes de compression
de données dont bzip2 (Linux), 7zip (Windows).
L’idée de départ est la suivante : sans rentrer dans les détails, on peut montrer qu’un excellent moyen de préparer le terrain
pour les algorithmes de compression (tels que l’algorithme de Huffman) est de travailler sur une source (un texte, par
exemple) triée.
Or, si il est très facile de trier un texte, il est malheureusement impossible d’effectuer le traitement inverse (retrouver un
texte à partir de la suite de ses lettre triées dans l’ordre alphabétique).
C’est là qu’intervient la BWT : elle permet de construire une version "presque" triée du texte tout en étant réversible (on
peut retrouver le texte initial). On optimisera ainsi les performances des algorithmes de compression, tout assurant que l’on
pourra décoder les textes compressés.
Cet algorithme utilise les matrices de rotation vues au TP précédent.
x Exercice 1. la transformée directe : BWT
Dans l’Exercice 2 du TP08, on a vu comment construire de manière très efficace la matrice des rotations d’un
texte sous la forme d’une liste de chaînes. C’est la première étape de l’algorithme BWT. La matrice ainsi
formée contient, en première ligne, le texte source initial.
La seconde étape consiste à trier cette matrice par ligne. Dans cette nouvelle matrice, le texte original (sans
rotation) n’est plus en ligne 0 mais quelque part au milieu (cf. exemple ci-dessous)
La nouvelle matrice contient, en première colonne, la version de source triée lexicographiquement. Ce n’est
pas celle là qui nous intéresse mais ses voisines immédiates : les seconde et dernière colonnes.
En observant celles-ci on remarque en général que, si elle ne sont pas réellement triées, elles présentent de
longues séries de symboles identiques. Ce phénomène provient du fait qu’un texte structuré présente de
nombreux motifs (suites de lettres identiques).
C’est le cas dans l’exemple ci-dessous : tous les ’m’ sont suivis de ’a’, eux-mêmes presque toujours suivis
de ’n’, tous les ’e’ sont suivis de ’*’, eux-mêmes presque toujours suivis de ’m’... L’algorithme a donc
tendance à regrouper les symboles en paquets sur les première et dernière colonnes. Sur des textes plus long
l’effet est beaucoup plus flagrant.
Exemple : pour txt="ma*maman*mange*une*mangue*" on obtient la
0 *ma*maman*mange*une*mangue
1 *maman*mange*une*mangue*ma
2 *mange*une*mangue*ma*maman
3 *mangue*ma*maman*mange*une
4 *une*mangue*ma*maman*mange
5 a*maman*mange*une*mangue*m
col.no 24
6 aman*mange*une*mangue*ma*m
7 an*mange*une*mangue*ma*mam
8 ange*une*mangue*ma*maman*m
9 angue*ma*maman*mange*une*m
col.no 0
10 e*ma*maman*mange*une*mangu
11 e*mangue*ma*maman*mange*un
12 e*une*mangue*ma*maman*mang
13 ge*une*mangue*ma*maman*man
col.no 1
14 gue*ma*maman*mange*une*man
15 ma*maman*mange*une*mangue* ⇐txt
16 maman*mange*une*mangue*ma*
17 man*mange*une*mangue*ma*ma
18 mange*une*mangue*ma*maman*
19 mangue*ma*maman*mange*une*
20 n*mange*une*mangue*ma*mama
21 ne*mangue*ma*maman*mange*u
22 nge*une*mangue*ma*maman*ma
23 ngue*ma*maman*mange*une*ma
24 ue*ma*maman*mange*une*mang
1
matrice de rotation triée :
: eaneemmmmmungnn**a**auaag*
: *****aaaaaeeeggmmmmmnnnnuu (triée)
: mmmmu*mnnn***euaaaaa*eggen
C’est en général la dernière colonne que l’on choisit comme code de sortie de l’algorithme. La seule autre
information nécessaire au décodage (cf. Exercice 2) est le no de ligne où se trouve le texte initial (line=15
dans l’exemple précédent).
☞ A partir de la fonction rotations5(text) de l’Exercice 2 du TP08, écrire une fonction BWT(text) qui construit
la matrice de rotation, la trie, en extrait l’indice line où se trouve le texte et le code (dernière colonne) sous
la forme d’une chaîne de caractère, et finalement renvoie ces deux informations.
x Exercice 2. la transformée inverse : iBWT
C’est l’opération permettant, à partir des informations line et code précédentes, la reconstruction de la source
initiale text.
Principe :
Il est a priori très simple. On dispose d’un texte (code) dont on sait qu’il correspond à la dernière colonne
Cn−1 d’une matrice de rotation triée (n est la longueur du code). On va donc essayer de reconstituer cette
matrice. L’information supplémentaire line nous dira à quelle ligne on pourra trouver le texte original.
En réalité on dispose de deux colonnes de notre matrice : il suffit en effet de trier la chaîne code pour obtenir
la première colonne C0 , qui est en fait la suivante de Cn−1 (il s’agit de rotations, ne l’oublions pas).
1 Version 1 : très simple, mais affreusement inefficace.
On initialise la matrice des rotations triée Pt comme une matrice à une colonne : C0
Ensuite, en concaténant les lignes de Cn−1 au début des lignes de Pt , puis en triant Pt par ligne, on
forme les deux premières colonnes de Pt . En procédant ainsi n − 1 fois, on aura reconstruit toutes les
colonnes de Pt .
Exemple : text="abraca" → code="caraab", line=1
C5 [0]
C5 [01]
C5 [012]
C5 [0123]
C5 [01234]
[012345]
c+a
a+a
r+a
a+b
a+c
b+r
c+aa
r+ac
a+ab
a+br
a+ca
b+ra
c+aab
a+abr
r+aca
a+bra
a+caa
b+rac
c+aabr
a+abra
r+acaa
a+brac
a+caab
b+raca
c+aabra
a+abrac
r+acaab
a+braca
a+caabr
b+racaa
aabrac
abraca
acaabr
bracaa
caabra
racaab
tri
tri
tri
tri
tri
0
1=line
2
3
4
5
Il ne reste plus qu’à extraire la ligne line de Pt .
☞ Ecrire une fonction iBWT_grosbourrin(code,line) qui implémente cette version de l’algorithme et
renvoie le texte décodé.
☞ Quelle est sa complexité ?
2 Version 2 : très efficace, mais moins simple.
L’idée est de remarquer que, puisqu’on concatène toujours les éléments de la colonne Cn−1 en début
de ligne, et que ces lignes sont déjà triées, le prochain tri effectuera toujours les mêmes échanges de
lignes.
Il suffit alors de déterminer quelle est cette table d’échanges pour reconstruire directement le texte en
une seule passe de complexité O(n).
Notons :
- Te la table d’échange. C’est en fait une permutation sur la liste d’entiers [0,1,2,....,n-2,n-1]
- C0 la version triée de la source code. C’est la première colonne de la matrice.
Alors la valeur de Te[i] correspond à la position du symbole C0 [i] dans code.
Dans l’exemple précédent (text="abraca" → code="caraab", line=1) on aurait :
C0 :
code :
0
1
2
3
4
5
a0
c
a1
a0
a2
r
b
a1
c
a2
r
b
=⇒ Te=[1,3,4,5,0,2]
En pratique, la construction de Te n’est pas si simple car il faut distinguer les différentes occurrences
d’un même symbole, comme les ’a’ numérotés dans l’exemple ci-dessus.
2
Pour cela nous allons utiliser un histogramme modifié qui associera aux symboles non pas un nombre
d’occurrences mais une positions.
☞ Ecrire une fonction histoPos(code) , qui crée et retourne un dictionnaire Hdp dont les clefs sont les
symboles présents dans la source code et les valeurs sont leur position de première apparition dans
la source triée.
Pour cela, on commencera par construire un histogramme trié, comme au TP précédent (dictionnaire
converti en liste triée de paires [symb,occ]), puis on remplacera les occurrences par les positions de
première apparition, et pour finir on convertira celle liste de paires [symb,pos] en dictionnaire.
Exemple : pour la source code="caraab", le dictionnaire de retour serait Hdp={{’a’ :0,’b’ :3,’c’ :4,’r’ :5}}.
☞ Ecrire une fonction tableEchange(code,Hdp) , qui à partir du dictionnaire précédent, constuit et retourne la table d’échange Te, sous la forme d’une liste d’entiers.
Pour cela, pour chaque symbole lu s=code[i], on place l’indice i dans Te à la position donnée par
le dictionnaire (Hdp[s]) et, pour marquer le fait que l’on a vu un exemplaire du symbole s, on
incrémente sa position dans le dictionnaire.
Exemple : pour la source code="caraab", cet algorithme se déroule comme suit :
init
Te=[ , , , , , ]
Hdp={{’a’:0,’b’:3,’c’:4,’r’:5}}
i=0
code[0]=’c’
→ Hdp[’c’]=4 → Te[4]=0 → Hdp[’c’]=5→
Te=[ , , , ,0, ]
Hdp={{’a’:0,’b’:3,’c’:5,’r’:5}}
i=1
code[1]=’a’
→ Hdp[’a’]=0 → Te[0]=1 → Hdp[’a’]=1→
Te=[1, , , ,0, ]
Hdp={{’a’:1,’b’:3,’c’:(,’r’:5}}
i=2
code[2]=’r’
→ Hdp[’r’]=5 → Te[5]=2 → Hdp[’r’]=6→
Te=[1, , , ,0,2]
Hdp={{’a’:1,’b’:3,’c’:5,’r’:6}}
i=3
code[3]=’a’
→ Hdp[’a’]=1 → Te[1]=3 → Hdp[’a’]=2→
Te=[1,3, , ,0,2]
Hdp={{’a’:2,’b’:3,’c’:5,’r’:6}}
i=4
code[4]=’a’
→ Hdp[’a’]=2 → Te[2]=4 → Hdp[’a’]=3→
Te=[1,3,4, ,0,2]
Hdp={{’a’:3,’b’:3,’c’:5,’r’:6}}
i=5
code[5]=’b’
→ Hdp[’b’]=3 → Te[3]=5 → Hdp[’b’]=4→
Te=[1,3,4,5,0,2]
Hdp={{’a’:3,’b’:4,’c’:5,’r’:6}}
Une fois que l’on a la table d’échange, l’algorithme de décodage se déroule comme suit :
(a)
(b)
(c)
(d)
on initialise l’indice d’échange à k=line et l’indice de sortie à i=0
le caractère de sortie text[i] est donné par code[Te[k]]
l’indice d’échange suivant est donné par k=Te[k]
on continue ainsi (i=i+1) jusqu’à remplissage complet du texte (n fois)
☞ Ecrire finalement une fonction iBWT_petitponey(code,line) qui implémente cette version de l’algorithme et renvoie le texte décodé.
3 Performances :
Tester et comparer les deux méthodes pour diverses sources de plus en plus volumineuses.
3