Traitement de données en tables

Les données organisées en table correspondent à une liste de p-uplets nommés qui partagent les mêmes descripteurs . La mobilisation de ce type de structure de données permet de préparer les élèves à aborder la notion de base de données qui ne sera présentée qu'en classe terminale. Il s'agit d'utiliser un tableau doublement indexé ou un tableau de p-uplets, dans un langage de programmation ordinaire et non dans un système de gestion de bases de données.

ContenusCapacités attendues CommentairesApplications

Indexation de tables

Importer une table depuis un fichier texte tabulé ou un fichier CSV.

Est utilisé un tableau doublement indexé ou un tableau de p-uplets qui partagent les mêmes descripteurs.

Recherche dans une table

Rechercher les lignes d'une table vérifiant des critères exprimés en logique propositionnelle.

La recherche de doublons, les tests de cohérence d'une table sont présentés.

Deux enregistrements (ici ce sera des dictionnaires) sont des doublons si tous les items des deux dictionnaires sont égaux deux à deux, il suffit qu'un des items soit différent pour une même clef pour que les deux dictionnaires soient différents et ne soient pas des doublons.
Exemple :
dic[1] = { "nom" : "Doe", "Prenom" : "John", "age" : 15 } n'est pas le doublon de dic[2] = { "nom" : "Doe", "Prenom" : "John", "age" : 35 }, il s'agit d'enregistrements d'homonymes d'âges différents
Une table est cohérente sssi elle ne contient pas de doublons !

Tri d'une table

Trier une table suivant une colonne.

Une fonction de tri intégrée au système ou à une bibliothèque peut être utilisée.

La table BDD est une liste de dictionnaires. Pour une clef donnée , la liste des valeurs correspondantes à cette clefs sera extraite à partir du parcours de chaque ligne de cette table. Puis la méthode sort() sera aplliquée à cette liste. Cette liste sera ensuite parcourue pour recontriure une table triée selon l'ordre croissant des valeurs associées à cette clef. Pour un ordre décroissant, la liste triée sera inversée par le slice listeTriee[::-1] qui inverse en place la liste (cf. exercice 6).

Fusion de tables

Construire une nouvelle table en combinant les données de deux tables.

La notion de domaine de valeurs est mise en évidence.

