Données Tables

Identification

Infoforall

9 - Recherche dans une table de données


Prérequis : l'activité OpenData de Python et les deux activités sur le parcours séquentiel de la partie Algo.

Vous savez maintenant

  • Ouvrir des fichiers-texte
  • Importer des modules
  • Utiliser des algorithmes pour réaliser des recherches ou des calculs dans un tableau.

Il est donc temps de voir comment gérer tout cela en même temps !

Voyons comment trouver le meilleur Pokemon parmi tous ceux à notre disposition.

Evaluation ✎ : questions 01-08-11-13-16-17-19-20.

1 - Collection et table

1.1 Collection, enregistrement et descripteur

Vocabulaire

Une collection est un ensemble d'informations partageant la même codification : chaque entrée partage le même nombre de colonnes et, sur une colonne, toutes les valeurs comportent le même type d'information :

Collection Descripteurs
Pokemon n° Nom Type Points de vie
Enregistrement d'index 0 1 Bulbasaur Grass 318
Enregistrement d'index 1 2 Ivysaur Grass 405
Enregistrement d'index 2 3 Venusaur Grass 525
Enregistrement d'index 3 3 VenusaurMega Venusaur Grass 625

Chaque ligne comporte des informations sur une entité qu'on nomme un enregistrement.

Chaque colonne se nomme un descripteur et l'ensemble des noms des descripteurs se nomme l'en-tête.

Collection en Python

La plupart du temps, on utilise un simple tableau Python (type list) pour stocker une collection.

1
collec = [enr0, enr1, enr2]

Pour lire ou agir sur les entrées une par une, on a donc deux possibilités :

La solution générique en utilisant l'indice

.. ..
for i in range(len(collec)): lire_ou_modifier(collec[i])

La solution en lecture seule

.. ..
for enregistrement in collec: lire_uniquement(enregistrement)
Résumé et synonymes

L'ensemble du tableau à double entrée : Table ou Collection

La première ligne : l'en-tête

Les autres lignes : enregistrements, objets

Les colonnes : descripteurs, attributs

Le contenu d'une case : une valeur

1.2 Implémenter les enregistrements en Python

Nous allons voir trois manières de stocker nos enregistrements dans notre collection.

Tableau de tableaux

La collection est de type list[list] : un tableau qui contient d'autres tableaux représentant les enregistrements.

On crée des tableaux dont l'indice (index en anglais) 0 est l'identifiant, l'indice 1 le nom ...

[1, 'Bulbasaur', 'Grass', 318]

On les rajoute au fur et à mesure à notre collection avec la méthode des tableaux : list.append().

1 2 3 4 5 6 7 8 9 10
pokemons = [] # Variable contenant la table pokemons.append( [1, 'Bulbasaur', 'Grass', 318] ) # Rajoute ce pokemon à la fin de la table pokemons.append( [2, 'Ivysaur', 'Grass', 405 ] ) # Rajoute ce pokemon à la fin de la table for i in range(len(pokemons)): # Pour chaque indice de ce tableau pokemons print(pokemons[i]) # Affiche le pokemon numéro i for pokemon in pokemons: # Pour chaque pokemon de ce tableau pokemons print(pokemon) # Affiche ce pokemon

Le problème de cette structure ? On utilise des tableaux non homogènes. Cela fonctionne, en Python, car quelques soient les choses contenues, le tableau ne contient pas vraiment les contenus mais les références vers les contenus. Tout tableau Python est un tableau de références, donc homogènes.

Mais si vous utilisez un langage utilisant de vrais tableaux, ça ne fonctionnera pas. Que faire alors ?

Comment avoir un objet officiellement capable de gérer des enregistrements non homogènes ?

Tableau de tuples

La collection est de type list[tuple] : un tableau qui contient des tuples représentant les enregistrements.

Les seules différences sont donc la présence des parenthèses et le fait que les enregistrements sont donc immuables.

1 2 3 4 5 6 7 8 9 10
pokemons = [] # Variable contenant la table pokemons.append( (1, 'Bulbasaur', 'Grass', 318) ) # Rajoute ce pokemon à la fin de la table pokemons.append( (2, 'Ivysaur', 'Grass', 405 ) ) # Rajoute ce pokemon à la fin de la table for i in range(len(pokemons)): # Pour chaque indice de ce tableau pokemons print(pokemons[i]) # Affiche le pokemon numéro i for pokemon in pokemons: # Pour chaque pokemon de ce tableau pokemons print(pokemon) # Affiche ce pokemon

Cette fois, on peut utiliser sans problème différents types de contenus selon les descripteurs. Mais les enregistrements sont immuables.

Par contre, le contenu est immuable et pour rechercher telle ou telle information, il faut connaitre le numéro de colonne de cette information. Comment obtenir un type à la fois muable et capable de localiser une information autrement qu'avec un numéro de colonne ?

Tableau de dictionnaires

On peut utiliser des enregistrements sous forme de n-uplets nommés partageant les mêmes descripteurs

En Python, on peut utiliser les dictionnaires pour faire office de n-uplets nommés.

On crée des dictionnaires avec des clés id, nom ...

1 2 3 4 5 6 7 8 9 10
pokemons = [] # Variable contenant la table pokemons.append( {'id':1, 'nom':'Bulbasaur', 'type':'Grass','pv':318} ) pokemons.append( {'id':2, 'nom':'Ivysaur', 'type':'Grass','pv':405} ) for i in range(len(pokemons)): # Pour chaque indice de ce tableau pokemons print(pokemons[i]) # Affiche le pokemon numéro i for pokemon in pokemons: # Pour chaque pokemon de ce tableau pokemons print(pokemon) # Affiche ce pokemon

On notera que le fait que les enregistrements soient des dictionnaires ne change rien à la gestion de la table / collection : la variable pokemons est toujours un tableau. C'est juste que les éléments du tableau sont maintenant des dictionnaires.

Le numéro du Pokemon sert à faire le lien entre différentes formes d'un même Pokemon : en forme normal, en forme améliorée... C'est pour cela que certains numéros mènent au même Pokemon mais sous une forme différente.

01° Réaliser les tâches suivantes :

  1. Utiliser le programme pour voir comment il fonctionne.
  2. 1 2 3 4 5 6 7
    pokemons = [] # Variable contenant la table pokemons.append( (1, 'Bulbasaur', 'Grass', 318) ) # Rajoute ce pokemon à la fin de la table pokemons.append( (2, 'Ivysaur', 'Grass', 405 ) ) # Rajoute ce pokemon à la fin de la table for i in range(len(pokemons)): # Pour chaque indice de ce tableau pokemons print(pokemons[index]) # Affiche le pokemon numéro i
  3. A quoi voit-on que les enregistrements sont des tuples ?
  4. Rajouter la ligne de code permettant d'obtenir l'affichage suivant : j'ai rajouté une nouvelle entrée.
  5. (1, 'Bulbasaur', 'Grass', 318) (2, 'Ivysaur', 'Grass', 405) (3, 'Venusaur', 'Grass', 525)
  6. Modifier la boucle en utilisant plutôt la forme for ... in ..., sans le range.

