Trier une table
sorted() et .sort()
Pour trier en ordre croissant de façon simple et facile, le type list offre une méthode .sort() qui permet de modifier un tableau en le triant.
L'instruction tab.sort() a modifié le tableau tab et a renvoyé None.
Dans cet exemple, on a trié un tableau de nombres entiers. On peut faire la même chose avec un tableau de nombres décimaux (float) ou de chaines de caractères. Les chaines de caractères sont triées par ordre lexicographique1.
>>> tab = ['pomme', 'banane', 'orange', 'fraise']
>>> tab.sort()
>>> tab
['banane', 'fraise', 'orange', 'pomme']
Noter que .sort() est une méthode réservée aux tableaux, c'est-à-dire aux variables de type list. Elle ne s'applique pas aux p-uplets ni aux dictionnaires :
>>> puplet = (5, 2, 3, 1, 4)
>>> puplet.sort()
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'sort'
>>> dico = {1: 'D', 2: 'B', 3: 'B', 4: 'E', 5: 'A'}
>>> dico.sort()
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'sort'
Au contraire de .sort(), la fonction sorted() accepte n'importe quel itérable et renvoie un nouveau tableau trié :
>>> sorted((5, 2, 3, 1, 4))
[1, 2, 3, 4, 5]
>>> sorted([5, 2, 3, 1, 4])
[1, 2, 3, 4, 5]
>>> sorted({'un':1, 'deux':2, 'trois':3 })
['deux', 'trois', 'un']
La fonction sorted() renvoie toujours un tableau, même pour trier un p-uplet ou un dictionnaire. Et dans le cas d'un dictionnaire, ce sont les clés qui sont triées et renvoyées.
Attention pour les tableaux, à la différence de
.sort(), le tableau trié n'est pas modifié, c'est un nouveau tableau trié qui renvoyé par sorted() :
>>> tab = [5, 2, 3, 1, 4]
>>> sorted(tab)
[1, 2, 3, 4, 5]
>>> tab
[5, 2, 3, 1, 4]
>>> tab = [5, 2, 3, 1, 4]
>>> tab_trie = sorted(tab)
>>> tab
[5, 2, 3, 1, 4]
>>> tab_trie
[1, 2, 3, 4, 5]
On peut trier sur des tableaux de tableaux ou tableaux de p-uplets, dans ce cas le tri est fait par ordre des éléments. Par exemple, pour trier notre tableau de tableaux pays.
>>> pays
[['France', 'Paris', '68'],
['Espagne', 'Madrid', '48'],
['Italie', 'Rome', '60']]
>>> sorted(pays)
['Espagne', 'Madrid', '48'], 'France', 'Paris', '68'], ['Italie', 'Rome', '60']]]
Ici, le tri s'est effectué en comparant les premières valeurs de chaque sous-tableau : 'Espagne' < 'France'< 'Italie'.
Par contre, on ne peut pas trier un tableau de dictionnaires :
>>> pays = [{'Capitale': 'Paris', 'Pays': 'France', 'Population (ml)': '68'},
{'Capitale': 'Madrid', 'Pays': 'Espagne', 'Population (ml)': '48'},
{'Capitale': 'Rome', 'Pays': 'Italie', 'Population (ml)': '60'}]
>>> sorted(pays)
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
TypeError: '<' not supported between instances of 'dict' and 'dict'
Paramètre key
.sort() et sorted() acceptent un paramètre nommé key permettant de spécifier une fonction à appeler sur chaque élément du tableau afin d'effectuer des comparaisons.
Par exemple, on peut modifier l'ordre de tri d'un tableau de nombres au format str :
en précisant que les données doivent être converties en entier par la fonction int() avant d'être triées :
De la même façon, le paramètre key permet de trier des données en table en précisant les colonnes selon lesquelles on veut trier. Par exemple, si on veut trier le tableau de tableaux des pays selon leur population :
On peut écrire une fonction popul qui renvoie le champs population de chaque pays converti en nombre entier :
et qui sert de clé à la fonction sorted() :
>>> sorted(pays, key=popul)
[['Espagne', 'Madrid', '48'], ['Italie', 'Rome', '60'], ['France', 'Paris', '68']]
Ou bien l'écrire directement dans une fonction lambda :
>>> sorted(pays, key=lambda x:int(x[2]))
[['Espagne', 'Madrid', '48'], ['Italie', 'Rome', '60'], ['France', 'Paris', '68']]
De la même façon, une fonction lambda va permettre de trier le tableau de dictionnaires en ordre croissant de population :
>>> pays = [{'Capitale': 'Paris', 'Pays': 'France', 'Population (ml)': '68'}, \
{'Capitale': 'Madrid', 'Pays': 'Espagne', 'Population (ml)': '48'}, \
{'Capitale': 'Rome', 'Pays': 'Italie', 'Population (ml)': '60'}]
>>> sorted(pays, key=lambda x:int(x['Population (ml)']))
[{'Capitale': 'Madrid', 'Pays': 'Espagne', 'Population (ml)': '48'}, {'Capitale': 'Rome', 'Pays': 'Italie', 'Population (ml)': '60'}, {'Capitale': 'Paris', 'Pays': 'France', 'Population (ml)': '68'}]
Paramètre reverse
.sort() et sorted() acceptent aussi un paramètre nommé reverse avec une valeur booléenne. Par défaut, reverse est False, c'est-à-dire qu'on tri en ordre croissant, mais on peut le changer pour indiquer un ordre décroissant des tris.
Par exemple, pour avoir les pays dans par population décroissante :
>>> sorted(pays, key=lambda x:int(x[2]), reverse=True)
[['France', 'Paris', '68'], ['Italie', 'Rome', '60'], ['Espagne', 'Madrid', '48']]
Exercice corrigé
On a importé un tableau de dictionnaires des codes postaux avec :
with open('laposte_hexasmal.csv', 'r', encoding='utf-8-sig') as f:
codes = list(csv.DictReader(f, delimiter=';'))
-
Écrire les fonctions
plus_petit_codeetplus_grand_codequi renvoient la commune qui a le plus petit code postal et celle qui a le plus grand code postal. -
Écrire les fonctions
plus_grande_latitudequi renvoie la commune qui a la plus grande latitude.
Réponse
def plus_petit_code():
return sorted(codes, key = lambda x:x['code_postal'])[0]
def plus_grand_code():
return sorted(codes, key = lambda x:x['code_postal'])[-1]
def gps(gps_str):
""" str -> float, float
Renvoie la longitude et latitude d'une chaine de caractère de coordonnées gps
"""
long, lat = gps_str.split(',')
return float(long), float(lat)
def plus_grande_latitude():
# filtre les communes dont les coordonnées gps sont données
codes_avec_gps = list(filter(lambda x:x['coordonnees_gps'] !='',codes))
return sorted(codes_avec_gps, key = lambda x:gps(x['coordonnees_gps'])[1], reverse= True)[0]
Exercice corrigé - Pour aller plus loin
- Écrire les fonctions
plus_loin(longA, latA)qui renvoie la commune la plus éloignée du point GPS de coordonnées (longA, latA).
Exemple:
>>> plus_loin(0,0)
{'code_commune_insee': '98612',
'code_postal': '98620',
'coordonnees_gps': '-14.270411199, -178.155263035',
'libelle_d_acheminement': 'SIGAVE',
'ligne_5': '',
'nom_de_la_commune': 'SIGAVE'}
Note : La distance en mètres entre les points de coordonnées (\(Long_A\);\(Lat_A\)) et (\(Long_B\); \(Lat_B\) ) est donnée par la formule de Pythagore :
-
\(x =({Long_B - Long_A )} \times {cos{ {Lat_A + Lat_B} \over 2}}\)
-
\(y = Lat_B-Lat_A\)
-
\(z = \sqrt {x^2+y^2}\)
-
\(d = 1852 \times 60 \times z\)
Source : http://villemin.gerard.free.fr/aGeograp/Distance.htm
Réponse
from math import cos, sqrt
def distance(longA, latA, longB, latB):
x = (longB - longA) * cos((latA+latB)/2)
y = latB - latA
z = sqrt(x**2 + y**2)
d = 1852 * 60 * z
return d
def plus_loin(longA, latA):
# filtre les communes dont les coordonnées gps sont données
codes_avec_gps = list(filter(lambda x:x['coordonnees_gps'] !='',codes))
return sorted(codes_avec_gps, key = lambda x:distance(longA, latA, gps(x['coordonnees_gps'])[0], gps(x['coordonnees_gps'])[1]), reverse= True)[0]
-
On commence par comparer les codes Unicode du premier caractère de chaque chaîne, puis en cas d'égalité le second caractère, et ainsi de suite comme dans un dictionnaire. Attention aux majuscules et aux nombres, '11' est plus petit que '2' ! ↩