Infoforall

Python 27 : Structure de données

Cette activité propose de faire le point sur les structures rencontrées jusqu'à maintenant :

Les liens suivants vous permettront de retrouver les informations à connaitre pour manipuler ces données.

Et nous verrons qu'il existe une autre structure bien pratique dans certains cas de figure : les dictionnaires.

1 - String

Cette partie possède un objectif et un seul : vous faire manipuler les strings.

Vous avez déjà rencontré plusieurs fois les strings : c'est un objet itérable. Il est séquentiel car on peut accéder à son contenu à l'aide d'un index chiffré. Il est non mutable : on ne peut pas le modifier après création. On peut par contre créer un nouveau string qui porte le même nom et qui va écraser l'ancien.

Et surtout : un string ne peut contenir que des caractères (ou plutôt des octets qui représentent des caractères si vous avez suivi les passages sur l'encodage des caractères !).

Nous allons donc travailler sur un fichier texte qui contient un grand nombre de caractères et nous allons tenter de compter les mots et les lettres dans celui-ci.

Commencons par créer un fichier texte contenant uniquement quelques mots et caractères.

01° Créer dans votre espace de travail un dossier qui contiendra le fichier .txt suivant, que vous téléchargerez avec un clic droit + enregistrer la cible de lien sous. Vérifiez (avec Notepad++ par exemple qu'il est bien encodé en UFT-8.

Le fichier est disponible ici : FICHIER TEST UFT8 (avec BOM).

02° Créez, dans le même dossier, un fichier .py contenant le code suivant. Lire ensuite le code ligne par ligne pour tenter de vous souvenir comment il fonctionne.

#!/usr/bin/env python

# -*- coding: utf-8 -*-


objFichier = open("py-024-test-UTF8-avec-BOM.txt","r")

livre = objFichier.read()

objFichier.close()


print(livre)


nA = livre.count('a')

n = len(livre)


pA = 1 / n * nA


print("Le livre contient {} caractères.".format(n))

print("Nombre de a : {0:7}, soit {1:.2%} % des caractères".format(nA,pA))


input("pause")

La correction :

objFichier = open("py-024-test-UTF8-avec-BOM.txt","r")

livre = objFichier.read()

objFichier.close()

On crée un objet-fichier nommé objFichier à partir du fichier texte nommé "py-024-test-UTF8-avec-BOM.txt" qu'on ouvre en lecture seule (à l'aide du paramètre fixé à "r").

On place dans le string livre l'intégralité du texte contenu dans l'objet objFichier à l'aide de la méthode read(). Cette façon de faire à l'avantage d'être simple. Elle a le désavantage de placer la totalité du fichier en mémoire vive. Avec les très gros fichiers, cela peut poser problème. Pour lire le fichier ligne par ligne, voir l'activité sur les fichiers.

On referme l'objet-fichier puisqu'on a réussi normalement à en extraire le contenu.

print(livre)


nA = livre.count('a')

n = len(livre)


pA = 1 / n * nA

J'affiche le contenu pour la forme (avec print(livre)).

On place le nombre de caractère 'a' dans la variable nA.

On place le nombre de caractères au total dans la variable n avec la fonction native len().

On calcule le pourcentage de a et on place le résultat dans la variable pA (on a donc pA = 1 si 100% des caractères sont des 'a').

print("Le livre contient {} caractères.".format(n))

print("Nombre de a : {0:7}, soit {1:.2%} % des caractères".format(nA,pA))


input("pause")

On affiche à l'aide d'un print associé à la méthode format() les résultats.

Sur le premier format, on ne place que les accolades { } et le format remplace les accolades avec la variable n.

Sur le second format, on voit qu'on va remplacer les premières accolades par l'élément 0 présent dans le format. Il s'agit de nA. Le :7 indique qu'il faut écrire n en prenant 7 caractères.

La seconde accolade est remplie par le numéro 1 : on va donc chercher l'élément 1 du format. Il s'agit de pA. Le :.2% indique qu'on doit afficher un pourcentage avec deux chiffres après la virgule (on a noté .2 pas simplement 2). Il va donc multiplier pA par 100 et va arrondir à deux chiffres après la virgule.

03° Lancer le script pour voir s'il fonctionne. Ouvrir ensuite le fichier texte dans Notepad++ par exemple. Cela se passe-t-il comme prévu ?

Sans indiquer d'encodage lors de la lecture du fichier :

Voici un fichier

de test qui contient

quelques   caractères.

Le livre contient 66 caractères.

Nombre de a :       2, soit 3.03% % des caractères

pause

Attention : chaque espace compte bien pour un caractère.

Le bilan est assez lourd : non seulement le code du "é" n'est pas décodé correctement depuis fichier en UTF-8 (ici Python a décidé de lire le fichier avec une table Ascii étendu puisqu'il représente les 2 octets du "é" utf-8 en prenant cela pour deux caractères "è") mais en plus il y a des caractères devant le premier caractère "v" ...

