Vous êtes sur la page 1sur 34

1

U NIVERSITÉ DE D OUALA
FACULTÉ DES S CIENCES

Département de Mathématiques et
Informatique

Licence 2

Informatique

INF274

Programmation fonctionnelle
II

Rodrigue Aimé DJEUMEN D.

Version 1.0, avril 2020


INF274 : Programmation
fonctionnelle II

Timing
— Total horaire : 30h
— Cours magistral (CM) : 16h
— Travaux Dirigés (TD) : 8h
— Travaux Pratiques (TP) : 6h
— Crédits : 3

Objectifs
Ce cours s’inscrit dans la suite du cours d’introduction à la pro-
grammation fonctionnelle, vu en première année. La notion d’ap-
plication développée en Haskell y est abordée et l’accent est surtout
mis sur les structures de données, les algorithmes qui vont avec et
une petite introduction à la théorie de la programmation.

Documentation
1. Introduction to Functional Programming using Haskell. Ri-
chard Bird, Prentice Hall ; 2 edition (May 9, 1998), 448 pages.
2. Paul Hudak and Joseph H. Fasel. 1992. A gentle introduction
to Haskell. SIGPLAN Not. 27, 5 (May 1992), 1–52.
DOI :https ://doi.org/10.1145/130697.130698.

2
3

3. Apprendre Haskell vous fera le plus grand bien ! Miran Lipo-


vača, https ://haskell.developpez.com/tutoriels/apprendre-
haskell/
4. https ://wiki.haskell.org/
Table des matières

1 Les Listes 6
1.1 Notations . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2 Opération sur les listes . . . . . . . . . . . . . . . . . . 6
1.2.1 Ajout d’un élément . . . . . . . . . . . . . . . . . 6
1.2.2 Tête et queue . . . . . . . . . . . . . . . . . . . . 6
1.2.3 Concaténation . . . . . . . . . . . . . . . . . . . 7
1.2.4 Fusion d’un ensemble listes . . . . . . . . . . . 7
1.2.5 Inverser une liste . . . . . . . . . . . . . . . . . . 8
1.2.6 Longueur d’une liste . . . . . . . . . . . . . . . . 8
1.2.7 Indexation . . . . . . . . . . . . . . . . . . . . . . 8
1.2.8 Foncteur pour une liste . . . . . . . . . . . . . . 9
1.2.9 Filtrage d’une liste par prédicat . . . . . . . . . 9
1.3 Liste en compréhension . . . . . . . . . . . . . . . . . . 10
1.4 La fonction Zip . . . . . . . . . . . . . . . . . . . . . . . 10
1.5 Les folds . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2 Programmation modulaire et opérations d’entrées/sorties


11
2.1 Notion de module . . . . . . . . . . . . . . . . . . . . . 11
2.1.1 Définitions . . . . . . . . . . . . . . . . . . . . . 11
2.1.2 Exemples de module . . . . . . . . . . . . . . . . 11
2.1.3 Importation de module . . . . . . . . . . . . . . 12
2.1.4 Exportation des éléments d’un module . . . . . 14
2.2 Bloc Principal . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2.1 Le bloc do . . . . . . . . . . . . . . . . . . . . . . 14
2.2.2 La fonction main . . . . . . . . . . . . . . . . . . 15
2.3 Entrées/Sorties de base . . . . . . . . . . . . . . . . . . 15
2.3.1 Fonctions de sorties . . . . . . . . . . . . . . . . 15

4
TABLE DES MATIÈRES 5

2.3.2 Fonctions d’entrée . . . . . . . . . . . . . . . . . 16


2.3.3 Gestion des fichiers . . . . . . . . . . . . . . . . 16
2.3.4 Lecture/écriture des valeurs numériques . . . 16

3 Types de données algébriques 18


3.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.2 Syntaxe et applications . . . . . . . . . . . . . . . . . . 18
3.2.1 Le cas du type Bool . . . . . . . . . . . . . . . . 18
3.2.2 Le cas du type shape . . . . . . . . . . . . . . . 19
3.3 Affichage d’un TAD . . . . . . . . . . . . . . . . . . . . 19
3.4 Forme générique d’un TAD . . . . . . . . . . . . . . . . 20
3.5 Structure d’enregistrement . . . . . . . . . . . . . . . . 20
3.6 Structures de données inductives . . . . . . . . . . . . 21

4 Les structures d’arbres 26


4.1 Arbres binaires . . . . . . . . . . . . . . . . . . . . . . . 26
4.1.1 Représentation . . . . . . . . . . . . . . . . . . . 26
4.1.2 Opérations sur les arbres binaires . . . . . . . . 27
4.2 Arbres binaires de recherche . . . . . . . . . . . . . . . 28
4.2.1 Structure de donnée . . . . . . . . . . . . . . . . 29
4.2.2 Recherche d’une valeur . . . . . . . . . . . . . . 29
4.2.3 Hauteur et taille . . . . . . . . . . . . . . . . . . 30
4.2.4 Insertion, ajout et suppression . . . . . . . . . . 30
Chapitre 1

