• Aucun résultat trouvé

Filtrages de listes : le schéma filter

7.2 Problèmes sur les listes

7.2.3 Filtrages de listes : le schéma filter

Le principe du schéma filter de transformation de liste est de produire une liste résultat consistant à filtrer les éléments d’une liste initiale en fonction d’un prédicat unaire (fonction à un argument et qui retourne un booléen). On obtient donc la liste de départ avec certains éléments supprimés, ceux pour lesquels le prédicat est faux. La liste filtrée résultat est donc de longueur potentiellement plus courte que la liste de départ.

Plus formellement, soitL une liste de type list[α] de longueur n : [ a0 , a1 , . . . , an−1]

ainsi qu’un prédicatp de signature α -> bool.

L’objectif est de construire la liste également de typelist[α] : [ b0 , b1 , . . . , bm−1] de longueur m ≤ n avec :

— pour chaque bk il existe un unique ai tels que bk= ai et p(ai) vautTrue avec k ≤ i

— et pour tout l > k, on vérifie bl= aj implique i < j.

. . . ouf ! mathématiquement c’est assez difficile à exprimer . . . mais retenons simplement : — tous les bk éléments de la liste résultat sont des ai de la liste de départ tels que p(ai)

vautTrue

— l’ordre séquentiel des éléments dans la liste de départ est préservé Un filtrage possède donc dans le cas général une signature de la forme : list[α] -> list[α]

que l’on peut lire comme : un filtrage d’une liste de α.

7.2.3.1 Exemple 1 : liste des entiers pairs Pour notre premier exemple de filtrage, considérons la spécification suivante :

def liste_pairs(L):

"""list[int] -> list[int]

retourne la liste des entiers pairs éléments de L."""

Ici il s’agit d’un filtrage d’une liste deint. Pour faire apparaître explicitement le prédicat p du schéma général de filtrage, on définit ci-dessous un prédicat permettant de tester la parité d’un entier.

def est_pair(n):

"""int -> bool

renvoie True si l'entier n est pair, False sinon."""

return n % 2 == 0

# Jeu de test

assert est_pair(0) == True assert est_pair(1) == False assert est_pair(2) == True assert est_pair(92) == True assert est_pair(37) == False

Ce prédicat est bien de signatureint -> bool permettant un filtrage d’entiers. On peut donc compléter la définition de la fonction liste_pairs :

def liste_pairs(L):

"""list[int] -> list[int]

Retourne la liste des entiers pairs éléments de L.""" # LR : list[int]

LR = [] # la liste filtrée, initialement vide # n : int (élément courant)

for n in L:

if est_pair(n): LR.append(n)

# sinon on ne fait rien

return LR

# Jeu de tests

assert liste_pairs([232, 111, 424, 92]) == [232, 424, 92] assert liste_pairs([51, 37, 5]) == []

assert liste_pairs([]) == []

En guise d’illustration du filtrage effectuons la simulation de liste_pairs([4, 7, 10, 11, 14]) c’est-à-dire pour L=[4, 7, 10, 11, 14] :

Tour de boucle variablen variable LR

entrée - [] 1er 4 [4] 2e 7 [4] 3e 10 [4, 10] 4e 11 [4, 10] 5e 14 [4, 10, 14] sortie - [4, 10, 14]

Exercice : définir la fonctionliste_impairs retournant, à partir d’une liste L d’entiers naturels en paramètre, la liste des éléments impairs deL.

7.2.3.2 Exemple 2 : liste des supérieurs à un nombre Comme nous avons un peu de pratique maintenant, abordons un problème légèrement plus complexe. Considérons la définition de fonction suivante :

def liste_superieurs(L, x):

"""list[Number] * Number -> list[Number]

renvoie la liste des nombres éléments de L supérieurs au nombre x."""

# LR : list[Number]

LR = [] # la liste résultat initialement vide # y : Number (élément nombre courant)

for y in L: if y > x:

LR.append(y)

# sinon ne rien faire

return LR # Jeu de tests assert liste_superieurs([11, 27, 8, 44, 39, 26], 26) == [27, 44, 39] assert liste_superieurs([11, 26, 8, 4, 9], 26) == [] assert liste_superieurs([11.3, 26.4, 8.9, 4.12, 9.7], 4.11) \ == [11.3, 26.4, 8.9, 4.12, 9.7] assert liste_superieurs([], 0) == []

Exercice : effectuer la simulation deliste_superieurs([11, 27, 8, 44, 39, 26], 26). Avec la définition complète et le jeu de tests ci-dessus, nous comprenons bien le rôle de la fonction : elle filtre dans la liste initialeL les nombres qui sont supérieurs strictement au paramètre x de typeNumber.

On constate sur le jeu de tests que la fonction s’applique tant aux listes d’entiers qu’aux listes de flottants, il s’agit donc d’un filtrage de nombres.

Cependant, la signature n’est pas list[Number] -> list[Number] car la fonction prend en entrée un second argument. C’est donc un filtrage un peu plus complexe mais qui reste bien dans la catégorie filtrage de listes.

7.2.3.2.1 Complément : définitions internes On aimerait, pour illustrer le fait que liste_superieurs est bien une fonction de filtrage, déterminer plus précisément notre prédicat unaire p du schéma général sur cette fonction.

Le problème ici est que la comparaison y > x effectuée dans le corps de la boucle nécessite deux arguments et non un seul. On constate cependant que la valeur du paramètrex est toujours la même dans tout le corps de la fonction, c’est une propriété essentielle des paramètres de fonction.

L’idée est donc de définir le prédicat unaire à l’intérieur de la fonction liste_superieurs : cela s’appelle une définition interne.

Voici une nouvelle définition deliste_superieurs, plus proche du schéma général de filtrage : def liste_superieurs(L, x):

"""list[Number] * Number -> list[Number]

renvoie la liste des nombres éléments de L supérieurs au nombre x."""

# Voici la définition interne :

def superieur_a_x(z):

"""Number -> bool

Renvoie True si z est supérieur à x, False sinon."""

return z > x

# et maintenant le reste du corps de la fonction principale # LR : list[Number]

LR = [] # la liste résultat initialement vide # y : Number (élément nombre courant)

for y in L:

if superieur_a_x(y): LR.append(y)

# sinon ne rien faire

Remarque : en pratique la version avec définition interne est un peu moins concise et on préfèrera la première. Mais elle permet de bien mettre en lumière le schéma de filtrage. Elle illustre de plus une capacité intéressante du langage Python : la possibilité de définir et manipuler des fonctions à l’intérieur des fonctions. Nous reviendrons sur ce point lors des prochains cours (toujours en guise de complément).