...CORRECTION...

  1. Lancement :
  2. (1, 'Bulbasaur', 'Grass', 318) (2, 'Ivysaur', 'Grass', 405)
  3. A la présence de parenthèses
  4. Rajout :
  5. 1 2 3 4 5
    pokemons = [] # Variable contenant la table pokemons.append( (1, 'Bulbasaur', 'Grass', 318) ) # Rajoute ce pokemon à la fin de la table pokemons.append( (2, 'Ivysaur', 'Grass', 405 ) ) # Rajoute ce pokemon à la fin de la table pokemons.append( (3, 'Venusaur', 'Grass', 525 ) ) # Rajoute ce pokemon à la fin de la table
  6. Autre version de la boucle :
  7. . .
    for pokemon in pokemons: # Pour chaque pokemon de ce tableau pokemons print(pokemon) # Affiche ce pokemon

02° Réaliser les tâches suivantes :

  1. Utiliser le programme pour voir comment il fonctionne.
  2. 1 2 3 4 5 6 7
    pokemons = [] # Variable contenant la table pokemons.append( {'id':1, 'nom':'Bulbasaur', 'type':'Grass','pv':318} ) pokemons.append( {'id':2, 'nom':'Ivysaur', 'type':'Grass','pv':405} ) for pokemon in pokemons: # Pour chaque pokemon de ce tableau pokemons print(pokemon) # Affiche ce pokemon
  3. A quoi voit-on que les enregistrements sont des dictionnaires ?
  4. Rajouter la ligne de code permettant d'obtenir l'affichage suivant : j'ai rajouté une nouvelle entrée.
  5. {'id': 1, 'nom': 'Bulbasaur', 'type': 'Grass', 'pv': 318} {'id': 2, 'nom': 'Ivysaur', 'type': 'Grass', 'pv': 405} {'id': 3, 'nom': 'Venusaur', 'type': 'Grass', 'pv': 525}
  6. Modifier la boucle en utilisant plutôt la forme for ... in range(..)..

...CORRECTION...

  1. Affichage obtenu :
  2. {'id': 1, 'nom': 'Bulbasaur', 'type': 'Grass', 'pv': 318} {'id': 2, 'nom': 'Ivysaur', 'type': 'Grass', 'pv': 405}
  3. A la présence d'accolades plutôt que de crochets (tableaux) ou parenthèses (tuples)
  4. Rajout :
  5. 1 2 3 4 5
    pokemons = [] # Variable contenant la table pokemons.append( {'id':1, 'nom':'Bulbasaur', 'type':'Grass','pv':318} ) pokemons.append( {'id':2, 'nom':'Ivysaur', 'type':'Grass','pv':405} ) pokemons.append( {'id':3, 'nom':'Venusaur', 'type':'Grass','pv':525} )
  6. Boucle :
  7. . .
    for i in range(len(pokemons)): # Pour chaque indice de ce tableau pokemons print(pokemons[index]) # Affiche le pokemon numéro i

2 - Création de la collection des Pokemons

2.1 Fichier CSV

Description générale

C'est un moyen simple de transmettre des données : c'est un simple fichier-texte compréhensible par n'importe quel langage de programmation et n'importe quel tableur.

CSV veut dire Comma Separated Values : des valeurs séparées par des virgules.

Un tableur est capable d'ouvrir les fichiers CSV.

Structure des données dans le fichier CSV
  1. (option) La première ligne du fichier contient les intitulés des attributs : l'en-tête.
  2. Chaque ligne constitue un enregistement. Rappel : la fin de ligne correspond à un octet valant 10 (norme ASCII), représentée ci-dessous par , et par le string "\n" en Python.
  3. Chaque valeur est séparée des autres valeurs de l'enregistrement par une virgule.
Exemple

A titre d'exemple voici la représentation CSV d'un tableau contenant la collection ci-dessous :

Collection Descripteurs
Identifiant Type Utilisation Couleur
Enregistrements 0 Voiture Sport Rouge
1 Hélicoptère Tourisme Marron
2 Voiture Sport Rouge

Contenu du fichier vehicules.csv :

1 2 3 4
Identifiant, Type, Utilisation, Couleur 0, Voiture, Sport, Rouge 1, Hélicoptère, Tourisme, Marron 2, Voiture, Sport, Rouge

Vous allez récupérer le fichier CSV contenant les Pokemons. Trois façons d'y aboutir :

  1. Utiliser le module urllib.request ou
  2. Utiliser le module requests ou
  3. Télécharger directement le fichier depuis le dépot.

Nous allons ici simplement utiliser un programme qui va aller télécharger les données et créer le fichier CSV dans le même dossier que celui du programme Python que vous allez créer.

03 (solution A)° Téléchargement avec le module urllib. Réaliser les tâches suivantes :

  1. Sauvegarder ce programme dans un répertoire qu'on nommera tables en le nommant download_csv.py.
  2. 1 2
    import urllib.request urllib.request.urlretrieve('https://gist.githubusercontent.com/armgilles/194bcff35001e7eb53a2a8b441e8b2c6/raw/92200bc0a673d5ce2110aaad4544ed6c4010f687/pokemon.csv', 'mesdonnees.csv')
  3. Ouvrir le répertoire pour vérifier qu'il ne contient que votre script python pour le moment.
  4. Lancer le programme et vérifier qu'il a téléchargé le fichier CSV qui devrait se trouver dans le même répertoire que votre fichier Python.

03 (solution B)° Téléchargement avec le module Requests. Réaliser les tâches suivantes :

  1. (optionnel) Si vous êtes chez vous, dans Thonny, aller dans le menu TOOLS - MANAGE PACKAGES. Lancer une recherche sur Requests et installer le module requests.
  2. Enregistrer le programme ci-dessous sous le nom download_csv2.py dans le répertoire tables.
  3. 1 2 3 4 5
    import requests reponse = requests.request(url='https://gist.githubusercontent.com/armgilles/194bcff35001e7eb53a2a8b441e8b2c6/raw/92200bc0a673d5ce2110aaad4544ed6c4010f687/pokemon.csv', method='GET') fichier = open('mesdonnees2.csv', 'w', encoding='utf-8') fichier.write(reponse.text) fichier.close()
  4. Vous devrirez constater l'existence d'un fichier CSV nommé pokemon.csv dans ce même dossier.

Vérifiez que le fichier CSV mesdonnees2.csv a bien été créé automatiquement.

03 (solution C)° Si les deux scripts de téléchargement automatique ne fonctionne pas depuis votre poste, ce n'est pas grave, on peut le télécharger à la main.

  1. Allez à l'adresse Github suivante (ou faire une simple recherche dans un moteur de recherche sur CSV + Pokemon): CSV POKEMON GITHUB.
  2. Vous devriez aboutir sur une page qui vous présente visuellement le fichier sous la forme d'un tableau, comme un tableur.

    Une définition : celle d'encodage

    Remarque : si le dépot git a disparu, vous pouvez utiliser cette version locale stockée sur infoforall : LE FICHIER CSV EN LOCAL. L'intérêt des dépôts en ligne est que les mises à jour sont automatiquement effectuées lorsqu'on réalise une modification pour corriger une erreur.

  3. Appuyez sur le bouton RAW ("brut" en Français) : vous devriez voir le vrai contenu de ce fichier : un beau fichier CSV, à savoir un fichier-texte qui possède juste un nom qui finit en .csv.
  4. Enregistrer le fichier sur votre disque local en utilisant le bouton de téléchargement et placez le dans votre répertoire tables.
  5. Vérifier que le fichier CSV apparaît bien dans votre répertoire.