Les Listes

1.1 Notations
Une liste est une collection d’éléments de même type ; c’est une
structure de donnée se décrivant de manière récursive. Une liste se
présente en Haskell entre crochets, les éléments étant séparés par
des virgules. La liste vide est dénotée par [ ] ; si les éléments d’une
liste sont de type a, alors la liste aura pour type [a]. Exemple :
[1, 2, 3] :: [Int]
[0 h0 ,0 a0 ,0 l0 ,0 l0 ,0 o0 ] :: [Char]
[[1, 2], [3]] :: [[Int]]
[(+), (∗)] :: [Int → Int → Int]

1.2 Opération sur les listes


1.2.1 Ajout d’un élément
L’ajout d’un élément dans une liste se fait grâce à l’opérateur
(:) :: a → [a] → [a], associatif à droite. Ainsi [1, 2, 3] = 1 : (2 : (3 : [])) =
1 : 2 : 3 : [].

1.2.2 Tête et queue


Dans une liste, le premier élément de la liste est appelé là tête
de liste (head), tandis que le reste de la liste est considérée comme

6
CHAPITRE 1. LES LISTES 7

étant sa queue (tail).

head :: [a] → a tail :: [a] → [a]


head(x:xs) = x tail(x:xs) = xs

1.2.3 Concaténation
La concaténation est l’opération permettant d’obtenir une liste
résultante, à partir de deux listes. Elle définie par l’opérateur (++) ::
[a] → [a] → [a].
[1, 2, 3] + +[4, 5] = 1 : ([2, 3] + +[4, 5])
= 1 : (2 : ([3] + +[4, 5]))
= 1 : (2 : ([3] + +[4, 5]))
= 1 : (2 : (3 : ([] + +[4, 5])))
= 1 : (2 : (3 : ([4, 5])))
= 1 : 2 : 3 : [4, 5]
Sa définition formelle est

(++) : : [a] → [a] →[a]


[] ++ ys = ys
(x:xs) ++ ys = x:(xs ++ ys)

La concaténation est une opération associative


(xs + +ys) + +zs = xs + +(ys + +zs)
xs + +[] = [] + +xs = xs

1.2.4 Fusion d’un ensemble listes


concat :: [[a]] → [a] est un opérateur qui concatène un ensemble
de liste afin d’obtenir une nouvelle liste résultante. Sa définition
est la suivante :

concat : : [[a]]→[a]
concat [] = []
concat (xs:xss) = xs ++ concat xss
CHAPITRE 1. LES LISTES 8

concat[[1, 2], [], [3, 2, 1]] = concat ([1, 2] : [[], [3, 2, 1]])
= [1, 2] + +concat ([] : [[3, 2, 1]])
= [1, 2] + +[] + +concat ([3, 2, 1] : [])
= [1, 2] + +[] + +[3, 2, 1] + +concat []
= [1, 2] + +[] + +[3, 2, 1] + +[]
= [1, 2, 3, 2, 1] déf inition de + +

1.2.5 Inverser une liste


Cette opération consiste à prendre une liste et la retourner dans
l’ordre inverse de ses éléments. Elle est définie par :

reverse::[a] → [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]

1.2.6 Longueur d’une liste


Déterminer la longueur d’une liste, revient à compter le nombre
d’éléments dans la dite liste.

length::[a] → Int
length [] = 0
length (x:xs) = 1 + length xs

Remarque 1. length(xs++ys) = length xs + length ys

1.2.7 Indexation
Les éléments d’une liste peuvent être indexés par des entiers
correspondant à leur positions dans la liste (dans un syntaxe proche
de celle des tableaux dans les langages comme C, pascal, ...). Il
devient de ce fait possible d’accéder à un élément à une position
donnée par l’opérateur d’indexation ”!!”. Ainsi
CHAPITRE 1. LES LISTES 9

[1, 2, 3, 4]!!0 = 1
[1, 2, 3, 4]!!3 = 3

(!!) :: [a] → Int → a


(x:xs) !! 0 = x
(x:xs) !! (n+1) = xs !! n

1.2.8 Foncteur pour une liste


Un foncteur est une fonction d’ordre supérieur (map), qui tout
en préservant la structure d’une liste, permet le mappage entre les
objets de la liste initiale et ceux de la liste résultante. Exemple :

map square [1, 2, 3] = [1, 4, 9]


map (< 3) [1, 2, 3] = [T rue, T rue, F alse]
map nextLetter ”HAL” = ”IBM ”

La définition de la fonction map est la suivante :

map :: (a → b) → [a] → [b]


map f [] = []
map f (x:xs) = f x : map f xs

1.2.9 Filtrage d’une liste par prédicat


