Vous retrouverez >ICI< le fichier regroupant l’ensemble des fonctions abordées dans ce chapitre.
Le tableau précédent doit vous rappeler des souvenirs, non ? On y retrouve les pokemons utilisés dans le (4.1).
En fait, puisque les pokemons partagent les mêmes clés. Il semble logique de pouvoir les regrouper dans un tableau.
Sachant qu’un tableau peut être représenté par une liste, il vient que le tableau ci-dessus peut être représenté par la liste suivante où chaque ligne est un dictionnaire qui décrit un pokemon.
pokemon1 = {'Id': 129, 'Name': 'Magikarp','HP': 20, 'DEF': 55, 'SP.DEF': 20,
'Type1': 'Water', 'Type2': ''}
pokemon2 = {'Id': 130, 'Name': 'Gyarados', 'HP': 95, 'DEF': 79, 'SP.DEF': 100,
'Type1': 'Water', 'Type2': 'Flying'}
tableau1 = [pokemon1, pokemon2]
ou plus directement,
tableau1 = [{'Id': 129, 'Name': 'Magikarp', 'HP': 20, 'DEF': 55, 'SP.DEF': 20,
'Type1': 'Water', 'Type2': ''}, {'Id': 130, 'Name': 'Gyarados',
'HP': 95, 'DEF': 79, 'SP.DEF':
100, 'Type1': 'Water', 'Type2':
'Flying'}]
Écrire la représentation en Python de ce second tableau de la même façon que précédemment.
pokemon3 = {'Id': 129, 'ATK': 10, 'SP.ATK': 15, 'SPD': 80} pokemon4 = {'Id': 130, 'ATK': 125, 'SP.ATK': 60, 'SPD': 81} pokemon5 = {'Id': 131, 'ATK': 85, 'SP.ATK': 85, 'SPD': 60} tableau2 = [pokemon3, pokemon4, pokemon5]
ou directement
tableau2 = [{'Id': 129, 'ATK': 10, 'SP.ATK': 15, 'SPD': 80}, {'Id': 130, 'ATK': 125, 'SP.ATK': 60, 'SPD': 81}, {'Id': 131, 'ATK': 85, 'SP.ATK': 85, 'SPD': 60}]
On reconnaît ici le tableau précédent ouvert dans un tableur.
Le format couramment utilisé est le csv (Comma Separated Values autrement dit valeurs séparées par une virgule).
Voyons de plus près le contenu du fichier source tableau1.csv avec un éditeur de texte (le Bloc Note par exemple si vous êtes sur Windows).
Vous devriez pouvoir y lire ce qui suit."Id","Name","HP","DEF","SP.DEF","Type1","Type2"
129,"Magikarp",20,55,20,"Water",
130,"Gyarados",95,79,100,"Water","Flying"
Sur la première ligne, on y trouve les clés. Sur les suivantes, l’ensemble des valeurs. Les données sont séparées par un séparateur, d’où le nom du format. Les séparateurs peuvent être une tabulation, une virgule (ce qui est le cas ici), un point-virgule, …
Remarque: notez la présence de ""
autour des chaînes de caractère et pas
autour des nombres. Par la suite, il faudra en tenir compte notamment lorsqu’on
voudra trier dans un certain ordre les valeurs d’une colonne. Pour rappel,
20<100
(comparaison de deux entiers), mais que "100"<"20"
(la comparaison de
deux chaînes de caractères se fait caractère par caractère en commençant à
gauche).
Écrire à la main le possible contenu du fichier source tableau2.csv qui, ouvert avec un tableur, donne cette image.
"Id","ATK","SP.ATK","SPD" 129,10,15,80 130,125,60,81 131,85,85,60
Cela va faire intervenir:
la lecture d’un fichier (fait dans le (1.5)),
les méthodes des chaînes de caractères:
rstrip()
qui retourne la chaîne de caractères éliminée des espaces
éventuellement présents en fin de lign);split()
qui découpe cette chaîne de caractères suivant le séparateur
précisé pour retourner une liste.Sans plus attendre, voici le code en Python:
def importer_csv(source, separateur=','):
"""
Importer un fichier dans un tableau
:param source: (str) lien pointant vers le fichier en entrée
:param separateur: (str) choix du séparateur
:param table: (list of dict) tableau créé en conservant le type des
données présentes
"""
table = []
with open(source, 'r') as s:
cles = s.readline().rstrip().split(separateur)
ligne = s.readline().rstrip().split(separateur)
while ligne != ['']:
dico = {}
for i in range(len(cles)):
if ligne[i] != '':
dico[eval(cles[i])] = eval(ligne[i])
else:
dico[eval(cles[i])] = ''
table.append(dico)
ligne = s.readline().rstrip().split(separateur)
return table
def importer_csv2(source, separateur=','):
"""
Importer un fichier dans un tableau
:param source: (str) lien pointant vers le fichier en entrée
:param separateur: (str) choix du séparateur
:param table: (list of dict) tableau créé en conservant le type des
données présentes
"""
table = []
with open(source, 'r') as s:
cles = s.readline().rstrip().split(separateur)
ligne = s.readline().rstrip().split(separateur)
while ligne != ['']:
dico = {eval(cles[i]): eval(ligne[i]) if ligne[i] !='' else '' for i in range(len(cles))}
table.append(dico)
ligne = s.readline().rstrip().split(separateur)
return table
Là encore, on utilise des méthodes des chaînes de caractères faisant intervenir inévitablement les listes.
Ci-dessous le code Python pour réaliser cette exportation:
def mettre_en_forme(liste):
"""
Mise en forme de la liste pour écriture dans le fichier csv
de sorte que conserver la nature des données lors de l'exportation
d'un tableau vers un fichier csv
:param liste: (list)
:return nlle_liste: (list)
>>> mettre_en_forme(['abc', 12])
['"abc"', 12]
"""
nlle_liste = []
for element in liste:
if isinstance(element, str):
element = '\"' + str(element) + '\"'
else:
element = str(element)
nlle_liste.append(element)
return nlle_liste
def exporter_csv(table, destination='table.csv', mode='w', separateur=','):
"""
Exporter un tableau en deux dimensions dans un fichier csv (tableur)
:param table: (list of dict) tableau donné
:param destination: (str) lien pointant vers le fichier de sortie
:param mode: (str) 'w' en écriture ou 'a' en écriture par ajout
:param separateur: (str) choix du séparateur
:effet de bord: modifie le fichier de destination
"""
cles = list(table[0].keys()) # liste des clés utilisées
entete = mettre_en_forme(cles)
with open(destination, mode) as dest:
dest.write(separateur.join(entete)+'\n') # écriture de la première
# ligne (entête des colonnes)
for ligne in table: # écriture des lignes suivantes dans le tableau:
# suite des valeurs
lig = mettre_en_forme(ligne.values())
dest.write(separateur.join(lig)+'\n')
a) Importer à l’aide de la fonction
importer_csv()
le fichier csv tableau1.csv obtenu >PRÉCÉDEMMENT< et comparer le tableau ainsi créé autableau1
donné au tout >DÉBUT< de la présente page.
b) Exporter à l’aide de la fonctionexporter_csv()
la tabletableau2
donnée >ICI< vers tableau2.csv, puis vérifier son contenu à la réponse obtenue >LÀ<.
Revenons aux pokemons.
Les tableaux donnés jusque là sont de taille extrêmement modeste en comparaison aux tableaux de données réels tant en nombre de lignes (enregistrements/objets) qu’en nombre de colonnes (champs identifiés par leurs clés).
Dans ce cas, si on dispose de tels tableaux, on pourrait souhaiter:
conserver seulement certaines colonnes;
sélectionner certaines lignes vérifiant certains critères sur les valeurs présentes;
trier les lignes suivant une colonne;
fabriquer un nouveau tableau en effectuant un jointure de deux tableaux ayant une colonne commune.
Toutes ces actions sont appelées requêtes.
a) Écrire sur papier le nouveau tableau obtenu à partir du tableau 1 après conservation des colonnes ayant pour clés
'Name'
,'HP'
,'Type1'
et'Type2'
.
b) Écrire sur papier sa représentation en Python.
nv_tableau1 = [{'Name': 'Magikarp', 'HP': 20, 'Type1': 'Water', 'Type2': ''}, {'Name': 'Gyarados', 'HP': 95, 'Type1': 'Water', 'Type2': 'Flying'}]
Vous avez compris qu’il suffit de recopier le tableau en filtrant les clés
suivant si elles sont ou non dans ['Name', 'HP', 'Type1', 'Type2']
.
Procédons comme suit:
Le filtre (ici un prédicat) utilisé pour répondre au critère souhaité :
def filtre_4col(cle):
"""
Retourne True si la clé donnée est dans la liste donnée
:param cle: (type non mutable)
:param liste: (list) liste de clés
:return: (bool) True si la clé appartient à la liste donnée
"""
return cle in ['Name', 'HP', 'Type1', 'Type2']
filtre_4col = lambda cle: cle in ['Name', 'HP', 'Type1', 'Type2']
La fonction qui construit le nouveau tableau à partir d’un tableau d’origine et du filtre choisi :
def conserver(table, filtre):
"""
Recréer un tableau en conservant les colonnes précisées
:param table: (list of dict) un tableau à deux dimensions
:param filtre: (function) retourne un booléen
:return nlle_table: (list if dict) un tableau regroupant les colonnes
désirées du tableau donné en entrée
"""
nlle_table = []
for ligne in table:
nlle_ligne = {}
for cle in ligne.keys():
if filtre(cle):
nlle_ligne[cle] = ligne[cle]
nlle_table.append(nlle_ligne)
return nlle_table
def conserver2(table, filtre):
"""
Recréer un tableau en conservant les colonnes précisées
:param table: (list of dict) un tableau à deux dimensions
:param filtre: (function) retourne un booléen
:return nlle_table: (list of dict) retourne le nouveau tableau désiré
"""
return [{cle: ligne[cle] for cle in ligne.keys() if filtre(cle)} for ligne in table]
Ne reste plus qu’à appliquer cela au tableau1
.
>>> nv_tableau1 = conserver(tableau1, filtre_4col)
>>> nv_tableau1
[{'Name': 'Magikarp', 'HP': 20, 'Type1': 'Water', 'Type2': ''}, {'Name':
'Gyarados',
'HP': 95,
'Type1':
'Water',
'Type2':
'Flying'}]
a) Écrire sur papier le tableau obtenu à partir du tableau 2 après sélection des lignes vérifiant
'ATK' < 90
.
b) Écrire sur papier sa représentation en Python.
nv_tableau2 = [{'Id': 129, 'ATK': 10, 'SP.ATK': 15, 'SPD': 80}, \ {'Id': 131, 'ATK': 85, 'SP.ATK': 85, 'SPD': 60}]
Cette fois, on veut recopier le tableau en filtrant les lignes
suivant si elles vérifient ou non ligne['ATK'] < 90
.
En vous inspirant des fonctions précédentes, proposer:
a) un prédicatfiltre()
qui permet de savoir si laligne
donnée en paramètre sera sélectionnée ou non ;
b) une fonctionselectionner()
qui prend en paramètres latable
donnée et le prédicatfiltre
à notre disposition et qui retourne unenlle_table
comprenant uniquement les lignes detable
passant lefiltre
.
c) une vérification du nouveau tableau précédent.
def filtre_atk(ligne):
"""
Retourne True si la clé donnée est dans la liste donnée
:param ligne: (dict) dictionnaire décrivant l'objet que la ligne représente
:return: (bool) True si la ligne vérifie le critère désiré
"""
return ligne['ATK'] < 90
filtre_atk = lambda ligne: ligne['ATK'] < 90
def selectionner(table, filtre):
"""
Recréer un tableau en filtrant certaines lignes du tableau donné
:param table: (list of dict) un tableau à deux dimensions
:param filtre: (function) retourne un booléen
:return nlle_table: (list of dict) retourne le nouveau tableau désiré
"""
nlle_table = []
for ligne in table:
if filtre(ligne):
nlle_table.append(ligne)
return nlle_table
def selectionner2(table, filtre):
"""
Recréer un tableau en filtrant certaines lignes du tableau donné
:param table: (list of dict) un tableau à deux dimensions
:param filtre: (function) retourne un booléen
:return: (list of dict) retourne le nouveau tableau désiré
"""
return [ligne for ligne in table if filtre(ligne)]
>>> nv_tableau2 = selectionner(tableau2, filtre_atk)
>>> nv_tableau2
[{'Id': 129, 'ATK': 10, 'SP.ATK': 15, 'SPD': 80}, {'Id': 131, 'ATK': 85,
'SP.ATK': 85, 'SPD': 60}]
a) Proposer un prédicat
doublons()
qui prend en paramètre unetable
et qui retourneTrue
si la table présente plusieurs lignes identiques,False
sinon.
Jeu de tests:>>> table = [{'a': 1, 'b': 3}, {'a': 1, 'b': 4}, {'a': 1, 'b': 3}, {'a': 2, 'b': 2}] >>> doublons(table) True
b) Écrire une fonction
effacer_doublons()
qui prend en paramètre unetable
et qui retourne une nouvelle table constituée des lignes distinctes detable
.
Jeu de tests:>>> table = [{'a': 1, 'b': 3}, {'a': 1, 'b': 4}, {'a': 1, 'b': 3}, {'a': 2, 'b': 2}] >>> nlle_table = effacer_doublons(table) >>> nlle_table [{'a': 1, 'b': 3}, {'a': 1, 'b': 4}, {'a': 2, 'b': 2}]
c) Écrire une fonction
effacer_finement()
qui prend en paramètres unetable
et unecle
et qui retourne une nouvelle table constituée des lignes dont les valeurs associée à cette clé sont toutes différentes.
Jeu de tests:>>> table = [{'a': 1, 'b': 3}, {'a': 1, 'b': 4}, {'a': 2, 'b': 2}, {'a': 1, 'b': 3}] >>> nlle_table = effacer_finement(table, 'a') >>> nlle_table [{'a': 1, 'b': 3}, {'a': 2, 'b': 2}]
Écrire sur papier le tableau obtenu à partir du tableau 2 après l’avoir trié dans l’ordre croissant de la vitesse
'SPD'
.
nv_tableau2 = [{'Id': 131, 'ATK': 85, 'SP.ATK': 85, 'SPD': 60}, {'Id': 129, 'ATK': 10, 'SP.ATK': 15, 'SPD': 80}, {'Id': 130, 'ATK': 125, 'SP.ATK': 60, 'SPD': 81}]
On pourrait faire appel aux fonctions tris étudiés dans le
(3.4), mais Python fournit une fonction tri
bien plus rapide sorted()
qui admet 3 paramètres la table
que l’on cherche à
trier, le paramètre key
(le filtre désignant sur quelle colonne
s’effectue le tri) et le paramètre optionnel reverse
(un booléen: True
si
vous souhaitez effectuer un tri dans l’ordre décroissant) et qui retourne la
table triée désirée.
Le filtre a utilisé:
def filtre_ordre(ligne):
"""
Retourne True si la clé donnée est dans la liste donnée
:param ligne: (dict) dictionnaire décrivant l'objet que la ligne représente
:return: (bool) True si la ligne vérifie le critère désiré
"""
return ligne['SPD']
filtre_ordre = lambda ligne: ligne['SPD']
Puis, la fonction principale
def trier(table, filtre, croissant=True):
"""
Trier la table donné selon la clé choisie par le filtre
:param table: (list of dict) un tableau à deux dimensions
:param filtre: (function) retourne la valeur à considérer pour le tri
Exemple: filtre = lambda ligne: ligne['HP']
"""
return sorted(table, key=filtre, reverse=not(croissant))
Application au tableau2
:
>>> nv_tableau2 = trier(tableau2, filtre_ordre, True)
>>> nv_tableau2
[{'Id': 131, 'ATK': 85, 'SP.ATK': 85, 'SPD': 60}, {'Id': 129, 'ATK': 10,
'SP.ATK': 15, 'SPD': 80}, {'Id': 130, 'ATK': 125, 'SP.ATK': 60, 'SPD': 81}]
À partir des 2 tableaux précédents, recopier et compléter le tableau suivant.
Vous l’aurez compris qu’en fait ces deux tableaux partagent une même clé
'Id'
.
Il vient que créer le tableau souhaité consiste à créer une liste de dictionnaires dont les clés sont obtenues par la concaténations des clés des deux tableaux (sans répétition de la clé commune) et les valeurs sont celles associées dans chacun des tableaux (identiques pour la clé commune).
def joindre(table1, table2, cle1, cle2=None):
"""
Faire la jonction entre table1 et table2 se faisant suivant les clés
précisées dans chacune des tables
:param table1: (list of dict) un tableau à deux dimensions
:param table2: (list of dict) un tableau à deux dimensions
:param cle1: (str) clé choisi dans la table1
:param cle2: (str) clé choisi dans la table2
Exemple: filtre = lambda ligne: ligne['HP']
"""
nlle_table = []
descripteurs1, descripteurs2 = table1[0].keys(), table2[0].keys()
if cle2 == None:
cle2 = cle1
if cle1 in descripteurs1 and cle2 in descripteurs2:
for ligne1 in table1:
for ligne2 in table2:
if ligne1[cle1] == ligne2[cle2]:
nlle_ligne = dict(list(ligne1.items()) + list(ligne2.items()))
nlle_table.append(nlle_ligne)
return nlle_table
def joindre2(table1, table2, cle1, cle2=None):
"""
Faire la jonction entre table1 et table2 se faisant suivant les clés
précisées dans chacune des tables
:param table1: (list of dict) un tableau à deux dimensions
:param table2: (list of dict) un tableau à deux dimensions
:param cle1: (str) clé choisi dans la table1
:param cle2: (str) clé choisi dans la table2
Exemple: filtre = lambda ligne: ligne['HP']
"""
nlle_table = []
descripteurs1, descripteurs2 = table1[0].keys(), table2[0].keys()
if cle2 == None:
cle2 = cle1
if cle1 in descripteurs1 and cle2 in descripteurs2:
for ligne1 in table1:
for ligne2 in table2:
if ligne1[cle1] == ligne2[cle2]:
ligne1.update(ligne2)
nlle_table.append(ligne1)
return nlle_table
Application aux tableau1
et tableau2
suivant la clé 'Id'
:
>>> tableau3 = joindre(tableau1, tableau2, 'Id')
>>> tableau3
[{'Id': 129, 'Name': 'Magikarp', 'HP': 20, 'DEF': 55, 'SP.DEF': 20, 'Type1':
'Water', 'Type2': '', 'ATK': 10, 'SP.ATK': 15, 'SPD': 80}, {'Id': 130, 'Name':
'Gyarados', 'HP': 95, 'DEF': 79, 'SP.DEF': 100, 'Type1': 'Water', 'Type2':
'Flying', 'ATK': 125, 'SP.ATK': 60, 'SPD': 81}]