04° Quelque soit la méthode utilisée, ouvrir ce fichier avec votre tableur habituel.

Avec OpenCalc de LibreOffice, on obtient la page d'accueil suivante :

Tableur

Il faut lui indiquer l'encodage utilisé (à priori UTF-8 si le fichier est récent) ainsi que le caractère séparateur. Ici la virgule.

Ensuite, on lance et on obtient ceci :

CSV dans un tableur

Maintenant que nous avons récupéré les données, nous allons créer la table correspondante. J'ai choisi d'utiliser la méthode du tableau de p-uplets nommés pour cette activité, mais le tableau de tableaux ou le tableau de tuples fonctionnent tout aussi bien.

05° Enregistrer le programme ci-dessous sous le nom creation_collec.py dans le même dossier que les autres.

Il s'agit de fonctions (décrites et construites dans l'activité Python nommée OpenData) qui permettent de transformer les données d'un fichier CSV en list[dict]. Inutile de les lire, nous allons juste les utiliser.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
def decomposer(chaine, separateur): """Renvoie un tableau avec un élément de la chaine par case :: chaine (str) :: la chaîne de caractères à décomposer :: separateur (str) :: le caractère de séparation :: return (list[str]) :: un tableau contenant les différents éléments :: exemple :: >>> decomposer('1,voiture,sport,rouge', ',') ['1', 'voiture', 'sport', 'rouge'] """ chaine = chaine.replace('\n', '') # supprime le passage à la ligne chaine = chaine.replace('\r', '') # supprime le retour chariot tableau = chaine.split(separateur) # tableau à partir de la chaine return tableau def creer_enregistrement(valeurs, attributs): """Renvoie un dictionnaire utilisant attributs--entree comme clés--valeurs :: param valeurs(list) :: tableau des X futures valeurs :: param attributs(list) :: tableau des X futures clés :: return (dict) :: le dictionnaire-enregistrement voulu :: exemple :: >>> creer([0, 'voiture', 'sport', 'rouge'], ['id', 'type', 'utilisation', 'couleur']) {'id': 0, 'type': 'voiture', 'utilisation': 'sport', 'couleur': 'rouge'} """ d = {} # dico vide pour le moment if len(valeurs) == len(attributs): # si les paramètres sont compatibles for i in range(len(valeurs)): # pour chaque indice des deux tableaux d[attributs[i]] = valeurs[i] # utilise attribut n°i comme clé de la valeur n°i return d def creer_collection(nom_fichier): """Renvoie un tableau de dictionnaires à partir d'un fichier CSV :: param nom_fichier(str) :: le nom du fichier CSV correct à traiter :: return (list[dict]) :: le tableau de dictionnaires """ # 1 - On crée une tableau vide collec = [] # 2 - On ouvre le fichier en lecture fichier = open(nom_fichier, 'r', encoding='utf-8') # 3 - On lit la première ligne pour créer le tableau des attributs entete = fichier.readline() # on lit la première ligne, \n inclus attributs = decomposer(entete, ',') # extraction des attributs dans un tableau # 4 - On lit la suite du fichier ligne par ligne for ligne in fichier: # pour chaque ligne du fichier-texte valeurs = decomposer(ligne, ',') # extraction des valeurs dans un tableau nouveau = creer_enregistrement(valeurs, attributs) collec.append(nouveau) # rajout de l'enregistrement à la collection # 5 - On ferme le fichier fichier.close() # 6 - On renvoie la collection return collec

06° Créons maintenant notre programme qui va utiliser le fichier creation_collection.py comme un simple module :

  1. créer un fichier Python gestion_tables.py contenant le programme ci-dessous, en le plaçant toujours dans le même répertoire que les fichiers précédents.
  2. 1 2 3
    from creation_collec import creer_collection pokemons = creer_collection('mesdonnees.csv')

    Vous devriez avoir ceci dans votre dossier :

    📁 votre_dossier

    📄 mesdonnees.csv ou pokemon.csv

    📄 creation_collec.py

    📄 download_csv.py

    📄 gestion_tables.py

  3. Mettre en concordance le nom de votre fichier CSV et le nom du fichier précisé sur la ligne 3.
  4. Lancer l'exécution du fichier.
  5. Vous devriez constater si l'onglet VARIABLE de Thonny est ouvert (Menu View-Variables) que la variable pokemons contient bien les enregistrements lus à partir du fichier CSV.

  6. Tapez ceci dans la console, pour vérifier visuellement qu'on obtient bien un tableau de dictionnaires :
  7. >>> pokemons ??? >>> for p in pokemons: print(p) ???
Importation

Nous venons de voir comment on peut importer des fonctions contenues dans d'autres fichiers. Très pratique.

Les fameux modules math, turtle... ne sont rien d'autres que des fichiers nommées math.py, turtle.py...

C'est pour cela qu'il ne faut JAMAIS nommer l'un de vos fichiers Python en utilisant le nom d'un module existant ! Sinon, vous allez rendre invisible le module en cachant son nom derrière votre propre fichier.

Maintenant que nous avons notre table, nous allons pouvoir traiter les informations qu'elle contient.

Question : Existe-il des pokemons dont l'attaque ET la défense dépassent la moyenne ?

3 - Tests de cohérence

07-A° Regardons le premier pokemon en tapant ceci dans la console :

>>> pokemons[0] {'#': '1', 'Name': 'Bulbasaur', 'Type 1': 'Grass', 'Type 2': 'Poison', 'Total': '318', 'HP': '45', 'Attack': '49', 'Defense': '49', 'Sp. Atk': '65', 'Sp. Def': '65', 'Speed': '45', 'Generation': '1', 'Legendary': 'False'} >>> pokemons[0]['Attack'] '49'

On voit bien que la valeur associée à la clé 'Attack' n'est pas un entier pour le moment mais un string. C'est normal, nous avons construit nos enregistrements à partir d'un fichier-texte.

07-B° Très compliqué : associer le bon type à chacune des évaluations a, b, et c ci-dessous :

>>> a = pokemons >>> b = pokemons[0] >>> c = pokemons[0]['Attack']

Propositions :

  1. Un tableau
  2. Un dictionnaire
  3. La valeur associée à l'une des clés d'un dictionnaire

Première chose à faire : transformer l'attaque et la défense en nombres.

✎ 08-A (sur un entier)° Taper et analyser les instructions suivantes :

>>> x = "5" >>> x '5' >>> x = int(x) >>> x 5

✎ 08-B (sur un pokemon)° Taper et analyser les instructions suivantes :