La fonction f ilter prend en arguments, une fonction booléenne
p (prédicat) et une liste xs, pour retourner une sous-liste de xs dont
les éléments satisfont p. Sa définition est donnée par :

filter :: (a → Bool) → [a] → [a]


filter p [] = []
filter (x:xs) =
if p x then x : filter p xs else filter p xs
CHAPITRE 1. LES LISTES 10

Exercices 4.1 :
1. Écrire la fonction even :: Int → Bool qui dit si un nombre
est pair ou pas.
2. Évaluer les expressions suivantes en montrant tous les dé-
tails :
(a) reverse [1,2,3,4]
(b) [1,2,3,4] ! !2
(c) map square [1,2,3,4]
(d) filter even [1,2,3,4,10,17,19,32]

1.3 Liste en compréhension


Une liste en compréhension a la forme [e|Q] où e est une expres-
sion et Q un sélecteur. Q est une séquence potentiellement vide
de générateurs et de prédicats, séparés par des virgules. Un géné-
rateur a la forme x ← xs où x est soit une variable, soit un tuple,
tandis que xs est une liste valuée d’expressions. Par exemple, consi-
dérons l’expression ci-dessous :

[x ∗ x | x ← [1..5], odd x]

Une telle expression, équivaut à la liste [1, 9, 25], puisque les élé-
ments de liste sont les carrés des nombres impairs compris entre 1
et 5.

1.4 La fonction Zip

1.5 Les folds


Chapitre 2

Programmation modulaire et
opérations d’entrées/sorties

2.1 Notion de module


2.1.1 Définitions
La programmation modulaire est réalisée en haskell, à l’aide de
modules. Un module est contenu dans un fichier portant le même
nom (en plus de l’extension hs) que le module défini.
Un module haskell est une collection de fonctions, types et
classes de types en rapport les uns avec les autres. une applica-
tion haskell est une collection de modules, où le module principal
charge les autres modules et utilise les fonctions et données qu’ils
définissent. Un module peut importer ou exporter ses structures
de données et ses fonctions.

2.1.2 Exemples de module


Soit le module suivant, associé au TAD List enregistré dans le
fichier List.hs

11
CHAPITRE 2. PROGRAMMATION MODULAIRE ET OPÉRATIONS D’ENTRÉES/SORTIE

module List where


data List a = Nil | Cons a (List a)
deriving (Show)
len :: List a → Int
...
tete :: List a → a
...
queue :: List a → List a
...
conc :: List a → List a → List a
...
rev :: List a → List a
...
lmap :: (a → b) → List a → List b
...

Comme deuxième exemple, considérons un module associé aux


nombres rationnels, enregistré dans le fichier Rationnel.hs.

module Rationnel where


type Rationnel = (Int, Int)
pgcd :: Int → Int → Int
...
simplify :: Rationnel → Rationnel
...
qAdd, qSub :: Rationnel → Rationnel → Rationnel
...
qMul, qDiv :: Rationnel → Rationnel → Rationnel
....

2.1.3 Importation de module


La syntaxe d’importation de module est

import N om_module
CHAPITRE 2. PROGRAMMATION MODULAIRE ET OPÉRATIONS D’ENTRÉES/SORTIE

Cette instruction met à la disposition du module courant (ce-


lui qui importe), toutes les données et fonctions exportées par le
module importé.
En application, considérons un module qui devrait manipuler
une liste de nombres rationnels. l’implémentation d’un tel module
devrait pouvoir réutiliser les modules List et Rationnel 1 précédents,
et se présente comme suit :

module ListRationnel where


import List
import Rationnel
type ListRationnel = List Rationnel
som :: ListRationnel → ListRationnel
som Nil = (0,1)
som (Cons x xs) = qAdd x (som xs)
totalNbre :: ListRationnel → Int
totalNbre xs = len xs

On remarque que instanciation List Rationnel est une spéciali-


sation de List a, les fonctions qAdd et len sont celles définies dans
les modules Rationnel et List respectivement.
Cependant, une importation de modules peut provoquer des col-
lisions de noms, avec ceux des objets locaux ; pour éviter ce cas de
figure, on peut faire usage des imports qualifiés, suivant la syntaxe
import qualif ied nom_module. Par exemple

module ListRationnel where


import qualified List
...
totalNbre :: ListRationnel → Int
totalNbre xs = List.len xs

On constate que l’appel de la fonction len est préfixée par le nom


1. Tous les fichiers associés aux modules devant se trouver dans le même
dossier.
CHAPITRE 2. PROGRAMMATION MODULAIRE ET OPÉRATIONS D’ENTRÉES/SORTIE

du module List. Il est également possible d’importer un module en


le renommant dans le module courant, la syntaxe étant
import qualif ied nom as nouveau_nom

2.1.4 Exportation des éléments d’un module


Par défaut, tous les éléments d’un module sont exportés ; cepen-
dant la possibilité est offerte de choisir explicitement les éléments
à exporter dans un module, en créant une liste d’exports à insé-
rer juste après le nom du module, comme dans le cas du module
Rationnel précédent :