A quoi sont dus ces caractères du début ? Au fait que le fichier est encodé en UTF-8 et que dans ce cas, on rajoute un caractère spécial sur 3 octets qui indique qu'il s'agit d'un fichier UTF.

04° Le fichier texte est en UFT-8. Le fichier Python est encodé en UFT-8. Mais Python 3.6 continue de base de décoder les fichiers texte avec une table 8 bits d'ASCII étendu. Indiquer maintenant à Python de décoder votre fichier texte avec la technique de l'UTF-8 à l'aide de l'instruction suivante encoding = 'utf-8' :

objFichier = open("py-024-test-UTF8-avec-BOM.txt","r", encoding = 'utf-8')

livre = objFichier.read()

objFichier.close()

Voici le résultat :

Voici un fichier

de test qui contient

quelques   caractères.

Le livre contient 63 caractères.

Nombre de a :       2, soit 3.17% % des caractères

pause

05° Allez voir la fiche sur les strings et modifiez votre programme pour qu'il transforme tout le texte en minuscules (lowercases en anglais). Demander ensuite au programme de compter les 'a' et les 'e', qu'ils soient minuscules ou majuscules.

Résultat en image :

voici un fichier

de test qui contient

quelques   caractères.

Le livre contient 63 caractères.

Nombre de a :       2, soit 3.17% % des caractères

Nombre de e :       7, soit 11.11% % des caractères

pause

J'ai obtenu ce résultat avec le code suivant :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


objFichier = open("py-024-test-UTF8-avec-BOM.txt","r", encoding = 'utf-8')

livre = objFichier.read()

livre = livre.lower() # rajout

objFichier.close()


print(livre)


nA = livre.count('a')

nE = livre.count('e') # rajout

n = len(livre)


pA = 1 / n * nA

pE = 1 / n * nE # rajout


print("Le livre contient {} caractères.".format(n))

print("Nombre de a : {0:7}, soit {1:.2%} % des caractères".format(nA,pA))

print("Nombre de e : {0:7}, soit {1:.2%} % des caractères".format(nE,pE)) # rajout


input("pause")

J'ai donc utiliser la méthode lower() sur le string livre. Cette méthode renvoie un nouveau string où tous les caractères sont en minuscules. LA METHODE NE TRANSFORME PAS LE STRING : les strings sont non mutables. J'ai donc placé le résultat en minuscules dans un string qui porte le même nom.

D'ailleurs, si livre est un grand string, il sera plus malin de faire ceci plutôt que de manipuler deux fois le string :

livre = (objFichier.read()).lower()

06° Je suis parti sur un site qui propose des oeuvres tombées dans le domaine public. J'ai téléchargé "The Dunwich Horror" de Lovecraft, enlevé les textes qui ne sont pas dans l'oeuvre. Téléchargez ce fichier sur votre poste et appliquez votre programme à ce long texte.

Le fichier est disponible ici : The Dunwich Horror.

Le site est disponible ici : www.gutenberg.org.

Pourquoi le prendre en anglais alors que les textes de Lovecraft sont dans le domaine public ? Pour des raisons de légalité ! L'oeuvre initiale est dans le domaine public, pas les traductions en français qui sont beaucoup plus récentes.

Le livre contient 101427 caractères.

Nombre de a :    6610, soit 6.52% % des caractères

Nombre de e :   10006, soit 9.87% % des caractères

pause

Par contre, s'il faut faire cela pour tous les caractères, c'est un peu lourd ... Voilà pourquoi nous allons utiliser une autre structure de données : la liste.

2 - Listes

La liste est un objet itérable : c'est un ensemble contenant d'autres variables et objets. Comme le string, il est séquentiel : on peut accéder à son contenu avec un index chiffré.

Deux différences majeures : on peut y mettre ce qu'on veut (pas que des caractères comme avec le string) et on peut le modifier après création.

Commençons avec l'une des méthodes des strings : split() qui renvoie une liste contenant l'ensemble des éléments contenus dans un string en prenant comme élément séparateur par défaut "espace". Si le string contient une phrase, on peut donc dire que le split() renvoie en quelques sortes la liste des mots. Pour être plus catégorique, il faudrait supprimer les ponctuations ... Mais le but ici n'est pas de réaliser un projet concret mais de voir rapidement l'intérêt des différentes structures de données.

#!/usr/bin/env python

# -*- coding: utf-8 -*-


objFichier = open("py-024-test-UTF8-avec-BOM.txt","r", encoding = 'utf-8')

livre = objFichier.read()

livre = livre.lower() # rajout

objFichier.close()


print(livre)


nA = livre.count('a')

nE = livre.count('e') # rajout

n = len(livre)