>>> pokemons[0]['Attack'] '49' >>> pokemons[0]['Attack'] = int(pokemons[0]['Attack']) >>> pokemons[0]['Attack'] 49

On voit donc que pokemons[0]['Attack'] = int(pokemons[0]['Attack']) veut dire :

  • ... = int(pokemons[0]['Attack']) : évalue sous forme d'un entier la valeur associée à la clé Attack puis
  • pokemons[0]['Attack'] = ... : place cet entier en tant que nouvelle valeur associée à la clé Attack

✎ 08-C (sur tous les pokemons)° Taper et analyser les instructions suivantes :

>>> for pokemon in pokemons : pokemon['Attack'] = int(pokemon['Attack']) >>> pokemons[10]['Attack'] ???

La variable pokemons (avec un s) est la collection/table contenant les enregistrements/dictionnaires.

On constate que tous les pokemons ont maintenant des attaques sous forme d'un entier.

Tests de cohérence

Un test de cohérence consiste à vérifier que chaque descripteur contient bien une donnée ayant le bon type et vérifiant éventuellement certaines conditions supplémentaires.

On pourrait ainsi vérifier :

  • Que tous les enregistrements possèdent bien une attaque sous forme d'entiers et que la valeur soit positive dans tous les cas;
  • Que tous les enregistrements possèdent bien un type parmis une liste de valeurs autorisées : 'Grass', 'Fire', 'Water', 'Bug', 'Normal', 'Poison', 'Electric', 'Ground', 'Fairy', 'Fighting', 'Psychic', 'Rock', 'Ice', 'Dragon', 'Dark', 'Flying', 'Ghost' ou 'Steel'.
Hors programme : mais bien utile ici, try ... except ...

Nous avons déjà la fonction creer_collection() qui transforme le fichier CSV en list[dict] dans Python. Nous voudrions maintenant écarter les enregistrements non cohérents par rapport aux types attendus.

Pour cela, nous allons réaliser une fonction coherence_type() dont voici le prototype :

coherence_type(table:list[dict]) -> list[dict]

Cette fonction :

  • récupère une collection/table de type list[dict]
  • elle tente de convertir les strings dans le type attendu. Si tout se passe bien, on rajoute le dictionnaire modifié dans la nouvelle réponse.
  • si l'une des conversions se passe mal, on lève une exeption mais le mot-clé except permet de récupérer la main et faire autre chose que stopper et afficher un message rouge : on va afficher l'enregistrement problématique dans la console.
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
def coherence_type(table): """Vérifie et transforme si possible les valeurs des enregistrements dans le bon type :: param table (list[dict]) :: une table contenant des dictionnaires :: return (list[dict]) :: table avec uniquement les enregistrements valides """ nouvelle = [] for data in table : # Pour chaque enreg. dans la table try: data['Attack'] = int(data['Attack']) data['Defense'] = int(data['Defense']) data['Total'] = int(data['Total']) data['HP'] = int(data['HP']) data['Name'] = str(data['Name']) if data['Name'] != '' : nouvelle.append(data) except: print(data) return nouvelle

09° La fonction coherence_type() renvoie une nouvelle table, nommée nouvelle dans la fonction et rajoute l'enregistrement en ligne 25 s'il est valide.

Question :Comment se nomme la façon de créer la table avec append() dans la fonction coherence_type() ?

  • A : Par extension
  • B : Par extensions succesives
  • C : Par compréhension
  • D : Par appréhension

...CORRECTION...

Il s'agit d'une création par EXTENSIONS SUCCESSIVES(réponse B)

L'extension (réponse A) consiste à fournir directement le contenu dans les crochets : t = [5,10,15] par exemple.

Hors programme : mais bien utile ici, le formatage en longueur de caractéres des fStrings

voir_enregistrement() vous permet de visualiser avec un fString trois des descripteurs en fournissant 25 caractères d'espace pour le nom, 5 caractères pour l'attaque et 5 caractères pour la défense.

5 6 7
def voir_enregistrement(data:dict) -> None: """Visualisation de trois descripteurs sur l'enregistrement""" print(f"{data['Name']:25}{data['Attack']:5}{data['Defense']:5}")

10° Mettre cette nouvelle version de gestion_tables.py en mémoire. Lancer puis Utiliser les instructions-console fournies sous le programme.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
# 1 -Importation depuis des modules from creation_collec import creer_collection # 2 - Déclaration des fonctions def voir_enregistrement(data:dict) -> None: """Visualisation de trois descripteurs sur l'enregistrement""" print(f"{data['Name']:25}{data['Attack']:5}{data['Defense']:5}") def coherence_type(table): """Vérifie et transforme si possible les valeurs des enregistrements dans le bon type :: param table (list[dict]) :: une table contenant des dictionnaires :: return (list[dict]) :: table avec uniquement les enregistrements valides """ nouvelle = [] for data in table : # Pour chaque enreg. dans la table try: data['Attack'] = int(data['Attack']) data['Defense'] = int(data['Defense']) data['Total'] = int(data['Total']) data['HP'] = int(data['HP']) data['Name'] = str(data['Name']) if data['Name'] != '' : nouvelle.append(data) except: print(data) return nouvelle # 3 - Programme if __name__ == '__main__' : pokemons = creer_collection('mesdonnees.csv') pokemons = coherence_type(pokemons)

Au lancement, aucun enregistrement rejété n'apparait dans la console normalement : le fichier CSV est cohérent.

Instructions à taper dans la console :

>>> pokemons[0] {'#': '1', 'Name': 'Bulbasaur', 'Type 1': 'Grass', 'Type 2': 'Poison', 'Total': 318, 'HP': 45, 'Attack': 49, 'Defense': 49, 'Sp. Atk': '65', 'Sp. Def': '65', 'Speed': '45', 'Generation': '1', 'Legendary': 'False'} >>> pokemons[0]['Attack'] 49

Lorsqu'on visualise l'un des enregistrements, on voit que les clés Total, HP, Attack, Defense sont bien associées à des entiers.

✎ 11° Finir la fonction coherence_type() : il faut tenter de gérer les clés #, Sp. Atk, Sp. Def, Speed et Generation en entier (fonction int). Faire de même avec la clé Legendary qu'on transformera en booléen en testant data['Legendary'] == 'True'.

Après réalisation, voilà la réponse que vous devriez obtenir avec les instructions précédentes :

>>> pokemons[0] {'#': 1, 'Name': 'Bulbasaur', 'Type 1': 'Grass', 'Type 2': 'Poison', 'Total': 318, 'HP': 45, 'Attack': 49, 'Defense': 49, 'Sp. Atk': 65, 'Sp. Def': 65, 'Speed': 45, 'Generation': 1, 'Legendary': False}

Voilà : nous avons maintenant une table issue d'un fichier CSV et nous avons transformé correctement les types des variables. Il nous reste à chercher la valeur moyenne par exemple.

4 - Valeur maximum

Pour cela, il suffit d'utiliser les fonctions vues dans la partie Algorithmique.

La seule différence, c'est que le tableau contient des dictionnaires et pas directement des nombres.