module Rationnel(Rationnel, pgcd, simplify,qSub) where


...

Seul les objets Rationnel, pgcd, simplif y, et qSub seront acces-


sibles lors de l’importation du module Rationnel.

2.2 Bloc Principal


2.2.1 Le bloc do
Le bloc do peut être employé pour séquencer opérations, comme
dans le cas des affichages suivants :

do putStr ”Hello”
putStr ” ”
putStr ”World”
putStr ”\n”

do attribue un nom de variable à la valeur passée en utilisant le


symbole ” ← ”
do x1 ← action1
x2 ← action2
action3 x1 x2
CHAPITRE 2. PROGRAMMATION MODULAIRE ET OPÉRATIONS D’ENTRÉES/SORTIE

x1 et x2 sont les résultats de action1 et action2 . x1 (resp. x2 ) sera


lié au type du résultat de action1 (resp. action2 ).
Un bloc do peut retourner un résultat

do test ← getLine
return text

2.2.2 La fonction main


La fonction main est définie comme point d’entrée d’un pro-
gramme Haskell (exactement comme en C) ; il est généralement de
type IO().

main::IO()
main = do c ← getChar
putChar c

La signature de la fonction main peut être omise

2.3 Entrées/Sorties de base


2.3.1 Fonctions de sorties
Signatures de quelques fonctions d’affichage sur la sortie stan-
dard (écran)

putChar :: Char → IO()


putStr :: Char → IO()
putStrLn :: Char → IO()
print :: Show a ⇒ a → IO()

main = do putStrLn ”Hello World”


CHAPITRE 2. PROGRAMMATION MODULAIRE ET OPÉRATIONS D’ENTRÉES/SORTIE

2.3.2 Fonctions d’entrée


Signatures de quelques fonctions de lecture sur l’entrée stan-
dard (clavier)

getChar :: IO Char
getLine :: IO String
getContents :: IO String

getContents retourne toutes les entrées utilisateur sous la forme


d’une seule chaîne de caractères.

2.3.3 Gestion des fichiers


Les fonctions ci-dessous s’appliquent sur les fichiers textes

writeF ile :: String → String → IO()


appendF ile :: String → String → IO()
readF ile :: String → IO String

writeF ile et appendF ile prennent en argument un nom de fichier


et une chaîne de caractère, qui sera écrite dan ledit fichier. writeF ile
écrase le contenu du fichier s’il existe. Dans le cas des données
autres que chaîne, la fonction show sera utilisée pour convertir ces
données en chaîne avant écriture.

main=appendFile ”square.dat” (show [(x,x*x) | x←[0 ..10]])

2.3.4 Lecture/écriture des valeurs numériques


Les primitives d’entrée/sortie vues jusqu’ici, ne manipulent que
les chaînes de caractère. Il est donc logique de se demander com-
ment faire les entrées/sorties avec les valeurs numériques.
— Lire une donnée numérique, revient à lire une chaîne de ca-
ractère, puis la parser pour la convertir en valeur numérique
CHAPITRE 2. PROGRAMMATION MODULAIRE ET OPÉRATIONS D’ENTRÉES/SORTIE

putStrLn ”Entrer un nombre entier”


input ← getLine
let a = read input::Int

— Afficher une valeur numérique, revient à la convertir en une


chaîne de caractère avent affichage. La fonction print est idéale,
parce qu’elle implémente la fonction show pour les valeurs
numériques.

main = do
putStrLn ”Entrer un nombre entier”
input1 ← getLine
let a = read input1::Int
putStrLn ”Entrer un nombre entier”
input2 ← getLine
let b = read input2::Int
print(pgcd a b)
Chapitre 3

Types de données algébriques

3.1 Généralités
Un type abstrait de données (ou TAD ou Abstract DataType),
est un type de donnée dont chacune des valeurs est une donnée
d’un autre type, enveloppée dans un constructeur de type. Toutes
les données enveloppées sont les arguments du constructeur.
Le constructeur ici n’est pas une fonction susceptible d’être éva-
luée, la seule manière d’opérer sur les données, est d’appliquer le
mécanisme de filtrage de motifs (pattern matching), pour enlever le
constructeur.

3.2 Syntaxe et applications


3.2.1 Le cas du type Bool
Considérons la déclaration suivante :
data Bool = False | True
data est un mot clé indiquant la création d’un nouveau type, F alse
et T rue sont les constructeurs de ce type, avec les signatures :

F alse F alse::Bool T rue T rue::Bool

18
CHAPITRE 3. TYPES DE DONNÉES ALGÉBRIQUES 19

F alse et T rue n’ont pas besoin de d’arguments pour construire


des données. Les noms des constructeurs commençant par la ma-
juscule.

3.2.2 Le cas du type shape