pA = 1 / n * nA

pE = 1 / n * nE # rajout


print("Le livre contient {} caractères.".format(n))

print("Nombre de a : {0:7}, soit {1:.2%} % des caractères".format(nA,pA))

print("Nombre de e : {0:7}, soit {1:.2%} % des caractères".format(nE,pE)) # rajout


input("pause")


# nouvelle partie du code

mots = livre.split()

nMots = len(mots)

print(mots)

print("La liste contient ", nMots, " mots")


input("pause")

07° Utiliser le code précédent sur le premier petit fichier. Ne l'utilisez pas sur le second (le livre entier), le print() final de l'ensemble de la liste de mots risquerait d'être un peu trop dur à afficher.

voici un fichier

de test qui contient

quelques   caractères.

Le livre contient 63 caractères.

Nombre de a :       2, soit 3.17% % des caractères

Nombre de e :       7, soit 11.11% % des caractères

pause

['\ufeffvoici', 'un', 'fichier', 'de', 'test', 'qui', 'contient', 'quelques', 'caractères.']

La liste contient 9 mots

pause

On a bien 9 mots mais vous pouvez constater que certains "mots" contiennent aussi la ponctuation. C'est donc très perfectible.

On retiendra également qu'une liste est définie comme un ensemble d'éléments séparés par des virgules et délimitée par des crochets [...].

08° Que contient le premier mot ? Rien de choquant ? Sachant que /u veut dire qu'on donne à la suite les 4 octets (en hexadécimale= du nombre Unicode du caractère, rechercher la signification Unicode du caractère devant v.

...CORRECTION...

Les 4 caractères après \u sont : FEFF.

F vaut 15 en base 10 et E vaut 14.

L'octet de poids faible (à droite) code 1, le suivant 16, le suivant 162(256) et le dernier 163(4096).

Le numéro UNICODE est donc : 15*4096 + 14*256 + 15*16 + 15*1 = 65279

...CORRECTION...

Le caractère UNICODE (FEFF)16 ou 6527910 est trouvable facilement via Internet.

Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF)

Il s'agit du fameux BOM dont j'avais parlé au début.

Invisible à l'affichage, il n'en reste pas moins présent au début du fichier et ses octets l'ont dévoilé !

09° A l'aide de Notepad++, modifier l'encodage des deux fichiers vers UFT-8 sans BOM et recommencer la manipulation sur le premier fichier.

Vous devriez obtenir ceci (à savoir la disparition des caractères \ufeff devant le v) :

['voici', 'un', 'fichier', 'de', 'test', 'qui', 'contient', 'quelques', 'caractères.']

La liste contient 9 mots

pause

Mais nous allons aller plus loin : je voudrais créer un programme qui va automatiquement me créer une liste qui contiendra le nombre d'occurrence des lettres de a à z dans mon string livre transformé intégralement en minuscules.

Le tout sans taper la recherche de toutes les lettres à la main bien entendu !