Point à totalement comprendre !

Dans les fonctions créées, il va donc falloir transformer

  • t[i] en
  • t[i][cle] puisque l'élément t[i] du tableau est un dictionnaire
1 2 3 4 5 6
def trouver_max(t:list[dict], cle:str) -> 'le plus grand trouvé' : maxi = t[0][cle] for i in range( 1, len(t) ): if t[i][cle] > maxi : maxi = t[i][cle] return maxi

12° Utiliser la fonction trouver_max() telle que notée ligne **52** : en fournissant la table et la clé "Attack" de façon à stocker la plus grande attaque qu'on puisse trouver sur les polemons. Regarder l'onglet Variables de Thonny pour visualiser cette valeur.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
# 1 -Importation depuis des modules from creation_collec import creer_collection # 2 - Déclaration des fonctions def voir_enregistrement(data:dict) -> None: """Visualisation de trois descripteurs sur l'enregistrement""" print(f"{data['Name']:25}{data['Attack']:5}{data['Defense']:5}") def coherence_type(table): """Vérifie et transforme si possible les valeurs des enregistrements dans le bon type :: param table (list[dict]) :: une table contenant des dictionnaires :: return (list[dict]) :: table avec uniquement les enregistrements valides """ nouvelle = [] for data in table : # Pour chaque enreg. dans la table try : data['#'] = int(data['#']) data['Name'] = str(data['Name']) data['Type 1'] = str(data['Type 1']) data['Type 2'] = str(data['Type 2']) data['Total'] = int(data['Total']) data['HP'] = int(data['HP']) data['Attack'] = int(data['Attack']) data['Defense'] = int(data['Defense']) data['Sp. Atk'] = int(data['Sp. Atk']) data['Sp. Def'] = int(data['Sp. Def']) data['Speed'] = int(data['Speed']) data['Generation'] = int(data['Generation']) data['Legendary'] = data['Legendary'] == 'True' if data['Name'] != '' : nouvelle.append(data) except : print(data) return nouvelle def trouver_max(collec, cle): """Renvoie la valeur maximale lue dans la clé descripteur des dict. du tableau :: param collec(list[dict]) :: le tableau NON VIDE d'éléments qu'on peut comparer :: param cle(type valide) :: une clé valide pour du descripteur voulu :: return (type variable) :: la plus grande valeur dans ce descripteur """ maxi = collec[0][cle] for i in range( 1, len(collec) ): if collec[i][cle] > maxi : maxi = collec[i][cle] return maxi # 3 - Programme if __name__ == '__main__' : pokemons = creer_collection('mesdonnees.csv') pokemons = coherence_type(pokemons) atk_max = trouver_max(pokemons, 'Attack')

✎ 13° Récupérer maintenant également la valeur maximale de la défense. On placera le résultat dans une variable dfc_max

5 - Recherche et extraction

Et maintenant, c'est fini !

Nous n'avons plus qu'à créer un tableau en compréhension en fonction de certaines conditions.

Un premier exemple : trouver les meilleurs Pokemons en attaque. Par exemple, ceux qui ont au moins 80% de l'attaque maximale.

Et ça, c'est super simple !

14° Vérifier que les variables pokemons, atk_max et dfc_max soient bien en mémoire. Utiliser ensuite les instructions-console pour vérifier que cela fonctionne et demander des explications si vous ne comprennez pas telle ou telle indiciation ou résultat.

Si vous n'avez pas trouvé la question 13, rajouter simplement ceci à la fin du code : dfc_max = 230.

Si on tape ceci, cela veut dire qu'on récupère tous les pokemons de la table pokemon. On crée donc une copie de la collection.

>>> reponses = [pokemon for pokemon in pokemons]

Comment ne récupérer que ceux qui ont au moins 80% de l'attaque maximale ?

>
>>> reponses = [p for p in pokemons if p['Attack'] > (0.8 * atk_max) ]

Cette fois, on a rajouté une condition.

Demander d'afficher les résultats de deux manières :

>>> for pokemon in reponses : print(pokemon) >>> for pokemon in reponses : voir_enregistrement(pokemon)

Pas mal, non  ?

Voici les résultats pour informations.