Considérons la déclaration suivante :
data Shape = Circle Float | Rectangle Float Float
Un type Shape ici est défini soit comme un cercle (Circle), soit comme
un rectangle (Rectangle) avec comme signatures :

r::F loat larg::F loat long::F loat


Circle Circle r::Shape
Rectangle Rectangle larg long::Shape

Il devient possible d’écrire des fonctions manipulant ces struc-


tures de données. Supposons que l’on veuille calculer la surface
d’un Shape. La fonction area associée s’écrira

area :: Shape → Float


area (Circle r) = pi * r^2
area (Rectangle larg long) = larg * long

On remarque que dans son écriture, la fonction area traite le cas


de chaque forme géométrique individuellement (filtrage de motif) ;
pi est une fonction constante prédéfinie. Une telle fonction s’évalue
comme toute fonction vue jusqu’ici. Soit à évaluer :

area (Circle 2.0) = pi ∗ 2.0ˆ2


= 3.14... ∗ 4.0
= 12.566...

L’argument de area en occurrence Circle 2.0, est appelé un terme


ou valeur du type Shape.

3.3 Affichage d’un TAD


Un appel en ligne de commande du genre
CHAPITRE 3. TYPES DE DONNÉES ALGÉBRIQUES 20

*Main> Circle 2.0


déclenche une erreur du fait que l’affichage des termes du type
Shape n’est pas définie. La définition de l’affichage d’un terme se
fait par dérivation de la classe de type Show, à l’aide de l’instruction
deriving (Show). Ainsi la redéfinition du type Shape précédent, sera :
data Shape = Circle Float | Rectangle Float Float deri-
ving (Show)
Il est toute fois à remarquer qu’un TAD peut dériver n’importe
quelle instance de classe préalablement définie.

3.4 Forme générique d’un TAD


Un TAD est paramétrable à l’aide de types génériques ; on ob-
tient ainsi un TAD générique ou polymorphe. Soit à redéfinir le type
Shape précédent :
data Shape a = Circle a | Rectangle a a deriving (Show)
Dans ce contexte, une instanciation du genre Shape F loat corres-
pond à la définition vue dans la section précédente ; c’est également
une spécialisation de la structure de donnée Shape a ci-dessus,
pour manipuler les données de type F loat. La fonction area sera
redéfinie comme :

area :: Shape a → Float


area (Circle r) = pi * r^2
area (Rectangle larg long) = larg * long

3.5 Structure d’enregistrement


Un enregistrement ou article (record) est une structure de don-
née, regroupant plusieurs valeurs (champs), potentiellement de types
différents. En exemple, un type P ersonne est défini par le nom ::
String, l’age :: Int et le poids :: F loat entre autres. On peut donc
définir le TAD P ersonne par :
CHAPITRE 3. TYPES DE DONNÉES ALGÉBRIQUES 21

data Personne = Personne String Int Float deriving (Show)


avec
nom :: String age :: Int poids :: F loat
P ersonne
P ersonne nom age poids :: P ersonne
Un exemple de terme du TAD Personne, est donné par P ersonne ”T oto Jean” 25 80.23.
Il est également possible de récupérer les valeurs d’un champ grace
aux fonctions nom et age ci-après :

nom :: Personne → String


nom (Personne n _ _ ) = n
age :: Personne → Int
age (Personne _ a _ ) = a

L’emploi du caractère ’_’ comme variable associée à un champ,


indique que ce champ n’est pas utile dans le contexte.
On constate également que dans la définition du TAD P ersonne,
les champs ne sont pas expressément nommés. Il est possible de
définir une structure d’enregistrement en attribuant à chaque champ
un nom, tel que défini ci-après :
data Personne = Personne {nom::String,
age:: Int,
poids:: Float
}deriving (Show)
Un terme de la structure de donnée sera par exemple P ersonne{nom =
”T oto Jean”, age = 25, poids = 80.23}, les fonctions permettant de sé-
lectionner les champs, s’écrivant comme vu précédemment.

3.6 Structures de données inductives


Une structure de donnée inductive, est un TAD dont la descrip-
tion est récursive. Considérons le type List ci-après :
CHAPITRE 3. TYPES DE DONNÉES ALGÉBRIQUES 22

data List a = Nil | Cons a (List a) deriving (Show)


avec
x::a xs::List a
N il N il::List a Cons Cons x xs::List a

Cette représentation de la liste, a quelques similitudes avec la


structure de liste définie précédemment ; ainsi N il correspond à [ ]
pour liste vide et Cons est une forme infixée de l’opérateur d’ajout
(:). Un terme d’une TAD List est par exemple Cons 1 (Cons 2 (Cons 3 N il)))
qui équivaut à la liste [1, 2, 3].
La fonction len ci-après permet de calculer la longueur d’une
liste.

len :: List a → Int


len Nil = 0
len (Cons x xs) = 1 + len xs