La fusion de deux tables est simple lorsque leur différence ne porte que sur leur contenu et que celui ci est constitué de dictionnaires de même format (même nombre d'items et même liste de clefs.
Elle se complique, lorsque les deux tables ne contiennent pas des dictionnaires de même format. Si la fusion est réalisée, cela entrainera la création d'items de valeur None associées aux clefs qui sont nouvelles pour certains dictionnaires.
Les deux tables mettront en communs leurs clefs et valeurs, cela entraînera peut être la création de doublons mais indétectables s'il n'y a pas de clef primaire pour les détecter dans des dictionnaires de format et de contenus légèrement différents.

Les données en tables ou tableaux permettent de synthétiser des sommes importantes d'information récoltées à l'aide de formulaires.
Python permet d'écrire, de lire ces données sous forme de fichier au format CSV et autre et de les traiter

Recueil des données dans un tableur et exportation en fichier csv

Utilisation d'un formulaire

Les formulaires en ligne : balises spécifiques des formulaires

La balise HTML form permet de délimiter un plage de formulaire ; elle contiendra un certain nombre d'éléments permettant de recueillir des informations :

  • la balise input dans laquelle il faut préciser le type d'input avec l'attribut type='' permet de saisir la plupart des entrées possibles :
    • button
    • checkbox : cases à cocher (toutes si on le souhaite) ;
    • radio : choix unique !
    • number : nombre
    • range : curseur qui varie dans un intervalle fixe
    • password : mot de passe (les entrées sont masquées)
    • text : du texte (type string) ;
    • color : choix d'une couleur dans la palette ;
    • file : ouvrir un fichier
    • email : reccueil d'une adresse de courriel ;
    • time : heure ; 
    • url ;
    • week ; 
    • date ;
    • datetime-local ;
    • month ;
    • image ;
    • tel ; 
    • reset : bouton qui réinitialise le formulaire = remise à zéro ou état initial ;
    • search ;
    • submit : à cliquer quand on désire envoyer les informations saisies dans le formulaire au serveur;
    • hidden : caché (provisoirement …) ;

Exemple de formulaire en ligne : google form

exercice : dans drive, créer un nouveau formulaire d'inscription en première pour les élèves de seconde qui recueillera :

  • Prénom ;
  • Nom ;
  • date de naissance ;
  • Adresse (sans code postal) ;
  • Code Postal ;
  • Ville ;
  • Cases à cocher avec (quelques exemples) les spécialités du bac général et les séries technologiques ;

Les formulaires seront envoyés à vos camarades et vous devrez tous y répondre rapidement. Les données peuvent être recueillis dans un tableau (google sheet) et ensuite devront être téléchargées au format CSV et ods.

Le format CSV

Définition

wikipedia

Le for mat 'Comma separated Value' est une mise en for me standardisées des données de tableaux codés en texte simple (utf-8) :

  • la première ligne du tableau contient les titres des colonnes (ou champs ou clefs) ;
  • les autres lignes contiennent un semble de données ou fiche ou enregistrement ;
  • chaque ligne du fichier CSV contient les données séparées par des virgules  (comma en anglais).

Ouverture/écriture d'un fichier en python

Python permet de lire et écrire (et modifier) très facilement des fichiers. Il crée un objet de type itérateur (parcouru une seule fois !) ; ici cet objet sera arbitrairement appelé 'fichier'.

Il y a deux méthodes de lecture/écriture d'un fichier en python, par défaut un fichier est lu sans être modifié (lecture seule : 'r' = read) :

  1. avec la fonction prédéfinie fichier = open('nomFichier.csv','r')  dans laquelle la variable appelée fichier représente le contenu du fichier texte (objet itérateur). Une lecture de l'objet fichier peut ensuite être réalisée lignes par lignes ; le fichier devra être fermé par la méthode fichier.close() ;
  2. avec le mot clef with : qui définit un bloc d'instructions relatives au  traitement du fichier ; l'avantage est que la sortie du bloc ferme automatiquement le fichier ; exemple de code simple.
Sources utiles :

https://www.python for beginners.com/files/reading-and-writing-files-in-python

https://www.w3schools.com/python/python_file_write.asp

lecture globale

fichier.read() extrait tout le contenu du fichier ; une fois lu, l'itérateur est vide !

essayer : 

print(fichier.read()) //deux fois de suite !

Remarque à écrire dans votre cours : effet du premier et du second affichage de l'itérateur complet

lecture ligne par ligne
for ligne in fichier :
        print(ligne)

traitement des données du fichier CSV

Recueil des données
Conversion des lignes en listes de clefs ou données

Un objet de type string (mot, phrase ou texte) peut être divisé grâce à la méthode split dont l'argument est le séparateur (ici la virgule) :

listeDonnées = ligne.split(',')
Conversion du fichier en tableau à deux dimensions (liste de liste)

Avant de lire le fichier CSV, une variable de type liste est créée et initialisée :

tableau = []     // type(tableau) renvoie < class 'list'>
Conversion du fichier CSV en liste de dictionnaires

Le fichier CSV sera ouvert en lecture et lu ligne par ligne :

with :
    fichier = open ("tableau.csv","rt")
    compteur = 0 # voir plus loin …
    for ligne in fichier :

La première ligne du fichier CSV contient les titres des colonnes du tableau qui devientront les clefs de dictionnaires correspondant à chaque ligne de données du tableau. Cette ligne sera lue la première, il faut donc utiliser un compteur (variable initialisée à zéro qui sera incrémenté de un après chaque tour de boucle) pour savoir quand il faudra stocker les clefs (quand compteur == 0).

if compteur == 0 :
    listeClefs = ligne.split(',')
    N = len (listeClefs) # compte le nombre de clefs
    n = len (listeClefs[N-1]) #taille de la dernière clef
    listeClefs[N-1] = listeClefs[N-1][:n-2] # les deux caractères \n de fin de ligne sont enlevés à la dernière clef
    compteur += 1
else : # les autres lignes fournissent les données pour construire les dictionnaires
    dico = {} #initialisation du dictionnaire
    listeDonnees = ligne.split(',')
    for i in range(N) :
         if i == N-1 :
             n = len (listeDonnees[N-1]) #taille de la dernière donnée
             listeDonnees[i] = listeDonnees [i][:n-2]
         dico[listeClefs[i]] = listeDonnees [i]
    tableau.append(dico)

Ressources et liens utiles :

 

Rappels sur les listes

Définition, initialisation

les objets de type liste

Opérations et méthodes

opérations par exemple avec : liste = [ 3, "coucou", [1,2,3] ]
méthodes (= fonctions aplicables à l'objet) par exemple avec : liste = [ 3, "coucou", [1,2,3] ]

Ouvrir, Créer, Lire des fichiers

ouvrir un fichier

avec open et close
avec with

Rappels sur les dictionnaires

Définition, initialisation

les objets de type dictionnaire

Opérations et méthodes

modifier, agir sur les dictionnaires

Par exemple nous prendrons pour ce qui suit :
dic = { "num" : 2, "message" : "coucou", "tuple" : (1,2), "liste" : [3,4,5]}

Les méthodes sont des fonctions associées aux objets. Ici, elles sont associées aux objets de type dictionnaire.

Tableau des méthodes (d'après w3school et en anglais !
MethodDescription
clear() Removes all the elements from the dictionary : grand nettoyage !
copy() Returns a copy of the dictionary : copy profonde
fromkeys()Returns a dictionary with the specified keys and value
get()Returns the value of the specified key
items()Returns a list containing a tuple for each key value pair
keys()Returns a list containing the dictionary's keys
pop() Removes the element with the specified key
popitem()Removes the last inserted key-value pair
setdefault()Returns the value of the specified key. if the key does not exist: insert the key, with the specified value
update()Updates the dictionary with the specified key-value pairs
values()Returns a list of all the values in the dictionary
DEBUG

Exercices d'application sur les tables ou liste de dictionnaires

Exercices de difficulté croissance et corrigés

  1. lire un fichier et ranger ses lignes dans une liste

    lire et afficher les résultats obtenus avec fichierCSV="BDD.csv"
    Télécharger BDD.csv et BDDdbl.csv

    Pensez à afficher le résultat avec la fonction print.
    Aide, utiliser :

    >
    fichierCSV = "BDD.csv"
    with open(fichierCSV, 'r') as fichier :  # le bloc with ferme le fichier dès que le bloc est terminé
        for  ligne in fichier :    # à vous de faire le reste …
            xxxxxxxxxxxxx
            xxxxxxxxxx
            xxxxxxxxx
            xxxxxxxxxxx
        return ListeDeDic
    
    proposition de corrigé
    with open('BDD.csv', 'r') as fichier :  # le bloc with ferme le fichier dès que le bloc est terminé
    		table = []
    		for  ligne in fichier :    # à vous de faire la suite
    		    table.append(ligne)
    		    
          # avec with ce n'est pas la peine d'écrire fichier.close() quand la lecture de l'itérateur fichier est terminée
    
  2. lire un fichier CSV et ranger ses lignes dans une liste de dictionnaires
    proposition de corrigé cf. corrigé exo 3
  3. transformer votre code (exo 2) en une fonction lireCSV(fichierCSV) qui retourne une liste de dictionnaires ; lire et afficher les résultats obtenus avec fichierCSV="BDD.csv" et fichierCSV ="BDDdbl.csv"

    Pour voir le résultat :

    listeDic = lireeCSV("BDD.csv")    # par exemple
    print (listeDic)
    print (len ("Nombre de lignes de données = ", len (listeDic))
    		
    proposition de corrigé
    def lireCSV(pathFichier , clefPrim = False) :
        """
        Fonction qui lit un fichier csv et renvoie une liste de dictionnaires
        clefPrim est un booleen qui par défaut vaut False
        si clefPrim = True, une clef primaire numérique unique est ajoutée à chaque
        dictionnaire
        """
         # vérifications de la conformité de pathFichier
        longueurFichier = len (pathFichier)
         # l'expression : assert (test logique) , "message si c'est faux"
         # permet de traquer les erreurs (bugs)
        assert type(pathFichier) == str , "Erreur : le format du fichier doit être du texte"
        assert pathFichier[longueurFichier-4:longueurFichier] == '.csv' or pathFichier[longueurFichier-4:longueurFichier] == '.CSV' , "Erreur : le format du fichier doit finir par .csv ou .CSV"
    
        try  :  # essaye d'ouvrir le fichier et si cela ne fonctionne pas 
             # renvoie un message d'erreur explicatif -> cf. except IOError = erreur d'entrée sortie (IO = In/Out)
            with open(pathFichier, 'r') as fichier : # le bloc with ferme le fichier dès que le bloc est terminé
                BDD = []    #la table de base de données sera une liste de dictionnaires
                n = 0        #compteur de lignes
    
                for  ligne in fichier:
                    if n == 0 : #lecture de la première ligne qui contient les clefs des dictionnaires
                        clefs = ligne.split(',')
                        nChamps = len (clefs)    #nombre de valeurs (et de clefs) dans chaque dictionnaire
                        l = len(clefs[nChamps-1]) # longueur de la dernière clef
                        clefs[nChamps-1] = clefs[nChamps-1][0:l-1]  #enlève le dernier caractère '\n' de retour à la ligne en fin de ligne
                    else : 
                        dictionnaire = {}   # le dictionnaire est initialisé
                    # https://www.w3schools.com/python/python_strings.asp
                    # pour voir les méthodes associées aux objets de type string
                        enregistrement = ligne.split(',')
                        m = 0
                    #création d'une clef primaire
                        if clefPrim : dictionnaire ["clefPrim" ] = n
                        for  valeur in enregistrement :
                            if m == nChamps - 1 :
                                l = len (valeur) # longueur de la dernière valeur
                                valeur = valeur[0:l-1] #enlève le dernier caractère '\n' de retour à la ligne en fin de ligne
    
                            dictionnaire [clefs [m] ] = valeur # ajoute au dictionnaire le couple : clefs [m] : valeur
                            m += 1
                        BDD.append(dictionnaire)
                        print("Enregistrement n° " , n+1, " : ", dictionnaire)
                    n += 1  # compte les lignes lues
            return BDD #renvoi la liste de dictionnaires = BDD
        
        except IOError :
             print('An error occured trying to read the file : \n une erreur est survenue en essayant de lire le fichier ",pathFichier," (absent ou ayant un autre nom).')
        
        except ValueError :
             print('Non-numeric data found in the file.')
    
        except ImportError :
            print ("NO module found")
            
        except EOFError :
             print('Why did you do an EOF on me?')
    
        except KeyboardInterrupt :
             print('You cancelled the operation.')
    
        except :
             print('An error occured.')
        
    BDD = lireCSV("BDD.csv")
        
     print("La table contient ", len (BDD), "lignes ou fiches ou enregistrements.")
    # permet de vérifier que la liste de dictionnaire est 
    # bien créée à partir de la lecture du fichier, sinon cela renvoie un 
    # message d'erreur et affiche None (le retour de la fonction)		
    		
  4. écrire une fonction qui affiche le tableau de données
    1. écrire une fonction qui prend en entrée la table (liste de dictionnaires), et retourne en sortie un tuple comprenant :
      1. la liste des longueurs de la plus grande clef ou de la plus grande donnée (valeur). Autrement dit, cette fonction renvoie la largeur minimale de chaque colonne dans le tableau à afficher;
      2. la liste des clefs pour les afficher sur la première ligne comme titre de chaque colonne.
      proposition de corrigé
      		def formaterColonnes (table) :
      			""" Fonction qui renvoie un tuple :
      			1) liste des clefs de la table ;
      			2) taille du plus long élément de chaque 
      			colonne sous forme d'une liste.
      			Exemple pour : 
      			table = [{"Nom" : "Doe"  , "Prenom" : "John"},
      					 {"Nom" : "Po"  ,  "Prenom" : "Edgard"},
      					 {"Nom" : "Ricki", "Prenom" : "Pictydirai"}]  
      			La fonction renvoie : ["Nom", "Prenom"], [5, 10]"""
      			listeformatsColonnes = []
      			nC = len (table[0])  # nombre de colonnnes
      			#source : https://www.science-emergence.com/Articles/Obtenir-une-liste-des-cl%C3%A9s-dun-dictionnaire-sous-python/
      			listeClefs = list(table[0].keys())  # liste des clefs de chaque dictionnaire de la table
      			nF = len (table)     # nombre de fiches
      			for  col in range(nC) :
      				# print("\nPour : ",listeClefs[col], "\n")
      				taille = 0
      				for  numF in range(nF) :
      					t = len (table[numF][listeClefs[col]])
      					# print(table[numF][listeClefs[col]], " de taille ", t)
      					if t > taille : taille = t
      				tailleClef = len (listeClefs[col])
      				if taille < tailleClef : taille = tailleClef
      				listeformatsColonnes.append(taille)
      				# print("Taille max de ", listeClefs[col] ," = ", taille)
      			return listeClefs, listeformatsColonnes    
              				
      				
    2. écrire une fonction qui prend en entrée un nombre n entier et renvoie (retourne) en sortie un chaîne de n espaces
      proposition de corrigé
      		########################################################################
      		# fonction qui renvoie une chaîne de n espaces
      		def espaces(n) :
      			espace = ""
      			for  i in range(n) : espace += " "
      			return espace
      		########################################################################
      		# sinon on a le droit d'écrire en python
      		
      		n * " " # et cela produit le même effet ! 
      		par exemple 3 * " " donne "   " ou 3 * "cou" donne "coucoucou"
      		Cette fonction ou cette expression permettront de rajouter des 
      		espaces pour les clefs ou valeurs de taille inférieure à la 
      		largeur maximale de colonne
      				
    3. écrire une fonction qui affichera la table sous forme de tableau avec sur la première ligne en titre les clefs et sur chaque ligne les données de chaque fiche ou enregistrement. Donner dans un deuxième temps une largeur fixe à chaque colonne (cf. n° 4)
      proposition de corrigé
      # La mauvaise solution c'est :
      ########################################################################
      # fonction d'affichage de la table
      def printTable (table) :
          """ Fonction d'affichage ligne par ligne de la table """
          n = 0
          for  fiche in table :
               print("Fiche n° ", n, " de la table :", fiche)
              n +=1
      ########################################################################
      # une des bonnes solutions :
      ########################################################################
      # fonction d'affichage simple de la table
      def printTableformatee (table) :
          """ Fonction d'affichage ligne par ligne de la table 
          avec un format de type tableau en colonnes de largeur régulière  """
          
          listeClefs, listeformatsColonnes = formaterColonnes (table)
          
          nC, nF = len (listeClefs), len (table)
          #nC est le nombre de colonne
          #nF est le nombre de lignes (ou fiches de données)
          
          #affichage de l'entête du tableau avec les noms de colonnes :
          premiereLigne = "║"      # le caractère "║" c'est pour faire joli
          for  elt in range(nC) :
              nEspaces = " " * (listeformatsColonnes[elt]-len (listeClefs[elt]))
              premiereLigne += " " + listeClefs[elt] + nEspaces + " ║"
          premiereLigne += "\n"     # retour à la ligne !
          
          #affichage des lignes du tableau
          n = 0
          for  fiche in table :
              ligne = "║"
              for  elt in range(nC) :
                  valeur = fiche[listeClefs[elt]]
                  nEspaces = " " * (listeformatsColonnes[elt]-len (valeur))
                  ligne += " " + str(valeur) + nEspaces + " ║"
               print(ligne + "\n"  )
              n += 1
      ########################################################################
      # fonction d'affichage décoratif de la table
      def printTableformateeDeco (table) :
          """ Fonction d'affichage ligne par ligne de la table 
          avec un format de type tableau en colonnes de largeur régulière  """
          
          listeClefs, listeformatsColonnes = for materColonnes (table)
          
          nC, nF = len (listeClefs), len (table)
          #nC est le nombre de colonne
          #nF est le nombre de lignes (ou fiches de données)
          
          premiereLigne = "║"     # élément décoratif !
          for  elt in range(nC) :
               # écriture des bordures haute inter et basse
              if elt == 0 : 
                  bordureHaut  = "╔" + (listeformatsColonnes[elt] + 2)* "═" + "╦"   # 2 espaces sont ajoutés, un avant et un après la plus grande largeur
                  bordureInter = "╠" + (listeformatsColonnes[elt] + 2)* "═" + "╬"
                  bordureBasse = "╚" + (listeformatsColonnes[elt] + 2)* "═" +" ╩"
              elif elt < nC - 1 : 
                  bordureHaut  += (listeformatsColonnes[elt] + 2)* "═" + "╦"
                  bordureInter += (listeformatsColonnes[elt] + 2)* "═" + "╬"
                  bordureBasse += (listeformatsColonnes[elt] + 2)* "═" + "╩"
              else :  
                  bordureHaut  += (listeformatsColonnes[elt] + 2)* "═" + "╗"
                  bordureInter += (listeformatsColonnes[elt] + 2)* "═" + "╣"
                  bordureBasse += (listeformatsColonnes[elt] + 2)* "═" + "╝"
              
              nEspaces = " " *(listeformatsColonnes[elt]-len (listeClefs[elt]))
              premiereLigne += " " + listeClefs[elt] + nEspaces + " ║"
          print(bordureHaut + "\n" + premiereLigne + "\n" + bordureInter)
          n = 0
          for  fiche in table :
              ligne = "║"
              for  elt in range(nC) :
                  valeur = fiche[listeClefs[elt]]
                  nEspaces = " " * (listeformatsColonnes[elt]-len (valeur))
                  ligne += " " + str(valeur) + nEspaces + " ║"
              if n < nF - 1 :
                   print(ligne + "\n" + bordureInter )
              else : 
                   print(ligne + "\n" + bordureBasse )
              n += 1
      ########################################################################				
      				
  5. Quelques requètes (questions) :
    1. texte = "Requète : recherche des gens qui habitent dans un département dont le code postal est inférieur à 29990"
      print ("\n\n\n",texte)
      		
      proposition de corrigé
      		reponse = [] #initialisation de la liste des réponses attendues
      		
      		for  enregistrement in BDD :
      			if int(enregistrement["code Postal"]) < 30000 :
      				reponse.append(enregistrement)
      				
      		nRep = len (reponse)        # nombre de réponses
      		if nRep == 0 :
      			print("Il n'y a aucun enregistrement qui correspond à la requète")
      		else :
      			print ("Il y a " + str(nRep) + " réponses : ")
      		printTableformateeDeco (reponse)
      		print ("____________________________________________________________________")
      		########################################################################		
      		
      			
    2. ########################################################################
      texte = "Requète : pourcentage d'enregistrements dont le numéro de dossier est plus grand que 2000 inclus"
      print ("\n\n\n",texte)
      			
      proposition de corrigé
      reponse = [] #initialisation de la liste des réponses attendues
      
      for  enregistrement in BDD :
          if int(enregistrement["Dossier num"]) >= 2000 :
              reponse.append(enregistrement)
              
      nRep = len (reponse)        # nombre de réponses
      N 	 = len (BDD)
       print("Il y a ",nRep / N * 100, "% des enregistrements qui correspondent à la requète : ",
      "\npourcentage d'enregistrements dont le numéro de dossier est plus grand que 2000 inclus")
       print("liste des enregistrements dont le numéro de dossier est >= à 2000")
      printTableformateeDeco (reponse)
      print ("____________________________________________________________________")
      ########################################################################
      			
    3. ########################################################################
      texte = "Requète : pourcentage d'enregistrements dont le nom commence par L"
      print ("\n\n\n",texte)
      			
      proposition de corrigé
      reponse = [] #initialisation de la liste des réponses attendues
      
      for  enregistrement in BDD :
          if int(enregistrement["Nom"]) == "L" :
              reponse.append(enregistrement)
              
      nRep = len (reponse)        # nombre de réponses
      N 	 = len (BDD)
       print("Il y a ",nRep / N * 100, "% des enregistrements qui correspondent à la requète : ",
      "\npourcentage d'enregistrements dont le nom commence par L")
       print("liste des enregistrements dont le nom commence par L")
      printTableformateeDeco (reponse)
      print ("____________________________________________________________________")
      ########################################################################
      			
  6. Éliminer les doublons (s'il y en a !)

    ########################################################################
    print ("\n\n\n")  # saut de trois lignes
    texte = "Requète : lister les doublons : créer une liste des listes de doublons"
    print ("\n\n\n",texte, "\n un doublon d'un enregistrement contient rigoureusement les mêmes données")
     print("La table suivante est redondante :")
    BDD = lireCSV("BDDdbl.csv", False)  # attention cette fois il y a des doublons !
    			
    proposition de corrigé
    N = len (BDD)  # nombres d'enregistrements ou fiches de la table (ou BDD)
    Nitems = len (BDD[0])-1  # nombre de colonnes ou de clefs dans la table
    
    nDoublon = 0
    for  n in range(0,N-1) :
        for  i in range(n+1,N) :
            if BDD[n] == BDD[i] :  # and n != i est inutile car i vaut au minimum n+1
                 print("La fiche ",n, " est le doublon de la fiche ", i)
                nDoublon += 1
                
    def pluriel(n) :  # fonction pour écrire l'accord du pluriel sur un mot
        if n > 0 : return "s"
        else :  return ""
     print("\nIl y a ", nDoublon, " doublon"+ pluriel(nDoublon))
    
     print("\n\nEssayons de ne créer que des listes de doublons différents !")
    
    ########################################################################
    			
  7. Pour continuer, il va falloir éviter de recompter deux fois les mêmes doublons ; pour cela, une liste de liste de doublons sera créée au fur et à mesure que des doublons seront trouvés ; la liste de liste permettra de ne pas enregistrer comme doublon un enregistrement qui est déjà dans une des listes.
    Commencer par créer une fonction booléenne (qui fait un test vrai/faux), qui en entrée reçoit deux arguments : le premier qui est la fiche ou dictionnaire dont on cherche si c'est un doublon ; le second qui est la liste de listes de doublons. Pour s'en sortir, travailler sur papier avec une table très simple comprenant quelques doublons …

    proposition de corrigé
    ########################################################################
    def inListeDeListes( a ,LL): 
        for  i in range(len (LL)) :
            if a in LL[i] : return True
        return False
    
    def chercherListesDoublons(BDD) :
        listesDbl = []  # initialisation de la liste des listes de doublons
        for  n in range(0,N-1) :  # on va comparer les n-1 éléments de la table avec ...
            listeD = [BDD[n]]
            for  i in range(n+1,N) :  # un nombre décroissant d'éléments pour ne pas refaire deux fois la même comparaison
                if BDD[i] in listeD and not inListeDeListes( BDD[i] ,listesDbl) :  # and n != i est inutile car i vaut au minimum n+1
                     print("La fiche ",n, " est le doublon de la fiche ", i)
                    listeD.append(BDD[i])
            if len (listeD) > 1 : 
                listesDbl.append(listeD)
        return listesDbl 
    ########################################################################
                
    listesDbl = chercherListesDoublons(BDD)   
    printTableformateeDeco (listesDbl)    
    
    nDbl = len (listesDbl)
     print("\nIl y a ", nDbl), " doublon" + 	pluriel(nDbl)+" :")
    ########################################################################
    
    n = 1
    for  liste in listesDbl :
         print("\nDoublon n° ", n, " : ", liste, "\nCette liste contient ", 
    			len (liste), " éléments redondants")
        n += 1
    ########################################################################
    			
  8. écrire une fonction qui épure (enlève) les doublons.

    Suggestion : reconstruire une table élément par élément en vérifiant qu'ils ne sont pas dans la liste des listes de doublons renvoyée par la fonction chercherListesDoublons(BDD). Utiliser la fonction logique inListeDeListes( a ,LL) pour tester si un enregistrement se retrouve au delà de une fois dans cette liste de listes.

    proposition de corrigé
    def eliminerDoublons (table) :
        """ eliminerDoublons (table) permet de retourner une table épurée de
        ses doublons	""" 
        listesDbl = chercherListesDoublons(table)
        tableEpuree = []
        for  fiche in table :
             # traduire le test logique qui suit en français vulgarisé
            if not inListeDeListes(fiche ,listesDbl) or (not fiche in tableEpuree and inListeDeListes(fiche ,listesDbl)):
                tableEpuree.append(fiche)
        
        return tableEpuree
       
    tableEpuree = eliminerDoublons (BDD)
    
    P = len (BDD)
    Q = len (tableEpuree)
     print("\nTable épurée des doublons : \n")
    printTableformateeDeco (tableEpuree)
     print( "\nCette table contient ", Q, " éléments, soit ", P - Q, " éléments de moins que la table redondante" )
    					
  9. tri de la table en fonction d'une clef donnée La table BDD est une liste de dictionnaires. Pour une clef donnée , la liste des valeurs correspondantes (à cette clef) sera extraite à partir du parcours de chaque ligne de la table. Puis la méthode sort() sera apliquée à cette liste. Cette liste sera ensuite parcourue pour reconstruire une table triée selon l'ordre croissant des valeurs associées à cette clef. Pour un ordre décroissant, la liste triée sera inversée par le slice listeTriee[::-1] qui inverse en place la liste.
    proposition de corrigé
    ########################################################################
    print ("____________________________________________________________________")
    print ("\n\n\n")  # saut de trois lignes
    texte = "Requète : trier les fiches de la table par ordre croissant des valeurs correspondant à une clef donnée."
    print ("\n\n\n",texte,"\n\nTable non ordonnée :")
    printTableformateeDeco (tableEpuree)
     print("\nTri croissant pour les éléments 'code Postal'. \nRésultat :")
    
    def triTable(table, clef, croissant = True) :
        """ Fonction qui renvoie une table triée en fonction de la clef 
        spécifiée dans l'ordre croissant ou décroissant
        on pourrait vérifier si la clef existe dans table"""
        tab = table.copy() #la copie permettra de ne pas toucher à l'original
    
        listeValeurs = []   # initialisation de la liste des valeurs à trier
        for  fiche in tab :  
            listeValeurs.append(fiche[clef])
        # pour debugguer : print("Liste des valeurs pour la clef : ",clef, " : ", listeValeurs)
     
         # tri de liste
        listeValeurs.sort()    
        # pour debugguer : # print("Liste des valeurs triées pour la clef : ",clef, " : ", listeValeurs)
        
        if not croissant : listeValeurs = listeValeurs[::-1] #inversion
        # pour debugguer : print("Liste inversée des valeurs triées pour la clef : ",clef, " : ", listeValeurs)
        
        tableTriee = []
        nF = len (tab)       # nombre de fiches de la table
        for  i in range(nF) :
            trouve = False
             # on cherche :
            
            for  fiche in tab :
                # print("i = ",i," n = ", n, " sur ", len (tab))
                if fiche[clef] == listeValeurs [i] :   
                    # print("fiche["+clef+"] = " , fiche[clef], "listeValeurs ["+str(i)+"] = ", listeValeurs [i])
                    # print(fiche)
                    tableTriee.append(fiche)
                    tab.remove(fiche)
        return tableTriee
        
    printTableformateeDeco (triTable(tableEpuree,"code Postal", True))
    
     print("\nLe même tri mais dans l'ordre inverse")
    
    printTableformateeDeco (triTable(tableEpuree,"code Postal", False))
    
     print("\nTri par nom croissant :")
    
    printTableformateeDeco (triTable(tableEpuree,"Nom", True))
    
     print("\nTri par 'Dossier num' décroissant :")
    
    printTableformatee (triTable(tableEpuree,'Dossier num', False))