>>> for pokemon in reponses : print(pokemon) {'#': 127, 'Name': 'PinsirMega Pinsir', 'Type 1': 'Bug', 'Type 2': 'Flying', 'Total': 600, 'HP': 65, 'Attack': 155, 'Defense': 120, 'Sp. Atk': 65, 'Sp. Def': 90, 'Speed': 105, 'Generation': 1, 'Legendary': False} {'#': 130, 'Name': 'GyaradosMega Gyarados', 'Type 1': 'Water', 'Type 2': 'Dark', 'Total': 640, 'HP': 95, 'Attack': 155, 'Defense': 109, 'Sp. Atk': 70, 'Sp. Def': 130, 'Speed': 81, 'Generation': 1, 'Legendary': False} {'#': 150, 'Name': 'MewtwoMega Mewtwo X', 'Type 1': 'Psychic', 'Type 2': 'Fighting', 'Total': 780, 'HP': 106, 'Attack': 190, 'Defense': 100, 'Sp. Atk': 154, 'Sp. Def': 100, 'Speed': 130, 'Generation': 1, 'Legendary': True} {'#': 214, 'Name': 'HeracrossMega Heracross', 'Type 1': 'Bug', 'Type 2': 'Fighting', 'Total': 600, 'HP': 80, 'Attack': 185, 'Defense': 115, 'Sp. Atk': 40, 'Sp. Def': 105, 'Speed': 75, 'Generation': 2, 'Legendary': False} {'#': 248, 'Name': 'TyranitarMega Tyranitar', 'Type 1': 'Rock', 'Type 2': 'Dark', 'Total': 700, 'HP': 100, 'Attack': 164, 'Defense': 150, 'Sp. Atk': 95, 'Sp. Def': 120, 'Speed': 71, 'Generation': 2, 'Legendary': False} {'#': 257, 'Name': 'BlazikenMega Blaziken', 'Type 1': 'Fire', 'Type 2': 'Fighting', 'Total': 630, 'HP': 80, 'Attack': 160, 'Defense': 80, 'Sp. Atk': 130, 'Sp. Def': 80, 'Speed': 100, 'Generation': 3, 'Legendary': False} {'#': 289, 'Name': 'Slaking', 'Type 1': 'Normal', 'Type 2': '', 'Total': 670, 'HP': 150, 'Attack': 160, 'Defense': 100, 'Sp. Atk': 95, 'Sp. Def': 65, 'Speed': 100, 'Generation': 3, 'Legendary': False} {'#': 354, 'Name': 'BanetteMega Banette', 'Type 1': 'Ghost', 'Type 2': '', 'Total': 555, 'HP': 64, 'Attack': 165, 'Defense': 75, 'Sp. Atk': 93, 'Sp. Def': 83, 'Speed': 75, 'Generation': 3, 'Legendary': False} {'#': 383, 'Name': 'GroudonPrimal Groudon', 'Type 1': 'Ground', 'Type 2': 'Fire', 'Total': 770, 'HP': 100, 'Attack': 180, 'Defense': 160, 'Sp. Atk': 150, 'Sp. Def': 90, 'Speed': 90, 'Generation': 3, 'Legendary': True} {'#': 384, 'Name': 'RayquazaMega Rayquaza', 'Type 1': 'Dragon', 'Type 2': 'Flying', 'Total': 780, 'HP': 105, 'Attack': 180, 'Defense': 100, 'Sp. Atk': 180, 'Sp. Def': 100, 'Speed': 115, 'Generation': 3, 'Legendary': True} {'#': 386, 'Name': 'DeoxysAttack Forme', 'Type 1': 'Psychic', 'Type 2': '', 'Total': 600, 'HP': 50, 'Attack': 180, 'Defense': 20, 'Sp. Atk': 180, 'Sp. Def': 20, 'Speed': 150, 'Generation': 3, 'Legendary': True} {'#': 409, 'Name': 'Rampardos', 'Type 1': 'Rock', 'Type 2': '', 'Total': 495, 'HP': 97, 'Attack': 165, 'Defense': 60, 'Sp. Atk': 65, 'Sp. Def': 50, 'Speed': 58, 'Generation': 4, 'Legendary': False} {'#': 445, 'Name': 'GarchompMega Garchomp', 'Type 1': 'Dragon', 'Type 2': 'Ground', 'Total': 700, 'HP': 108, 'Attack': 170, 'Defense': 115, 'Sp. Atk': 120, 'Sp. Def': 95, 'Speed': 92, 'Generation': 4, 'Legendary': False} {'#': 475, 'Name': 'GalladeMega Gallade', 'Type 1': 'Psychic', 'Type 2': 'Fighting', 'Total': 618, 'HP': 68, 'Attack': 165, 'Defense': 95, 'Sp. Atk': 65, 'Sp. Def': 115, 'Speed': 110, 'Generation': 4, 'Legendary': False} {'#': 486, 'Name': 'Regigigas', 'Type 1': 'Normal', 'Type 2': '', 'Total': 670, 'HP': 110, 'Attack': 160, 'Defense': 110, 'Sp. Atk': 80, 'Sp. Def': 110, 'Speed': 100, 'Generation': 4, 'Legendary': True} {'#': 646, 'Name': 'KyuremBlack Kyurem', 'Type 1': 'Dragon', 'Type 2': 'Ice', 'Total': 700, 'HP': 125, 'Attack': 170, 'Defense': 100, 'Sp. Atk': 120, 'Sp. Def': 90, 'Speed': 95, 'Generation': 5, 'Legendary': True} {'#': 719, 'Name': 'DiancieMega Diancie', 'Type 1': 'Rock', 'Type 2': 'Fairy', 'Total': 700, 'HP': 50, 'Attack': 160, 'Defense': 110, 'Sp. Atk': 160, 'Sp. Def': 110, 'Speed': 110, 'Generation': 6, 'Legendary': True} {'#': 720, 'Name': 'HoopaHoopa Unbound', 'Type 1': 'Psychic', 'Type 2': 'Dark', 'Total': 680, 'HP': 80, 'Attack': 160, 'Defense': 60, 'Sp. Atk': 170, 'Sp. Def': 130, 'Speed': 80, 'Generation': 6, 'Legendary': True}
>>> for pokemon in reponses : voir_enregistrement(pokemon) PinsirMega Pinsir 155 120 GyaradosMega Gyarados 155 109 MewtwoMega Mewtwo X 190 100 HeracrossMega Heracross 185 115 TyranitarMega Tyranitar 164 150 BlazikenMega Blaziken 160 80 Slaking 160 100 BanetteMega Banette 165 75 GroudonPrimal Groudon 180 160 RayquazaMega Rayquaza 180 100 DeoxysAttack Forme 180 20 Rampardos 165 60 GarchompMega Garchomp 170 115 GalladeMega Gallade 165 95 Regigigas 160 110 KyuremBlack Kyurem 170 100 DiancieMega Diancie 160 110 HoopaHoopa Unbound 160 60

Le plus beau avec la création par compréhension, c'est qu'on peut mettre plusieurs conditions liées par des or ou des and, des not ...

15° Tester ce nouveau programme qui comporte simplement une requête un peu plus avancée en dernière ligne. On voit qu'on cherche les pokemons dont l'attaque est au moins 80% du maximum disponible et (and) la défense 60% du maximum disponible.

Uniquement le programme principal :

55 56 57 58 59 60 61 .. ..
# 3 - Programme if __name__ == '__main__' : pokemons = creer_collection('mesdonnees.csv') pokemons = coherence_type(pokemons) atk_max = trouver_max(pokemons, 'Attack') dfc_max = trouver_max(pokemons, 'Defense') r = [p for p in pokemons if p['Attack'] > (0.8*atk_max) and p['Defense'] > (0.6*dfc_max)]

Le programme complet :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
# 1 -Importation depuis des modules from creation_collec import creer_collection # 2 - Déclaration des fonctions def voir_enregistrement(data:dict) -> None: """Visualisation de trois descripteurs sur l'enregistrement""" print(f"{data['Name']:25}{data['Attack']:5}{data['Defense']:5}") def coherence_type(table): """Vérifie et transforme si possible les valeurs des enregistrements dans le bon type :: param table (list[dict]) :: une table contenant des dictionnaires :: return (list[dict]) :: table avec uniquement les enregistrements valides """ nouvelle = [] for data in table : # Pour chaque enreg. dans la table try : data['#'] = int(data['#']) data['Name'] = str(data['Name']) data['Type 1'] = str(data['Type 1']) data['Type 2'] = str(data['Type 2']) data['Total'] = int(data['Total']) data['HP'] = int(data['HP']) data['Attack'] = int(data['Attack']) data['Defense'] = int(data['Defense']) data['Sp. Atk'] = int(data['Sp. Atk']) data['Sp. Def'] = int(data['Sp. Def']) data['Speed'] = int(data['Speed']) data['Generation'] = int(data['Generation']) data['Legendary'] = data['Legendary'] == 'True' if data['Name'] != '' : nouvelle.append(data) except : print(data) return nouvelle def trouver_max(collec, cle): """Renvoie la valeur maximale lue dans la clé descripteur des dict. du tableau :: param collec(list[dict]) :: le tableau NON VIDE d'éléments qu'on peut comparer :: param cle(type valide) :: une clé valide pour du descripteur voulu :: return (type variable) :: la plus grande valeur dans ce descripteur """ maxi = collec[0][cle] for i in range( 1, len(collec) ): if collec[i][cle] > maxi : maxi = collec[i][cle] return maxi # 3 - Programme if __name__ == '__main__' : pokemons = creer_collection('mesdonnees.csv') pokemons = coherence_type(pokemons) atk_max = trouver_max(pokemons, 'Attack') dfc_max = trouver_max(pokemons, 'Defense') r = [p for p in pokemons if p['Attack'] > (0.8*atk_max) and p['Defense'] > (0.6*dfc_max)]

Si on cherche à lancer le programme et à afficher le résultat :