Le premier élément de la liste (l'élément 0) devra contenir le nombre de caractère au total.

Le second élément devra contenir le nombre de a.

Le troisième élément devra contenir le nombre de b.

...

Pour cela, vous aller avoir besoin d'utiliser les fonctions chr(i) qui renvoie le caractère UNICODE associé au numéro i. Par exemple, chr(97) donne 'a'.

Il va falloir créer une liste et la remplir au fur et à mesure avec la méthode append() par exemple. Aller voir la fiche sur les LISTES.

Et bien entendu des boucles et éventuellement des fonctions.

Le programme ne devra plus afficher la liste des mots mais devra afficher la liste des occurrences et réaliser ensuite un affichage plus lisible.

10° A vous de jouer. Réalisez le programme, testez le sur le petit fichier puis sur le livre.

Voici mes résultats sur le fichier basique :

[62, 2, 0, 5, 1, 7, 1, 0, 1, 6, 0, 0, 1, 0, 3, 2, 0, 3, 3, 3, 5, 4, 1, 0, 0, 0, 0]
Lettre a présente      2 fois.
Lettre b présente      0 fois.
Lettre c présente      5 fois.
Lettre d présente      1 fois.
Lettre e présente      7 fois.
Lettre f présente      1 fois.
Lettre g présente      0 fois.
Lettre h présente      1 fois.
Lettre i présente      6 fois.
Lettre j présente      0 fois.
Lettre k présente      0 fois.
Lettre l présente      1 fois.
Lettre m présente      0 fois.
Lettre n présente      3 fois.
Lettre o présente      2 fois.
Lettre p présente      0 fois.
Lettre q présente      3 fois.
Lettre r présente      3 fois.
Lettre s présente      3 fois.
Lettre t présente      5 fois.
Lettre u présente      4 fois.
Lettre v présente      1 fois.
Lettre w présente      0 fois.
Lettre x présente      0 fois.
Lettre y présente      0 fois.
Lettre z présente      0 fois.
pause
					

Et pour The Dunwich Horror :

[101427, 6610, 1325, 2065, 3662, 10006, 1786, 1758, 5222, 5163, 55, 671, 3647, 1884, 5681, 5750, 1310, 77, 4665, 5026, 7264, 2296, 654, 1922, 88, 1538, 73]

Lettre a présente   6610 fois.

Lettre b présente   1325 fois.

Lettre c présente   2065 fois.

Lettre d présente   3662 fois.

Lettre e présente  10006 fois.

Lettre f présente   1786 fois.

Lettre g présente   1758 fois.

Lettre h présente   5222 fois.

Lettre i présente   5163 fois.

Lettre j présente     55 fois.

Lettre k présente    671 fois.

Lettre l présente   3647 fois.

Lettre m présente   1884 fois.

Lettre n présente   5681 fois.

Lettre o présente   5750 fois.

Lettre p présente   1310 fois.

Lettre q présente     77 fois.

Lettre r présente   4665 fois.

Lettre s présente   5026 fois.

Lettre t présente   7264 fois.

Lettre u présente   2296 fois.

Lettre v présente    654 fois.

Lettre w présente   1922 fois.

Lettre x présente     88 fois.

Lettre y présente   1538 fois.

Lettre z présente     73 fois.

pause

Et voici une proposition de correction :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


objFichier = open("py-024-lovecraft.txt","r", encoding = 'utf-8')

livre = (objFichier.read()).lower()

objFichier.close()


n = len(livre)


occ = []

occ.append(n)


for i in range(97,97+26,1) :

    occ.append(livre.count(chr(i)))


print(occ)


for i in range(1,1+26) :

    print("Lettre {0} présente {1:6} fois.".format(chr(i+96),occ[i]))


input("pause")

Je crée une liste nommée occ initialement vide : occ = [].

Je la remplis au fur et à mesure avec la méthode append(). L'élément 0 contient le nombre de caractère total. Puis l'élément 1, le nombre de 'a' ...

Par contre, on voit que la récupération des éléments n'est pas facile. Pour connaitre le nombre de 'd', il faut connaître le numéro de l'élément qui doit contenir le nombre correspondant à cette lettre. Pas diffcile sur le moment, mais pas facile ni clair non plus.

3 - Les tuples

Continuons avec la structure suivante : le tuple délimité par les parenthèses (...) et dont les éléments sont séparés par des virgules.

Si on devait résumer cette structure en quelques mots, nous pourrions dire que c'est une liste non mutable.

Comme les listes, les tuples peuvent contenir des éléments divers et variés (c'est une strucure itérable).

Comme les listes, les tuples sont séquentiels (on peut accéder aux éléments via un numéro).

Par contre, ils ne sont pas mutables et c'est une propriété intéressante lorsqu'on veut être certain de ne pas altérer les données.

11° Comparons les listes et tuples à l'aide des tests suivants. Répondre aux questions 2 et 4 et tenter de prévoir l'affichage pour les questions 1 et 3.

>>> x = [1,2,3]

>>> x

[1, 2, 3]

>>> type (x)

*** réponse 1 : donnez la réponse qui devrait s'afficher sur la console ***

>>> x[2] = 10

*** réponse 2 : possible ou non ? ***

>>> y = (1,2,3)

>>> y

(1, 2, 3)

>>> type (y)

*** réponse 3 : donnez la réponse qui devrait s'afficher sur la console ***

>>> y[2] = 10

*** réponse 4 : possible ou non ? ***

...CORRECTION...

1 : x est de type list car on l'a défini avec des crochets.

2 : il est donc possible de modifier directement l'élément 2 et d'obtenir alors liste [1, 2, 10].

3 : y est de type tuple car on l'a défini avec des parenthèses .

4 : on peut obtient donc une erreur car les tuples sont non mutables.

Nous allons utiliser les tuples comme élément à placer dans notre liste occ précédente : chaque élément de la liste devra être rempli par un simple tuple e contenant lui même trois élément :

Ceci sera valable pour les éléments occ[1] à occ[26]. Le premier élément occ[0] contiendra lui un tuple spécial :

Rappel : un tuple peut se définir ainsi : e = (element1, element2, element3).

12° A vous de jouer. Réalisez ce programme.

Un affichage possible :

[('py-024-lovecraft.txt', 101427), ('a', 6610, 0.0651700237609315), ('b', 1325, 0.013063582675224545), ('c', 2065, 0.02035947035799146), ('d', 3662, 0.036104784722016815), ('e', 10006, 0.09865223264022399), ('f', 1786, 0.017608723515434743), ('g', 1758, 0.017332662900411133), ('h', 5222, 0.05148530470190383), ('i', 5163, 0.050903605548818356), ('j', 55, 0.0005422619223678113), ('k', 671, 0.006615595452887298), ('l', 3647, 0.03595689510682559), ('m', 1884, 0.01857493566801739), ('n', 5681, 0.0560107269267552), ('o', 5750, 0.05669101915663482), ('p', 1310, 0.012915693060033323), ('q', 77, 0.0007591666913149358), ('r', 4665, 0.04599367032446981), ('s', 5026, 0.04955288039673854), ('t', 7264, 0.07161801098326875), ('u', 2296, 0.022636970431936267), ('v', 654, 0.006447987222337247), ('w', 1922, 0.01894958935983515), ('x', 88, 0.0008676190757884981), ('y', 1538, 0.015163615210939887), ('z', 73, 0.0007197294605972768)]

Lettre a présente 6610 fois.

Lettre b présente 1325 fois.

Lettre c présente 2065 fois.

Lettre d présente 3662 fois.

Lettre e présente 10006 fois.

Lettre f présente 1786 fois.

Lettre g présente 1758 fois.

Lettre h présente 5222 fois.

Lettre i présente 5163 fois.

Lettre j présente 55 fois.

Lettre k présente 671 fois.

Lettre l présente 3647 fois.

Lettre m présente 1884 fois.

Lettre n présente 5681 fois.

Lettre o présente 5750 fois.

Lettre p présente 1310 fois.

Lettre q présente 77 fois.

Lettre r présente 4665 fois.

Lettre s présente 5026 fois.

Lettre t présente 7264 fois.

Lettre u présente 2296 fois.

Lettre v présente 654 fois.

Lettre w présente 1922 fois.

Lettre x présente 88 fois.

Lettre y présente 1538 fois.

Lettre z présente 73 fois.

pause

La correction est un peu plus bas, mais tentez de le faire seul. Sinon, on ne rend pas compte de ce qu'on a réellement compris ou non.

Voici le code complet avant les explications :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


fichier = "py-024-lovecraft.txt"

objFichier = open(fichier,"r", encoding = 'utf-8')

livre = (objFichier.read()).lower()

objFichier.close()


n = len(livre)


occ = []

occ.append((fichier,n))


for i in range(97,97+26,1) :

    nc = livre.count(chr(i))

    e = (chr(i), nc, 1/n*nc)

    occ.append(e)


print(occ)


for i in range(1,1+26) :

    print("Lettre {0} présente {1:6} fois.".format(occ[i][0],occ[i][1]))


input("pause")

La lecture du livre se fait comme auparavant. La seule différence vient du fait que j'ai placé le nom du fichier dans le string fichier. De cette façon, la méthode open et la première case de la liste pourront contenir à coup sur le même nom.

Ensuite, on crée toujours une liste vide avec occ = [].

La différence vient juste après avec la création du premier élément de la liste avec append() qui vient rajouter le tuple suivant : (fichier,n). Les parenthèses indiquent bien de générer un tuple dont on donne les deux éléments : le string fichier et le nombre de caractères n.

occ = []

occ.append((fichier,n))

Si on avait demandé à voir le contenu de la liste à ce moment, voilà ce qu'on aurait pu afficher (j'ai rajouté les espaces pour que cela soit clair ) :

>>> occ

[ ( 'py-024-lovecraft.txt' , 101427 ) ]

On voit donc que occ est une liste à l'aide de l'indication 'crochets' : [ ].

On voit que le premier élément (occ[0] donc) est un tuple à l'aide de l'indication 'parenthèses' : ( , ).

Ce tuple contient lui-même deux éléments séparés par une virgule : un string valant 'py-024-lovecraft.txt' et un intéger valant 101427.

C'est ensuite qu'on lance la boucle qui va créer les tuples suivants et les rajouter dans la liste :

for i in range(97,97+26,1) :

    nc = livre.count(chr(i))

    e = (chr(i)<, nc, 1/n*nc)

    occ.append(e)

L'intéger i commence à 97 et va jusqu'à 97+26 car le boucle continue tant que i < (97+26+1).

On commence par trouver le nombre de caractères chr(i) dans livre à l'aide du count(). On place le résultat dans nc.

A la troisième ligne, on crée un tuple (voyez les parenthèses) nommé e avec le code e = (chr(i) , nc , 1/n*nc ) . Ce tuple contient :

Finalement, on rajoute ce tuple à la liste avec occ.append(e).

print(occ)


for i in range(1,1+26) :

    print("Lettre {0} présente {1:6} fois.".format(occ[i][0],occ[i][1]))

On affiche le contenu de la liste occ à l'écran pour vérification. Cela permet de vérifier le contenu du premier élément et de vérifier qu'on a bien gérer les caractères jusqu'à 'z".

Vient ensuite la restitution à l'écran. On va lire le contenu à l'aide d'une boucle for numérique. Cela va me permettre de vous expliquer la forme un peu étrange de l'affichage du contenu :

    print("Lettre {0} présente {1:6} fois.".format(occ[i][0],occ[i][1]))

Que contient occ[i][0] ? Quelques explications :

explication de occ[1][0]

Pour plus de clarté, nous aurions pu taper quelque chose du genre :

print(occ)


for i in range(1,1+26) :

    e = occ[i]

    print("Lettre {0} présente {1:6} fois.".format(e[0], e[1]))

Nous sommes donc parvenu à créer cette fameuse liste d'occurrence. Mais nous voulions la protéger contre les modifications malheureuses, nous pourrions finaliser l'ensemble avec ceci :

occ = tuple(occ)

Cette simple ligne va créer un tuple nommé occ à partir de l'ancienne liste. L'avantage ? On ne pourra pas modifier manuellement les éléments. Vous indiquez ainsi que cet ensemble est informatif et ne devrait pas être modifié.

Cela fonctionne dans l'autre sens d'ailleurs si vous voulez transformer un tuple en liste : x = list(x) transforme le tuple x en liste x.

Mais on pourrait également vouloir créer une liste d'occurrence de tous les caractères présents dans le livre. On peut le faire en manipulant les listes et les strings mais ce n'est pas le cas le plus adapté. Nous allons voir ici une dernière façon de structurer les données : les dictionnaires.

4 - Les dictionnaires

Alors qu'est-ce qu'un dictionnaire ?

Prenons l'exemple d'un dictionnaire au sens usuel du terme, le dico, le livre plein de mots et de définition. Je suis parti sur le site d'un éditeur bien connu de dictionnaire. Et je suis parti voir ce qu'on y disait de l'encodage. Voici la réponse :

dico

Et bien, un dictionnaire au sens Pythonique est assez proche de cela : c'est un objet itérable qui contient d'autres éléments.

Comme un dictionnaire 'physique', on n'accède pas au contenu d'un élément d'un dictionnaire 'Phytonique' à l'aide d'un nombre : si vous cherchez la définition du mot "encodage", vous ne recherchez pas la définition à l'aide du numéro du mot dans le dictionnaire. Vous le recherchez à l'aide du mot-clé "encodage", tout simplement.

dico

Voici deux façons de gérer notre problème de nombres de caractères dans un ouvrage : la première est celle d'une liste remplie de tuples par exemple.

Premier cas : la liste occ

occ = [ 101427, 6610, 1325, 2065 ]

Index numérotéContenu de l'élément
0101427
16610
21325
32065

Ca fonctionne bien mais il faut savoir que pour obtenir le nombre de 'a', il faut chercher occ[1]. Pas très intuitif.

>>> occ[1]

6610

Deuxième cas : le dictionnaire occ

occ = { 'total' : 101427, 'a' : 6610, 'b' : 1325, 'c' : 2065 }

13° Dans le shell(console) Python, tapez les instructions ci-dessous pour voir comment fonctionne un dictionnaire. Répondre ensuite aux questions :

  1. Le symbole délimitateur d'un dictionnaire est-il le crochet [...] ou l'accolade {...} ?
  2. L'accès au contenu se fait-il à l'aide d'un index chiffré ou à l'aide d'une clé quelconque ?
  3. Peut-on accéder au contenu d'un dictionnaire à l'aide d'un index chiffré ?

>>> occ = { 'total' : 101427, 'a' : 6610, 'b' : 1325, 'c' : 2065 }

>>> occ

{'total': 101427, 'a': 6610, 'b': 1325, 'c': 2065}

>>> occ['a']

6610

>>> occ['total']

101427

>>> occ[0]

KeyError: 0

La correction, même si c'est plutôt facile et visuel :

Un dictionnaire est défini à l'aide des accolades { ... }. En effet, les parenthèses définissent les tuples et les crochets définissent les listes.

Contrairement aux listes, tuples et strings ont ne peut pas accéder au contenu d'un dictionnaire en utilisant un index chiffré. Cela provoque même une erreur si aucune clé ne correspond au chiffre utilisé.

Par contre, on peut choisir n'importe quelle clé d'accès.

Comment ? Regardons la strucure d'un dictionnaire :

occ = { 'total' : 101427, 'a' : 6610, 'b' : 1325, 'c' : 2065 }

On remarque que chaque élément est séparé par des virgules comme dans les listes ou les tuples. Rien de nouveau de ce côté.

La nouveauté vient de la présence du double point ':' qui sépare deux parties distinctes de l'élément :

Avant le double point, on trouve la clé permettant d'accéder à ce contenu : il faut taper monDictionnaire[maCle] pour voir le contenu correspondant à cette clé.

Après le double point, on trouve la valeur qui sera fournie lorsqu'on fait appel à la clé.

CléValeur renvoyé par l'utilisation de la clé
'total'101427
'a'6610
'b'1325
'c'2065

Ca fonctionne beaucoup mieux : plus besoin de connaitre l'élément contenant ce qu'on cherche pourvu qu'on connaisse la clé de définition !

>>> occ['a']

6610

Vous avez déjà été revoir les fiches sur les strings, les tuples et les listes. Le but de cette fin d'activité n'est pas de vous présenter les dictionnaires en long et en large. Nous allons simplement voir les bases. Et si le besoin s'en fait sentir, vous pourrez toujours aller vous renseigner sur la fiche ou sur Internet.

Nous avons vu comment créer un dictionnaire juste au dessus.

Comment rajouter des éléments ?

Pas de méthode de type append() ici : il suffit de déclarer la nouvelle clé et sa valeur associée :

>>> occ['d'] = 3662

>>> occ

{'total': 101427, 'a': 6610, 'b': 1325, 'c': 2065, 'd': 3662}

14° Créer quelques clés supplémentaires contenant n'importe quoi : nombres, strings, tuples, listes et même d'autres dictionnaires si vous le voulez. Vous allez voir juste après comment les supprimer donc vous pouvez faire n'importe quoi.

Comment supprimer une clé et sa valeur associée ?

Il faut utiliser la fonction native del() de cette façon :

>>> del occ['b']

>>> occ

{'total': 101427, 'a': 6610, 'c': 2065, 'd': 3662}

>>> del occ['z']

KeyError: 'z'

Comme vous le voyez, c'est facile mais cela provoque également une erreur en cas de clé inexistante.

Comment connaitre les clés disponibles ?

Nous avons vu que tenter d'accéder à une clé inexistance crée une erreur. C'est facheux ...

Pour vérifier l'existence d'une clé, il suffit d'utiliser un test x in dico qui teste la présence du mot-clé x dans le dictionnaire dico.

>>> 'total' in occ

True

>>> 'a' in occ

True

>>> 'ahah' in occ

False

Avec un code du type if 'a' in occ: vous avez donc la possibilité de tester la présence d'une clé avant d'en demander la valeur et de provoquer une exception.

Tester une clé, c'est bien mais connaitre la totalité des clés, c'est mieux. Pour cela, il faut utiliser la méthode keys() :

>>> print(occ.keys())

dict_keys(['total', 'c', 'd', 'a'])


>>> for cle in occ.keys():

     print(cle, " permet d'accéder à : ", occ[cle])


total permet d'accéder à : 101427

c permet d'accéder à : 2065

d permet d'accéder à : 3662

a permet d'accéder à : 6610

En fait, c'est tellement important que si vous tapez ceci, vous aurez le même effet : transmettre occ via un in va calculer occ.keys().

>>> for cle in occ:

     print(cle, " permet d'accéder à : ", occ[cle])


total permet d'accéder à : 101427

c permet d'accéder à : 2065

d permet d'accéder à : 3662

a permet d'accéder à : 6610

Et voilà, vous savez maintenant comment obtenir l'ensemble des clés et l'utiliser pour lire l'ensemble du contenu.

Il existe deux méthodes similaires à keys() :

La méthode values() renvoie la liste des valeurs stockées et pas les clés.

>>> print(occ.values())

dict_values([101427, 2065, 3662, 6610])

La méthode items() renvoie la liste des tuples (clé, valeur associée).

>>> print(occ.items())

dict_items([('total', 101427), ('c', 2065), ('d', 3662), ('a', 6610)])

Ces trois méthodes ne renvoient pas exactement des listes mais des listes d'un type particulier :

>>> type(occ.keys())

<class 'dict_keys'>

>>> type(occ.values())

<class 'dict_values'>

>>> type(occ.items())

<class 'dict_items'>

Que peut-on utiliser en tant que clé ?

N'importe quel type d'entités non-mutables : des nombres, des strings ou des tuples par exemple.

C'est extrémement intéressant pour gérer des cases sur un plateau de jeu par exemple :

toucheCoule = { (10,5):"Bateau", (10,6):"Bateau" }

C'est super pratique pour gérer un plateau sans avoir à stocker plein d'informations sur des cases ... vides.

5 - Retour au mini-projet

Il reste encore des choses à dire sur les dictionnaires mais vous aurez voir la fiche une prochaine fois.

Il ne devrait pas vous rester beaucoup de temps et il reste à finaliser ce programme qui permet de comptabiliser automatiquemet les caractères utilisés et leurs nombres.

Question finale :

15° Créer un programme utilisant les dictionnaires qui permet de stocker les caractères utilisés et le nombre de fois où le caractère apparait dans le texte.

Voici un résultat possible, bien améliorable. On remarquera au passage la présence du caractère unicode 65279 : nous avons donc affaire à un fichier qui possédait le caractère BOM.

pause

Le fichier py-024-lovecraft.txt contient 101427 caractères.

Le caractére  de code UNICODE 65279 apparaît 1 fois.

Le caractére de code UNICODE 32 apparaît 16231 fois.

Le caractére _ de code UNICODE 95 apparaît 126 fois.

Le caractére t de code UNICODE 116 apparaît 7264 fois.

Le caractére h de code UNICODE 104 apparaît 5222 fois.

Le caractére e de code UNICODE 101 apparaît 10006 fois.

Le caractére d de code UNICODE 100 apparaît 3662 fois.

Le caractére u de code UNICODE 117 apparaît 2296 fois.

Le caractére n de code UNICODE 110 apparaît 5681 fois.

Le caractére w de code UNICODE 119 apparaît 1922 fois.

Le caractére i de code UNICODE 105 apparaît 5163 fois.

Le caractére c de code UNICODE 99 apparaît 2065 fois.

Le caractére o de code UNICODE 111 apparaît 5750 fois.

Le caractére r de code UNICODE 114 apparaît 4665 fois.

Le caractére

de code UNICODE 10 apparaît 1714 fois.

Le caractére b de code UNICODE 98 apparaît 1325 fois.

Le caractére y de code UNICODE 121 apparaît 1538 fois.

Le caractére . de code UNICODE 46 apparaît 856 fois.

Le caractére p de code UNICODE 112 apparaît 1310 fois.

Le caractére l de code UNICODE 108 apparaît 3647 fois.

Le caractére v de code UNICODE 118 apparaît 654 fois.

Le caractére a de code UNICODE 97 apparaît 6610 fois.

Le caractére f de code UNICODE 102 apparaît 1786 fois.

Le caractére " de code UNICODE 34 apparaît 114 fois.

Le caractére g de code UNICODE 103 apparaît 1758 fois.

Le caractére s de code UNICODE 115 apparaît 5026 fois.

Le caractére , de code UNICODE 44 apparaît 987 fois.

Le caractére m de code UNICODE 109 apparaît 1884 fois.

Le caractére - de code UNICODE 45 apparaît 392 fois.

Le caractére æ de code UNICODE 230 apparaît 8 fois.

Le caractére k de code UNICODE 107 apparaît 671 fois.

Le caractére ? de code UNICODE 63 apparaît 15 fois.

Le caractére j de code UNICODE 106 apparaît 55 fois.

Le caractére ! de code UNICODE 33 apparaît 24 fois.

Le caractére x de code UNICODE 120 apparaît 88 fois.

Le caractére : de code UNICODE 58 apparaît 4 fois.

Le caractére 1 de code UNICODE 49 apparaît 31 fois.

Le caractére ' de code UNICODE 39 apparaît 533 fois.

Le caractére q de code UNICODE 113 apparaît 77 fois.

Le caractére ; de code UNICODE 59 apparaît 104 fois.

Le caractére z de code UNICODE 122 apparaît 73 fois.

Le caractére 9 de code UNICODE 57 apparaît 17 fois.

Le caractére 2 de code UNICODE 50 apparaît 15 fois.

Le caractére 8 de code UNICODE 56 apparaît 6 fois.

Le caractére 6 de code UNICODE 54 apparaît 6 fois.

Le caractére 7 de code UNICODE 55 apparaît 9 fois.

Le caractére 4 de code UNICODE 52 apparaît 4 fois.

Le caractére 0 de code UNICODE 48 apparaît 4 fois.

Le caractére 5 de code UNICODE 53 apparaît 7 fois.

Le caractére 3 de code UNICODE 51 apparaît 5 fois.

Le caractére ( de code UNICODE 40 apparaît 1 fois.

Le caractére ) de code UNICODE 41 apparaît 1 fois.

Le caractére [ de code UNICODE 91 apparaît 3 fois.

Le caractére ] de code UNICODE 93 apparaît 3 fois.

Le caractére ä de code UNICODE 228 apparaît 1 fois.

Le caractére * de code UNICODE 42 apparaît 5 fois.

Le caractére é de code UNICODE 233 apparaît 1 fois.

Le caractére ü de code UNICODE 252 apparaît 1 fois.

pause

Une possibilité de correction :

#!/usr/bin/env python

# -*- coding: utf-8 -*-


fichier = "py-024-lovecraft.txt"

objFichier = open(fichier,"r", encoding = 'utf-8')

livre = (objFichier.read()).lower()

objFichier.close()


n = len(livre)


occ = {}

occ['total'] = (fichier,n)


for c in livre :

    if c not in occ :

        occ[c] = livre.count(c)


input("pause")

print("Le fichier ",fichier, " contient ",n, " caractères.")

for cle in occ :

    if not cle == 'total' :

        texte = 'Le caractére {0} de code UNICODE {2} apparaît {1:6} fois.'

        print(texte.format(cle,occ[cle],ord(cle)))


input("pause")