TP 7 : manipulation de textes 1 L`algorithme de

publicité
T.P. 7 : manipulation de textes
1
L’algorithme de recherche dans une chaı̂ne de caractères
On a dit en cours (chap. 7) que si on considère les lignes de codes suivantes :
devise="le lutin bleu est votre ami et il est important d’avoir des amis imaginaires"
print(devise.rfind("ami"))
La méthode rfind sur la chaı̂ne devise donne l’indice du début de la première occurrence d’une
sous-chaı̂ne, en partant de la droite r.
On veut ici écrire un algorithme qui ≪ fasse la même chose ≫ mais pour changer un peu, en
partant de la gauche. Le droite ou gauche n’est pas important : le but est de comprendre comment
on fait !
Cahier des charges : fabriquer une fonction recherche qui prend deux arguments partie et
tout qui sont deux chaı̂nes de caractères et qui va déterminer si la chaı̂ne partie apparaı̂t dans
tout et si oui à quel indice se trouve le début de la première occurrence de partie dans tout. La
fonction retournera cet indice dans ce cas et la valeur None sinon qui est une valeur spéciale de
Python sinon.
2
Cryptographie : complément au D.S. 2
Niveau ♯ : finir le 2) du D.S. 2 puis faire le 3
Niveau ♮ : à l’aide du corrigé du D.S. 2. faire le 3, pour le codage du César.
Ce 3) nécessite un peu de documentation supplémentaire (internet).
1
Plan plus détaillé pour le T.P. 7. paragraphe 2
2
Codage de César appliqué à un fichier texte
Choisir un petit texte, enregistré dans un fichier texte : texte_a_coder.txt
2.1
Comment lire ce texte pour obtenir une chaı̂ne de caractères en
python ?
Cf. cours chap. 7.
2.2
Choix de l’intervalle en utf8
Si on essaie de taper un texte en français, et si on fait un peu attention, on peut se limiter
aux caractères dont les numéros utf8 sont entre 10 et 255. Par exemple 10 correspond au retour
à la ligne et 32 au caractère d’espace. Attention toutefois, par exemple, u début j’avais tapé un
premier texte avec trois points de suspension, qui ont été remplacés par un caractère spécial pour
les trois points, qui lui a un code uft8 à quatre chiffres ! Donc écrire une petite fonction qui vérifie
où vivent les codes utf8 de votre texte, pour être sûr que votre encodage soit correct.
On peut donc adapter la fonction represente du D.S. en créant une liste de nombres entre 0
et 245, ou bien faire un autre choix plus intelligent... à vous de voir le vôtre !
2.3
Analyse en fréquence : le caractère espace devient très majoritaire
Ecrivez un test de fréquence pour ces caractères et vous verrez !
2.4
Quelles variantes possibles du codage de César pour résister à l’analyse en fréquence ?
a) Coder l’espace avec plusieurs numéros différents et de même pour le ’e’. Le texte ainsi fabriqué
devrait résister à la fonction decodeCesar. Bien sûr le nombre de chiffres de code devient
plus grand que le nombre de caractères de notre jeu de caractères.
Programmez un encodage de ce type.
b) Commencer par supprimer tous les espaces. Programmez une fonction Python qui prend en
entrée un fichier texte et fabrique un nouveau fichier qui contient le même texte sauf qu’on a
supprimé tous les espaces.
Cette opération semble irréversible. En fait, à l’aide d’un dictionnaire de mots, il serait
possible de refabriquer à peu près les espaces, mais c’est une autre histoire qu’Aliaume nous
racontera à l’occasion !
c) Changer de principe de codage : ne plus agir sur les codes des caractères mais sur les séquences
de chiffres plus longues dans le texte binaire obtenu en mettant les numéros binaires de
caractères à la suite. Comment faire ceci en Python ?
Rien qu’avec ces petites variantes, ce n’est plus si simple de décoder non ?
2
C.R. du T.P. 7
1
Algorithme de recherche dans une chaı̂ne de caractères
def recherche(partie,tout):
"""renvoie, si elle existe, la position de la première occurence
de partie dans tout et renvoie None sinon"""
for i in range(1+len(tout)-len(partie)): # le compteur i va parcourir
# tous les indices entre 0 et len(tout)-len(partie) (inclusivement)
# qui sont ceux susceptibles d’^
etre au début d’une occurrence
# de partie dans tout
j=0 # à partir de la position j on teste lettre par lettre
while j<len(partie) and partie[j]==tout[i+j]:
j+=1
# remarque le caractère paresseux du "and" est crucial
# si la condition j<len(partie) n’est pas vérifiée, il ne va pas
# regarder l’autre condition, ce qui évitera un "index out of range"
if j==len(partie):
return i
return None
# L’intér^
et du None est d’^
etre bien interprété par un autre programme
# qui utiliserait celui-ci
## Test
partie="bleu"
tout="le lutin bleu regarde le ciel bleu"
recherche(partie,tout)
##
Remarque On peut, en Python, faire mieux que tester l’égalité lettre par lettre : on peut tester
directement l’égalité des sous-listes avec les commandes d’extractions dans une liste (slicing) ce qui
enlèvera le while.
2
Codage de César appliqué à un fichier texte
Choix d’un petit texte, enregistré dans un fichier texte : texte-a-coder.txt dans le même
répertoire que mon script de T.P. 7.
Bon voici un petit texte que j’aimerais coder avec le codage de César. J’ai mis
quand m^
eme quelques accents qui font la richesse de notre belle langue à préserver,
voyons où cela va nous mener ? Ça va bien bien marcher !
2.1
Comment lire ce texte en Python : cf. chap. 7
from os import chdir
chdir("/Users/romain/Documents/MPSI/Informatique/TP7-algo-recherche-texte")
Fichier=open("texte-a-coder.txt","r",encoding="utf8")# ouverture en lecture seule
contenu=Fichier.read()# crée une cha^
ıne de caractères contenant
On dispose maintenant de la chaı̂ne de caractères contenu en mémoire.
3
2.2
Choix de l’intervalle en utf8
Si on essaie de taper un texte en français, et si on fait un peu attention, on peut se limiter aux
caractères dont les numéros utf8 sont entre 10 et 255. Par exemple 10 correspond au retour à la
ligne et 32 au caractère d’espace. C’est le cas dans le petit texte que j’ai tapé ci-dessus. Attention
toutefois, j’avais tapé trois points au début, qui ont été remplacé par le caractère . . . qui lui a un
code uft8 à quatre chiffres !
On peut donc adapter la fonction represente du D.S. en créant une liste de nombres entre 0
et 245.
def represente(texte):
"""prend en argument une cha^
ıne de car. et renvoie une liste
contenant les numero utf8 des car. successifs moins 10"""
l=[]
for i in range(len(texte)):
l.append(ord(texte[i])-10)
return l
Cependant l’analyse en fréquence va changer par rapport à ce qui était dit dans le problème.
2.3
Analyse en fréquence : le caractère espace devient très majoritaire
Avec le même code que dans le D.S.
def frequence(tc):
freq=[0]*255
for lettre in tc: # on parcourt les valeurs de tc
freq[lettre]+=1
on obtient bien sûr que le caractère numéro 22 réalise le max. des fréquences. Donc le décodage de
César devra en tenir compte.
2.4
Quelles variantes possibles du codage de César pour résister à l’analyse en fréquence ?
a) Coder l’espace avec plusieurs numéros différents et de même pour le ’e’. Le texte ainsi fabriqué
devrait résister à la fonction decodeCesar.
b) Comment supprimer tous les espaces :
● (M1) Si on lit bien le help(str) : on y trouve une méthode replace :
replace(...)
|
S.replace(old, new[, count]) -> str
|
|
Return a copy of S with all occurrences of substring
|
old replaced by new. If the optional argument count is
|
given, only the first count occurrences are replaced.
|
Avec cette méthode, la programmation de la fonction est immédiate :
def supprimer_espaces (texte):
""" Supprime tous les espaces d’un texte """
return texte.replace (" ", "")
● (M2) plus artisanal, mais aussi plus instructif sans utiliser la méthode replace
c) Comment transformer un texte en binaire :
● (M1) avec l’argument supplémentaire "rb" pour read binary de la fonction open :
Imaginons que j’ai un fichier phrase.txt formé d’une seule phrase : Une seule phrase,
accentuée à l’envie quand m^
eme. Voyons ce que fait le script suivant :
4
Fichierbin=open("phrase.txt","rb")# ouverture en lecture seule, en binaire
contenu_binaire=Fichierbin.read()
print(contenu_binaire)
>>>
b"Une seule phrase, accentu\xc3\xa9e \xc3\xa0 l’envie quand m\xc3\xaame.\n"
Cela peut paraı̂tre un peu décevant, cela n’a pas l’air d’une chaı̂ne binaire, mais c’est seulement un choix d’affichage de Python. Ce qui est important, c’est le b qui est devant, qui dit
que c’est une chaı̂ne binaire, cf. cours du chap. 7. § 5.4, autrement dit un objet de la classe
bytes. La convention de représentation de Python est la suivante :
● les octets de valeurs numériques comprises entre 32 et 127 (ascii de base) sont représentés
par le caractère correspondant du code ASCII,
● certains octets de valeur numérique inférieure à 32 sont aussi représentés de manière convertionnelle comme le caractère de fin de ligne (ASCII 10, convention Python, \n).
● les autres octets sont représentés par leur valeur hexadécimale, précédée de \x.
Ce qui compte : à partir d’une chaine de type bytes on peut extraire un par un le
codage binaire de chaque élément de la chaı̂ne.
Comment faire ? Si c est une chaı̂ne de type bytes, c[i] va renvoyer le numéro de i. Donc
à l’aide d’une boucle
l=[]
for oct in contenu_binaire:
l.append(oct)
print(l)
>>> (executing cell "parcours de la ch..." (line 102 of "script-TP7.py"))
[85, 110, 101, 32, 115, 101, 117, 108, 101, 32, 112, 104, 114, 97, 115, 101, 44, 32, 97, 99
Cependant, pour l’objectif du c) on voudrait que ces nombres soient écrit en binaire sur huit
bits : mais cela, on sait le faire !
def huitbit(n):
"""prend un entier n entre 0 et 255 et l’écrit sous la forme d’une chaine de caractère
binaire=bin(oct)[2:]# renvoie une cha^
ıne de caractères contenant
# l’écriture binaire mais elle n’est peut-^
etre pas assez longue
formate_en_huit=binaire.zfill(8)
return formate_en_huit
##
# programme principal
seq=""
for oct in contenu_binaire:
seq+=huitbit(oct)
print(seq)
On dispose enfin d’une chaı̂ne de caractères formée de 0 et de 1 où chaque octet (huit
caractères) est le numéro utf des caractères de notre chaı̂ne initiale
● (M2)Avec la méthode encode sur les chaı̂ne de caractères (cf. cours chap. 7).
C’est en gros la même chose qu’à la (M1) sauf qu’on ouvre le fichier texte pour fabriquer
une chaı̂ne str puis on encode la chaı̂ne en chaı̂ne bytes.
● (M3) Avec la commande ord qu’on connaı̂t. Une autre commande utile dont je n’ai pas
parlé est la commande format, distincte de la méthode format.
>>> format(32,’08b’)
’00100000’
On obtient alors immédiatement :
5
def texte_vers_binaire(texte):
seq=""
for x in texte :
seq+=format(ord(x),"08b")
return seq
d) Une fois qu’on a le texte binaire : on peut faire un codage par décalage sur une
longueur différente...
Par exemple, on choisit de faire de découper en morceaux de longueurs 12. (Il restera un
morceau à la fin qu’on complètera par des zéros).
Chaque morceau de longueur douze représente, en binaire, un chiffre entre 0 et 212 − 1 = 4095
On choisit un décalage à ajouter et on fait ce décalage modulo 4095.
6
Téléchargement