>>> for pokemon in r : voir_enregistrement(pokemon) TyranitarMega Tyranitar 164 150 GroudonPrimal Groudon 180 160

On remarquera qu'on aurait pu faire les requêtes l'une après l'autre également puisque c'est un et : d'abord FILTRER les meilleurs attaquants parmi tous les pokemons puis FILTRER les meilleurs défenseurs parmi les meilleurs attaquants.

61 62
r = [p for p in pokemons if p['Attack'] > (0.8*atk_max)] r = [p for p in r if p['Defense'] > (0.6*dfc_max)]

✎ 16-A° Fournir les requêtes à faire pour obtenir la liste des pokemons ayant plus de 180 en attaque OU 180 en défense.

A partir de là, vous voyez qu'on peut s'amuser assez facilement en créant des filtres faisant exactement ce qu'on veut.

✎ 16-B° Fournir les requêtes à faire pour obtenir la liste des pokemons de génération 2 ET légendaires.

Le filtrage et la sélection sont donc des choses assez faciles à réaliser une fois qu'on a bien construit notre programme Python d'extraction. Comme ce sont des tâches usuelles dans tous systèmes d'informations, vous verrez comment utiliser les bases de données en Terminale NSI avec le langage SQL qui va nous simplifier le travail. Enormémemt.

6 - Doublons

Les doublons sont l'un des plus grands problèmes du stockage de données : des données strictement identiques présentes plusieurs fois. On veille donc à contrôler les rajouts sur les enregistrements : ce nouveau client est peut-être juste un client qui a oublié son mot de passe et se crée un nouveau compte !

6.1 Contrainte d'UNICITE

C'est pour cela qu'on rajoute toujours un attribut supplémentaire dans nos tables : un identifiant qu'on veut absolument n'attribuer qu'à un seul et unique enregistrement.

On la nomme souvent clé primaire : c'est une valeur qui doit :

  1. Exister : on ne doit pas lui donner une valeur vide
  2. Etre unique : elle ne doit apparaître que sur l'une des lignes, jamais deux fois.

Pour imopser cette contrainte d'UNICITE de la clé primaire, on vérifie que l'enregistrement n'existe pas déjà dans la collection puis on lui attribue une valeur de clé primaire qui n'existe pas déjà.

Dans le cas de notre collection de Pokemons, il n'y en a pas pour l'instant : la clé "#" fait le lien entre plusieurs formes d'un même Pokemon et plusieurs enregistrements portent donc ce même numéro.

✎ 17° Puisqu'on compare le nouveau enregistrement à tous ceux déjà présents, un par un, la vérification d'absence de doublons sur un nouvel enregistrement qu'on veut rajouter est un algorithme :

  • A : à coût constant (Θ(1))
  • B : à coût linéaire (Θ(n))
  • C : à coût quadratique (Θ(n2))
  • D : à coût cubique (Θ(n3))
6.2 Doublons absolus

Les doublons absolus : les deux entrées sont entièrement similaires, même l'identifiant unique ! Ces doublons peuvent apparaître si on fusionne des tables entre elles alors qu'elles sont issues initialement d'une même collection.

Exemple :

  • on extrait dans une nouvelle table les Pokemons ayant plus que 150 en Attaque.
  • 1
    r1 = [p for p in pokemons if p['Attack'] > 150]
    • Les pokemons obtenus dans la première table :
    • PinsirMega Pinsir - GyaradosMega Gyarados - MewtwoMega Mewtwo X - HeracrossMega Heracross - TyranitarMega Tyranitar - BlazikenMega Blaziken - Slaking - BanetteMega Banette - GroudonPrimal Groudon - RayquazaMega Rayquaza - DeoxysAttack Forme - Rampardos - GarchompMega Garchomp - GalladeMega Gallade - Regigigas - KyuremBlack Kyurem - DiancieMega Diancie - HoopaHoopa Unbound
  • On crée ensuite la table des Pokemons ayant plus de 150 en Défense.
  • 1
    r2 = [p for p in pokemons if p['Defense'] > 150]
    • Les pokemons obtenus dans la première table :
    • SlowbroMega Slowbro - Cloyster - Onix - Steelix - SteelixMega Steelix - Shuckle - Aggron - AggronMega Aggron - Regirock - GroudonPrimal Groudon - DeoxysDefense Forme - Bastiodon - Avalugg-
  • On fusionne les résultats dans une unique table : si on ne fait pas attention, on va nécessairement se retrouver avec des doublons absolus : tous les Pokemons qui ont une attaque supérieure à 150 ET une défense supérieure à 150.
  • 1
    r3 = r1 + r2
    • La fusion des pokemons sans précaution :
    • PinsirMega Pinsir - GyaradosMega Gyarados - MewtwoMega Mewtwo X - HeracrossMega Heracross - TyranitarMega Tyranitar - BlazikenMega Blaziken - Slaking - BanetteMega Banette - GroudonPrimal Groudon - RayquazaMega Rayquaza - DeoxysAttack Forme - Rampardos - GarchompMega Garchomp - GalladeMega Gallade - Regigigas - KyuremBlack Kyurem - DiancieMega Diancie - HoopaHoopa Unbound - SlowbroMega Slowbro - Cloyster - Onix - Steelix - SteelixMega Steelix - Shuckle - Aggron - AggronMega Aggron - Regirock - GroudonPrimal Groudon - DeoxysDefense Forme - Bastiodon - Avalugg-

Dans cette partie, nous allons donc modifier notre création de collection à partir du CSV : nous allons vérifier qu'un enregistrement n'existe pas déjà AVANT de le rajouter dans notre collection : on ne peut pas avoir une confiance absolue en un fichier qu'on a récupéré sur Internet.

6.3 Doublons relatifs

Les doublons relatifs : les deux entrées identifient un même objet mais sont sinon un peu différentes.

Ces doublons sont réellement problématiques car les données ne sont pas vraiment identiques alors qu'il s'agit bien d'une entité unique qui devrait normalement n'avoir qu'une version !

  • Cas des Pokemons : la personne renseignant le fichier CSV a peut-être rempli deux fois les informations sur un même Pokemon sur deux lignes très éloignées. On aura ainsi un identifiant différent MAIS le Pokemon présent deux fois.
  • Cas des listes de personnes : il faut vérifier si le p-uplet (nom, prénom, numéro de sécu) n'apparaît qu'une fois. Nom Prénom n'est pas suffisant pour savoir que c'est bien un doublon.
  • Cas des strings : un nom avec Majuscule ou sans majuscule. Un nom composé avec un espace ou un tiret...

Cela peut arriver lors de rajouts : un utilisateur oublie qu'il a déjà crée un compte chez vous et se réinscrit sous le même nom, prénom, email... Si vous ne faites pas attention et que vous acceptez cette nouvelle inscription, votre table des clients risque rapidement de ne plus être exploitable : un même utilisateur va apparaître dans plusieurs enregistrements.

Moralité : demandez vous ce qui doit identifier à coup sur une entrée unique.

Dans le cas de nos Pokemons, c'est ici juste le nom qui importe. Il suffit juste de vérifier que le descripteur Name des autres enregistrements n'est pas identique à celui-ci.