Soit à évaluer
len Cons 1 (Cons 2 (Cons 3 N il))) = 1 + len Cons 2 (Cons 3 N il))
= 1 + 1 + len Cons 3 N il
= 1 + 1 + 1 + len N il
= 1+1+1+0
= 3

Exercices
1. Écrire la fonction conc :: List a → List a → List a équivalente
à (++), qui concatène deux listes.
2. Écrire la fonction rev :: List a → List a équivalente à reverse,
qui inverse l’ordre des éléments de la liste.
3. Écrire les fonctions head et tail associées.
4. Proposer un foncteur map pour le type List a.
Fiche de TD/TP (N°1)

Exercice 1 : Tri par insertion


On désire trier une liste d’éléments numériques anfin que ceux-
ci soient rangés par ordre croissant. La méthode choisie est celle
du tri par insertion. Elle consiste à insérer un élément dans une
liste déjà rangée par ordre croissant, et à répéter cette opération
jusqu’à obtenir la liste triée.
Insertion : Ecrire la fonction insertion qui insère un élément
à sa place dans une liste. On suppose que cette liste est
déjà rangée par ordre croissant de ses éléments. Exemples
: insertion 2 [ ] donne [2] et (insertion 2 [1, 4]) donne [1, 2, 4]
Tri insertion : Ecrire la fonction triInsertion qui trie une liste
par ordre croissant, par insertions successives. On insère un
à un les éléments de la liste initiale dans une liste déjà triée.
Exemples : triInsertion [ ] donne [ ] et triInsertion [4, 6, 2, 8, 1, 3]
donne [1, 2, 3, 4, 6, 8].

Exercice 2 : Tri rapide (Quicksort)


Le tri rapide est un tri ayant de bonnes performances en moyenne
et dont l’algorithme est typique du principe « diviser pour régner » :
1. on choisit un élément de la liste initiale, que l’on appelle le
pivot ;
2. on sépare la liste initiale en deux sous-listes, la première
contenant les éléments strictement plus petits que le pivot et
la deuxième les autres ;

23
CHAPITRE 3. TYPES DE DONNÉES ALGÉBRIQUES 24

3. on trie récursivement les deux sous-listes ;


4. on ré-assemble les morceaux en une unique liste triée.
— Écrire une fonction qui découpe une liste à partir d’un pivot
donné en argument.
— Écrire une fonction qui trie une liste selon l’algorithme du
tri rapide. On prendra le premier élément de la liste comme
pivot.

Exercice 3 : Le codage R.L.E. (Run-Length


Encoding)
Le codage R.L.E. (Run Length Encoding) est une méthode de
compression de listes très simple. Son principe est de remplacer
dans une liste une suite de caractères identiques par le couple
constitué du nombre de caractères identiques et du caractère.
Ainsi, la liste[’a’, ’a’, ’a’, ’b’, ’a’, ’b’, ’b’] est compressée en [(3, ’a’), (1, ’b’), (1, ’a’), (2, ’b’)].
1. Écrire une fonction de décompression qui prend une liste de
données compressées et retourne la liste initiale.
2. Écrire la fonction de compression, qui prend une liste de don-
nées l et retourne la liste de données compressée au maxi-
mum par le codage R.L.E associée à l.

Exercice 4 : Polynômes
Un polynôme d’ordre n s’exprime sous la forme an xn + an−1 xn−1 +
. . . + a1 x + a0 avec ai ∈ N, x ∈ R étant la variable.
En supposant que les coefficients du polynôme sont représentés
par une liste simplement chaînée telle, que l’élément ai se trouve à
la position n − i ; exemple, le polynôme 4x3 + 2x2 + 1 sera représenté
par la liste [4, 2, 0, 1]. On voudrait mettre en œuvre une bibliothèque
de fonctions pour manipuler les polynômes.
1. Définir le type abstrait P oly a b équivalent à la liste ci-dessus,
avec a les types génériques des coefficients et b type géné-
rique des variables.
CHAPITRE 3. TYPES DE DONNÉES ALGÉBRIQUES 25

2. Ecrire la fonction pow qui prend en paramètre une variable x


et un nombre entier n, puis élève la variable à la puissance
n.
3. Ecrire la fonction evalP oly :: P oly a b → b qui faisant usage
de pow, prend en paramètre les coefficients, l’ordre n, la va-
riable x et retourne le résultat obtenu après évaluation du
polynôme.
4. Ecrire la fonction deriveP oly :: P oly a b → P oly a b qui prend
en paramètre les coefficients et l’ordre n pour retourner la
dérivée du polynôme.
5. Ecrire la fonction sumP oly :: P oly a b → P oly a b → P oly a b qui
fait la somme de deux polynômes.
Chapitre 4

Les structures d’arbres

4.1 Arbres binaires


Un arbre est une structure de donnée non linéaire, utile dans
la représentation de formes de données hiérarchisées. Cette leçon
s’intéresse au cas particulier des arbres binaires, qui sont ces arbre
dont les nœuds ont au plus deux branches.

4.1.1 Représentation
Structure de donnée

data Btree a = Leaf a | Fork (Btree a) (Btree a)


Un arbre binaire dispose de deux types de nœuds :
— Soit une feuille ou Leaf ,
— Soit un nœud intermédiaire ou F ork, qui est en réalité la
représentation de deux Btree.
Avec
x::a
Leaf Leaf x::Btree a
F ork xt::Btree a yt::Btree a
F ork xt yt::Btree a

Termes d’un arbre binaire


Un exemple de terme associé à la définition précédente est

26
CHAPITRE 4. LES STRUCTURES D’ARBRES 27

Fork (Leaf 1) (Fork (Leaf 2) (Leaf 3))


Représenté graphiquement comme

4.1.2 Opérations sur les arbres binaires


Taille et hauteur
La taille d’un arbre correspond au nombre de feuilles de ses
nœuds, et peut être calculée par la fonction ci-après.

size :: Btree a → Int


size (Leaf x) = 1
size (Fork xt yt) = size xt + size yt

La hauteur d’un arbre correspond à la profondeur maximale de


ses feuilles, calculée par la fonction suivante.

height :: Btree a → Int


height (Leaf x) = 0
height (Fork xt yt) =
1+(height xt ‘max‘ height yt)

Transformation d’un arbre en liste


Un arbre peut être vu comme une séquence de valeurs (feuilles),
prises de gauche à droite. La fonction f lattern transforme un arbre
binaire en une liste.
CHAPITRE 4. LES STRUCTURES D’ARBRES 28

flattern :: Btree a → [a]


flattern (Leaf x) = [x]
flattern (Fork xt yt) = flattern ++ flattern yt

Remarque 2. size = length·flattern

Foncteur associé

mapBtree :: (a → b) → Btree a → Btree b


mapBtree f (Leaf x) = Leaf (f x)
mapBtree f (Fork xt yt) =
Fork (mapBtree f xt)(mapBtree f yt)

Exercice
Écrire une fonction permettant de compter les nœuds internes
d’un Btree.

4.2 Arbres binaires de recherche


Un arbre binaire de recherche est une structure de données ba-
sée sur des nœuds où chaque nœud contient une étiquette et deux
sous-arbres, le gauche et le droit. Pour chaque nœud, l’étiquette
de la sous-arborescence gauche doit être inférieure à l’étiquette
du nœud actuel, et l’étiquette du sous-arbre droit doit être supé-
rieure à l’étiquette du nœud. Ces sous-arbres doivent aussi être
des arbres binaires de recherche.
CHAPITRE 4. LES STRUCTURES D’ARBRES 29

4.2.1 Structure de donnée

data (Ord a) ⇒ Stree a = Null | Fork (Stree a) a (Stree a)


Cette définition précise que les éléments d’un Stree sont ordonnés.
De fait la transformation d’un Stree en une liste, donne un en-
semble d’élément toujours ordonnés ; La fonction flattern associée
s’écrira donc :

flattern :: (Ord a) ⇒ Stree a → [a]


flattern Null = []
fattern (Fork xt x yt) =
flattern xt ++ [x] ++ flattern yt

Un arbre binaire de recherche est dit conforme, si en lui appli-


quant la fonction f lattern, on obtient une liste ordonnée d’éléments.
En d’autres termes, xt est un arbre binaire de recherche si l’appel
de fonction inordered xt renvoie T rue, avec :

inordered :: (Ord a) ⇒ Stree a → Bool


inordered xt = ordered · flattern xt

où odered xs est une fonction qui indique si la liste xs est dans


l’ordre croissant.
Exercice
Écrire la fonction odered :: (Ord a) ⇒ Stree a → Bool

4.2.2 Recherche d’une valeur


Rechercher une valeur donnée, revient à déterminer si cette
valeur est une feuille de l’arbre, telle que décrite par la fonction
member suivante.
CHAPITRE 4. LES STRUCTURES D’ARBRES 30

member :: (Ord a) ⇒ a → Stree a → Bool


member x Null = False
member x (Fork xt y yt)
| (x < y) = member x xt
| (x == y) = True
| (x > y) = member x yt

4.2.3 Hauteur et taille


La taille d’un arbre binaire de recherche correspond au nombre
de ses feuilles.

size :: Stree a → Int


size Null = 0
size (Fork xt x yt) = size xt + 1 + size yt

La hauteur d’un arbre binaire de recherche est donnée par

height :: Stree a → Int


height Null = 0
height (Fork xt x yt) =
1 + (height xt ‘max‘ height yt)

4.2.4 Insertion, ajout et suppression


Insertion d’un nouveau nœud
L’insertion d’un nœud commence par une recherche ; on cherche
la position du nœud à insérer ; lorsqu’on arrive à une feuille, on
ajoute le nœud comme fils de la feuille en comparant sa clé à celle
de la feuille : si elle est inférieure, le nouveau nœud sera à gauche ;
sinon il sera à droite.
CHAPITRE 4. LES STRUCTURES D’ARBRES 31