18° Réaliser les actions suivantes.

  1. Ouvrir le fichier creation_collec.py.
  2. On y trouve dans la fonction creer_collection() le code qui rajoute automatiquement le nouvel enregistrement sans vérifier s'il s'agit d'un doublon dont le double est déjà présent dans la collection.

    53 54 55 56 57
    # 4 - On lit la suite du fichier ligne par ligne for ligne in fichier: # pour chaque ligne du fichier-texte valeurs = decomposer(ligne, ',') # extraction des valeurs dans un tableau nouveau = creer_enregistrement(valeurs, attributs) collec.append(nouveau) # rajout de l'enregistrement à la collection
  3. Remplacer ce code par le suivant : on vérifie si un Pokemon portant le même nom existe déjà dans la collection avant de faire le append().
  4. Pour cela, on crée une variable booléenne doublon. Si elle devient True, on sort de la boucle de lecture des enregistrements un à un : puisque qu'on sait que c'est un doublon, ce n'est plus la peine de vérifier les autres fiches, c'est une perte de temps.

    Nouveauté : comment sortir d'une boucle avant qu'elle ne soit finie ? Il faut utiliser le mot-clé break. On arrête alors immédiatement la boucle et on continue le programme sur la première instruction derrière la boucle. ATTENTION : il faut vraiment éviter de l'utiliser. Même problème que les fonctions avec plusieurs sorties : àa devient compliqué de prouver la correction.

    53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    # 4 - On lit la suite du fichier ligne par ligne numero_unique = 1 for ligne in fichier: # pour chaque ligne du fichier-texte valeurs = decomposer(ligne, ',') # extraction des valeurs dans un tableau nouveau = creer_enregistrement(valeurs, attributs) doublon = False for enregistrement in collec : if nouveau['Name'] == enregistrement['Name']: doublon = True break # On stoppe la boucle if not(doublon): # Si aucun autre dictionnaire ne porte le même nom nouveau['cle_primaire'] = numero_unique numero_unique = numero_unique + 1 collec.append(nouveau) # rajout à la collection else : print('Doublon relatif') print(nouveau)

✎ 19° Que doit contenir doublon pour que la condition de la ligne 63 autorise à effectuer le rajout de l'enregistrement avec append à la ligne 66 ?

Quasi-doublon

Ceux là sont plus difficile à déceler : il s'agit d'enregistrement dont la différence tient à un détail, une faute de frappe. Un prénom avec un é ou ï comme Héloïse qu'on trouve orthographié Héloise ou Hèloise. Une lettre oubliée ou déplacée : Sarah écrit Sara ou Sarha... Il existe un très grand nombre d'erreurs possibles.

Autre erreur courant : les majuscules - minuscules. C'est bien pour cela que la plupart des systèmes stockent toutes les entrées en minuscules, transformant au besoin les entrées utilisateurs en minuscules. C'est plus facile de vérifier la cohérence si on est certain que tout doit être normalement en lettres minuscules.

Elles sont bien plus difficiles à déceler que les erreurs de typage (la cohérence entre un type et une valeur est facile à programmer) ou les erreurs de doublons classiques (facile à programmer également).

Pour éviter les quasi-doublons, il faut donc une fonction qui veille à étudier en détail chaque entrée, au besoin en demandant l'intervention d'un opérateur humain pour valider telle ou telle modification.

Python possède d'ailleurs 3 méthodes applicables aux strings :

  • upper renvoie une copie du string en mettant tout en majuscules.
  • lower renvoie une copie du string en mettant tout en minuscules.
  • title renvoie une copie au chaque début de mot est en majuscule, les autres en minuscules.

✎ 20° En parlant de cohérence des noms utilisés en vue de vérifier la présence de quasi-doublons, quelle critique pourrait-on faire au nom de la clé nommée cle_primaire par rapport aux autres ?

Voilà pour cette activité bien fournie.

En résumé :

  • On doit veiller à la cohérence des données avant d'insérer l'enregistrement dans la table (notamment à transformer les données dans le bon type dans la mesure où les données proviennent souvent d'un fichier texte)
  • On doit veiller à ce qu'un enregistrement ne soit pas un doublon avant de l'insérer dans la table
  • La recherche ou le calcul sur un descripteur n'est pas différent de la même tâche sur un tableau simple.
  • L'extraction de données est simplifiée en Python avec les créations de tableaux par compréhension.

7 - FAQ

Des compléments sur le mot-clé break

On utilise l'instruction break pour sortir définitivement d'une boucle avant sa fin naturelle.

Exemple avec un compteur évoluant normalement de 4 à 80 de 2 en 2. Mais on sort définitivement de la boucle lorsque le compteur vaut 40 :

1 2 3 4 5
for compteur in range(4,80,2): print(compteur, end= " ") if (compteur == 40): break print('Fini')
4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 Fini

Et le mot-clé continue

Lorsqu'on le rencontre, on sort aussi mais moins fortement : on considère juste que le tour de boucle en cours est terminé : on revient au début. Si on est à l'étape 6, on passe donc immédiatement au début de la 7.

Je voudrais compter de 0 à 7 mais sans passer par 5, c'est possible ? Oui, comme ça par exemple :

1 2 3 4 5
for compteur in range(8): if (compteur == 5): continue print(compteur) print('Fini')
0 1 2 3 4 6 7 Fini

Et la structure try except

Aucune connaissance n'est exigible là dessus en 1er NSI. Mais c'est très pratique.

La structure permet notamment de rendre vos applications solides face aux entrées des utilisateurs.

Demander un nombre et vous êtes presque certain que certains vont vous répondre AZERTY

Le principe de cette structure est de remplacé la levée d'exception (lorsqu'il y a une erreur), par ... autre chose qu'un arrêt du programme et un message dans la console.

On trouve 3 blocs dont les actions liées sous tabulées à droite, comme d'habitude avec Python.

Dans le bloc try, l'action qu'on veut tenter de réaliser mais qu'on soupçonne de pouvoir provoquer une erreur dans certaines conditions.

Si l'une des actions provoquent une erreur, on stoppe les instructions à cet endroit et on passe au bloc except. Cela veut dire que ce qui est situé sous l'erreur dans le bloc try ne va pas être exécuté.

Si aucune erreur n'intervient, on ne lève pas d'exception et on n'exécute donc pas le bloc except.

1 2 3 4 5 6 7 8 9 10 11 12
continuer = True while continuer : a = input('Entrez un nombre entier : ') b = input('Entrez un autre nombre entier : ') try : a = int(a) b = int(b) c = a + b continuer = False except : print('Des nombres entiers ! Nan mais quoi !') print(f'{a}+{b} = {c}')

Voici un exemple d'utilisation :

Entrez un nombre entier : 8 Entrez un autre nombre entier : A Des nombres entiers ! Nan mais quoi ! Entrez un nombre entier : 8 Entrez un autre nombre entier : 15 8+15 = 23

Activité publiée le 15 03 2020
Dernière modification : 15 03 2020
Auteur : ows. h.