insert :: (Ord a) ⇒ a → Stree a → Stree a


insert x Null = Fork Null x Null
insert x (Fork xt y yt)
| (x < y) = Fork (insert x xt) y yt
| (x == y) = Fork xt y yt
| (x > y) = Fork xt y (insert x yt)

Suppression d’un nœud

delete :: (Ord a) ⇒ a → Stree a → Stree a


delete x Null = Null
delete x (Fork xt y yt)
| (x < y) = Fork (delete x xt) y yt
| (x == y) = join xt yt
| (x > y) = Fork xt y (delete x yt)

avec
join :: Stree a → Stree a → Stree a
join Null yt = yt
join (Fork ut x vt) yt = Fork ut x (join vt yt)

Exercice :
1. Définir le foncteur associé à un Stree
2. Définir la fonction empty :: Stree a → Bool qui indique s’il
n’y a aucun élément dans l’arbre.
Fiche de TD/TP (N°2)

Exercice 1 : Le Quadtree
Un quadtree est une structure de données de type arbre dans
laquelle chaque nœud a quatre fils. Les quadtrees sont le plus sou-
vent utilisés pour partitionner un espace bidimensionnel en le sub-
divisant récursivement en quatre nœuds.
Le quadtree peut être défini en haskell par :
data QuadTree a = Leaf a
| Node (QuadTree a)(QuadTree a)(QuadTree a)(QuadTree a)
deriving(Show)
1. Proposer un exemple de terme correspondant à cette défini-
tion.
2. Ecrire la fonction flattern correspondante au quadtree.
3. Ecrire les fonctions size et height correspondantes.
4. Ecrire la fonction map associée.

Exercice 2 : Compression d’image


On présente ici une représentation d’images sous forme d’arbres.
Cette représentation donne une méthode de compression plus ou
moins efficace et facilite certaines opérations sur les images.
Pour simplifier, on suppose les images carrées, de côté 2n , et
en noir et blanc. L’idée est la suivante : une image toute blanche
ou toute noire se représente par sa couleur, tandis qu’une image
composite se divise naturellement en quatre images carrées.

32
CHAPITRE 4. LES STRUCTURES D’ARBRES 33

On définit :
data Couleur = Blanc | Noir deriving(Show)
1. Proposer une représentation d’une image dans ce contexte ;
2. Écrire une fonction inverse qui prend un quadtree qt repré-
sentant une image i et renvoie un quadtree représentant
l’image i0 obtenue à partir de i en échangeant Noir et Blanc.
3. Écrire une fonction rotate qui prend un quadtree qt représen-
tant une image i et renvoie un quadtree représentant l’image
i tournée d’un quart de tour vers la gauche.
4. Considérons le type
data Bit = Zero | Un deriving(Show)
On souhaite pouvoir transformer un Quadtree Color en un QuadT ree Bit ;
sachant que Blanc se code Zero et Noir se code Un, écrire la fonc-
tion code qui fait usage de la fonction map de QuadTree pour réaliser
cela.
Le main
1. Proposer une fonction main permettant de lire la description
d’une image, fournie par l’utilisateur puis, afficher successi-
vement l’inverse, la rotation et le codage de cette image.
2. Générer l’exécutable haskell par la commande :
$ ghc -o <nom executable> <nom fichier>.hs
CHAPITRE 4. LES STRUCTURES D’ARBRES 34

Exercice 3 : La génétique
L’ADN est un polymère composé uniquement de quatre nucléo-
tides : l’adénine, la thymine, la guanine et la cytosine, qui consti-
tuent donc les "lettres" de "l’alphabet" génétique. L’ADN est consti-
tué de deux fibres constituées à partir de ces quatre nucléotides.
Ces deux fibres sont "complémentaires" : chaque nucléotide d’une
fibre est lié à un autre sur l’autre fibre, sachant que l’Adénine (A)
s’associe avec la Thymine (T) et la Guanine (G) avec la Cytosine (C).
On va créer des fonctions pour représenter et manipuler ces
fibres : une fibre sera constituée d’une liste de nucléotides. Pour
certaines de ces fonctions, il faudra peut-être décomposer le pro-
blème en plusieurs parties.
1. Définir les types N ucleotide et F ibre
2. f ibreV ide : qui teste si une fibre est vide ou non.
3. complement : qui prend un nucléotide et renvoie son complé-
ment ;
4. dupliquer qui renvoie le complémentaire d’une fibre passée
en paramètre. Par exemple, dupliquer [ ‘A‘,‘T‘,‘T‘,‘C‘] renvoie [
‘T‘,‘A‘, ‘A‘,‘G‘ ].
5. comparer qui renvoie vrai si 2 fibres sont complémentaires.

Vous aimerez peut-être aussi