0% ont trouvé ce document utile (0 vote)
107 vues5 pages

Listes chaînées en Scheme

Ce document décrit les listes chaînées en Scheme. Il présente la structure des listes et certaines fonctions de base comme first, rest, cons et append. Le document explique comment représenter des données structurées avec des listes et donne un exemple de fonction retournant un couple.

Transféré par

JP Roy
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
107 vues5 pages

Listes chaînées en Scheme

Ce document décrit les listes chaînées en Scheme. Il présente la structure des listes et certaines fonctions de base comme first, rest, cons et append. Le document explique comment représenter des données structurées avec des listes et donne un exemple de fonction retournant un couple.

Transféré par

JP Roy
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Les données structurées

Programmation Fonctionnelle I, Printemps 2017 Cours n°6


http://deptinfo.unice.fr/~roy

• Jusqu'à présent, nous avons principalement travaillé sur des données


atomiques (insécables) comme les nombres ou les booléens.
• Seules les images étaient des données composées [d'autres images !].
Mais il n'était pas possible de déconstruire une image…
Les listes (chaînées) • La structuration des données va nous permettre d'envisager une
valeur comme étant composée de plusieurs autres valeurs et de pouvoir
accéder à ces valeurs. Nous avons déjà vu les structures dans le cours 2.

• En maths, l'exemple typique est un produit cartésien A x B x C dont


les éléments sont les triplets (a,b,c) avec a ∈ A, b ∈ B, c ∈ C. Et
l'exemple typique de produit cartésien est l'espace vectoriel Rn .
• Nous allons nous focaliser en Scheme sur les listes, qui représentent
first rest Chap. 8 les suites finies de valeurs L = (a b ...). Mais attention, ce ne sont pas
les listes de Python, ce sont des listes chaînées. 2

Le type liste de Scheme • La primitive (list x1 x2 ...) est une fonction, donc elle évalue ses
arguments avant de construire la liste des valeurs obtenues :
• Une liste est une suite finie de valeurs.
> (define L (list (* 2 3) (+ 4 5))) > (first L) > (rest L)
Expression Résultat > L 6 (9)
(6 9)
(define L (list 6 4 5 8 3)) void Définition d'une liste en extension
(list 6 4 5 8 3) (6 4 5 8 3) Construction d'une liste en extension
L (6 4 5 8 3)
• Il est important de savoir utiliser la quote afin de distinguer une
Affichage d'une liste
(first L) 6 Accès au premier élément demande de calcul et une donnée brute sous forme de liste...
Accès à la liste privée du premier
(rest L) (4 5 8 3) > (define L (+ 1 2 3)) > (define L ’(+ 1 2 3))
élément
> L > L
(list? L) true Reconnaisseur de listes
6 (+ 1 2 3)
(length L) 5 Nombre d'éléments
(empty? L) false La liste est-elle vide ?
Construction d'une nouvelle liste par
(cons 0 L) (0 6 4 5 8 3)
ajout d'un élément en tête
une demande de calcul une donnée à l'état brut
N.B. La fonction (cons x L) ne modifie pas la liste L. C'est bien une fonction (appel de fonction) (ne pas calculer !)
cons : Elément x Liste → Liste, qui construit une nouvelle liste dont le premier
> 'bonjour
élément est x et dont le reste est la liste L. On rajoute à gauche, pas à droite ! bonjour
3 4
Première application : des fonctions à plusieurs résultats ! (define (division a b) ; a et b ∈ N, b > 0, retourne le couple (q,r)
(if (< a b)
• Exemple dans les entiers naturels : comment programmer par
(list 0 a)
récurrence la division de a par b si elle n'existait pas ? Elle doit (local [(define HR (division (- a b) b))] ; HR = Hyp. de Récurrence
retourner deux résultats : le quotient q et le reste r, sous la forme (list (+ 1 (first HR)) (second HR)))))

d'une liste (q r).


> (define d (division 19 5))
- Le quotient de a par b, c'est 1 de plus que le > d
(3 4)
quotient de a-b par b. > (printf "La division de 19 par 5 s'écrit 19=5*~a+~a\n"
- Le reste de la division de a par b est le même (first d) (second d))
que celui de la division de a-b par b. La division de 19 par 5 s'écrit 19=5*3+4
- Donc a décroît. Cas de base lorsque a < b.
• Pour une fonction à trois résultats, on retournerait une liste à trois
POUR diviser a par b et calculer (q,r) : éléments, etc. Bien voir que l'Hypothèse de Récurrence produit une
liste !
- si a < b, facile : le résultat est (0,a).
- sinon, je suppose par HR que je sais calculer la first, second, third, fourth....
division (q1,r1) de a-b par b. Mais alors, la division
de a par b n'est autre que (q,r) = (q1+1,r1). 5 6

• Premier aide-mémoire sur les listes :


Complexité Quelques fonctions primitives sur les listes
list

cons
Obj × ... × Obj → Liste
Obj × Liste → Liste*
O(n)

O(1) } en nombre
d'appels à cons
• Nous allons programmer les principales fonctions Scheme prédéfinies
sur les listes. Il faudra bien comprendre à la fois leur fonctionnement
et leur complexité pour savoir les utiliser dans les algorithmes !
first Liste* → Obj O(1)
(append L1 ... Lk) O(n1 + · · · + nk 1)
second Liste** → Obj O(1)
(build-list n f) O(n)
L[1:] est
rest Liste* → Liste O(1) O(n) en Python (length L) O(n)
(member x L) O(n)
empty? Obj → Booléen O(1)
(reverse L) O(n)

• Principe de récurrence sur les listes [TRES IMPORTANT !] : (sort L p) O(n log n)

Pour programmer par récurrence une fonction (foo L) portant sur une liste L :
• Rappel : dans ce cours, la complexité O(n) dénote un nombre
- je commence par examiner le cas de la liste vide.
d'opérations proportionnel à n dans le pire des cas ! Vous aurez des
- si la liste est ≠ , je suppose que je sais calculer (foo (rest L))
définitions mathématiques plus précises en maths...
et je montre comment je peux en déduire la valeur de (foo L). 7 8
La longueur d'une liste : (length L) L'appartenance à une liste : (member x L)

• La longueur d'une liste est le nombre de ses éléments en surface : • La fonction (member x L) retourne #t ou #f suivant que x est ou non
un élément de surface de la liste L.
(length ’(6 4 #t (5 a 1) 8 "une chaîne" coucou)) 7

• Exemples : > (member 'qui '(le chien qui est noir))


• Programmation par récurrence sur (la longueur de) L : #t
> (member 'qui '(le chien (qui est noir) mange vite)
- si L est vide : sa longueur est 0, c'est le cas de base. #f

- sinon, supposons par HR que l'on sache calculer la longueur de qui n'est pas en surface...
• Programmation :
(rest L). Quid de la longueur de L ? Facile, c'est 1 de plus...
(define ($member x L)
(define ($length L) ; $ pour ne pas tuer la primitive length ! (cond ((empty? L) #f)
(if (empty? L) ((equal? x (first L)) #t) COMPLEXITE DU
0 (else ($member x (rest L)))))
(+ 1 ($length (rest L)))))
PARCOURS :
COMPLEXITE DU PARCOURS O(n) si l'on mesure le
en O(n) : Le nombre d'éléments (define ($member x L)
nombre d'appels à rest.
(and (not (empty? L))
> ($length '(a b (c d e) f g h)) de L si l'on mesure le nombre (or (equal? (first L) x)
6
avec un coût de 6... d'appels à rest. ($member x (rest L)))))
9 10

L'accès à l'élément numéro k d'une liste : (list-ref L k) Un constructeur de liste en compréhension : (build-list n f)

• Les éléments sont numérotés à partir de 0, comme dans tous les • Intéressant si l'on connaît la loi de formation du terme numéro i :
langages de programmation. Par exemple (first L) (list-ref L 0).
(build-list 5 sqr) (0 1 4 9 16)
> (list-ref '(jaune rouge bleu noir) 2)
bleu (build-list 5 (lambda (i) (* 2 i))) (0 2 4 6 8)

• Voici comment est implémenté list-ref en Scheme :


(define ($list-ref L k) ; 0 ≤ k < length(L)
(cond ((empty? L) (error "$list-ref : Liste trop courte"))
la fonction qui exprime COMPLEXITE DE LA
((= k 0) (first L)) comment se calcule l'élément CONSTRUCTION en O(n)
(else ($list-ref (rest L) (- k 1))))) numéro i. Attention, les si l'on mesure le nombre
numéros commencent à 0. d'appels à cons.
• La complexité [nombre d'appels à rest] est clairement en O(k). Ce
n'est donc pas du tout la même chose qu'en Python, où le calcul de (define ($build-list n f)
(if (= n 0)
len(L) se fait en O(1). Les listes Scheme sont des listes chaînées alors
empty ; la liste vide !
que les listes Python sont des tableaux. (cons (f 0) ($build-list (- n 1) (lambda (i) (f (+ i 1)))))))

• MORALE : on s'efforcera de ne PAS utiliser list-ref en Scheme !


Contentez-vous d'avancer dans une liste par récurrence... ( f(0) f(1) f(2) f(3) ... )
11 12
La concaténation de deux listes : (append L1 L2) L'inversion d'une liste : (reverse L)

• La fonction (append L1 L2) retourne une liste contenant les éléments • Cette fonction (reverse L) retourne une copie inversée de L :
de L1 juxtaposés à ceux de L2 :
> (reverse '(je suis sur la plage))
> (append '(le chien que) '(je vois est noir)) (plage la sur suis je)
(le chien que je vois est noir)
(je suis sur la plage)
• Récurrence sur L.
• Récurrence sur L1 : (le chien que) ⊕ (je vois est noir)
Hypothèse de récurrence : HR
(le chien que je vois est noir)
je sais inverser le reste de L ! (plage la sur suis) ⊕ (je)
(define ($append L1 L2)
(if (empty? L1)
(define ($reverse L) ; algorithme naïf en O(n )
2
L2
(if (empty? L)
(cons (first L1) ($append (rest L1) L2))))
L
(append ($reverse (rest L)) (list (first L)))))
• COMPLEXITE. On fait autant d'appels à cons que d'éléments dans L1.
Donc le coût est O(n1) et indépendant de L2 ! • COMPLEXITE. Soit cn le coût en nombre d'appels à cons pour inverser
une liste de longueur n. Alors c0 = 0 et cn = cn-1 + 1 + (n-1) = cn-1 + n , d'où
• Généralisation : (append L1 L2 ... Lk), COMPLEXITE : O(n1+...+nk-1)
cn = O(n2). Nous verrons plus tard un meilleur algorithme en O(n)...
13 14

Pourquoi donc cn = O(n2) ? Savoir si une liste est triée

• Comment raisonner sur une telle équation cn = cn-1 + n ? • Une liste (x0 x1 x2 ...) est triée [en croissant] si xi xi+1 ⇥i
• Si l'on possède des théorèmes : cn = cn-1 + O(nd) cn = O(nd+1).
• Comment savoir si une liste est triée ? En la parcourant et en
• Sinon, courage, on déplie la formule jusqu'à voir une loi de formation : cherchant s'il existe une inversion (xi,xi+1) : telle que xi > xi+1.

cn = cn-1 + n • Hypothèse de récurrence : je sais si le reste de la liste est trié. Il me


cn = cn-2 + (n-1) + n
suffit alors de comparer les deux premiers éléments :
cn = cn-3 + (n-2) + (n-1) + n
....... ; je pousse jusqu'au bout !
(define (croissante? L)
cn = c0 + (1 + 2 + ... + n) ; Ah-ah ! Formule connue...
(cond ((empty? L) #t)
cn = c0 + n(n+1)/2
((empty? (rest L)) #t) ; un seul élément ?
cn = O(n2)
L'algorithme est ((<= (first L) (second L)) (croissante? (rest L)))
(else #f)))
quadratique !
10000 éléments 20000 éléments > (croissante? '(2 8 8 12 23)) COMPLEXITE : O(n) si
#t
843 ms 3275 ms l'on mesure le nombre
> (croissante? '(2 6 8 12 10 23))
#f d'appels à rest.
15 16
Le tri primitif d'une liste : (sort L rel) > (sort '(12 6 2 23 8) <) Un algorithme de tri naïf : le TRI PAR INSERTION
(2 6 8 12 23)
• Voici la manière la plus simple de trier une liste L de nombres. On
• Problème plus difficile mais fondamental. Il existe plusieurs manières
procède par récurrence brutale sur L.
de le résoudre ! Les meilleurs algorithmes ont une COMPLEXITE
[nombre d'appels à cons] en O(n log n). • Hypothèse de récurrence : je sais trier le reste de L !
• Par exemple la primitive Racket (sort L rel) où rel est une relation
d'ordre strict quelconque [par exemple <]. (12 6 2 23 8 4 19 7)
> (define L (build-list 20 (λ (i) (random 100))))
HR
> L
(70 46 81 77 92 57 81 24 62 51 46 59 97 94 25 0 69 85 69 63)
> (sort L <) (2 4 6 7 8 19 23)
(0 24 25 46 46 51 57 59 62 63 69 69 70 77 81 81 85 92 94 97)
insertion
• Vérifions que le tri est rapide [en n log n] :
(define (tri-ins L)
> (define L1 (build-list 10000 (λ (i) (random 100)))) (if (empty? L)
> (time (void (sort L1 <))) ; void pour ne pas voir le résultat L
cpu time: 2 real time: 3 gc time: 0 ; 2 millisecondes (insertion (first L) (tri-ins (rest L)))))
> (define L2 (build-list 100000 (λ (i) (random 100))))
> (time (void (sort L2 <)))
cpu time: 23 real time: 23 gc time: 0 ; 23 millisecondes 17 • Il reste à programmer la fonction d’insertion ! 18

• Soit donc à définir la fonction (insertion x LT) qui construit une Rendre le tri polymorphe
nouvelle liste obtenue en insérant x à sa place dans la liste triée LT.
• Et si je souhaite un tri décroissant, dois-je programmer un autre
• Récurrence sur LT : supposons qu'on sache insérer x dans le reste algorithme de tri ? Non, il me suffit de faire abstraction de la
de la liste LT. Comment en déduire l'insertion de x dans LT ? relation d'ordre strict < et la passer en paramètre :

(define (insertion x LT) (define (tri-ins L rel?) ; rel? : L x L " boolean


(cond ((empty? LT) (list x)) ; (list x) ⬄ (cons x empty) (if (empty? L)
((< x (first LT)) (cons x LT)) L
(else (cons (first LT) (insertion x (rest LT)))))) (insertion (first L) (tri-ins (rest L) rel?) rel?)))

(define (insertion x LT rel?) ; rel? est une relation d'ordre


> (insertion 8 '(4 7 9 10))
(cond ((empty? LT) (list x))
(4 7 8 9 10)
> (tri-ins '(12 6 2 23 8 4 19 7)) ((rel? x (first LT)) (cons x LT))
(2 4 6 7 8 12 19 23) (else (cons (first LT) (insertion x (rest LT) rel?)))))

• COMPLEXITE. Soit cn le coût [nombre d'appels à cons] de trier une > (tri-ins '(12 6 2 23 8) <) > (tri-ins '(12 6 2 23 8) >)
liste de longueur n. Alors c0 = 0 et cn = cn-1 + <coût de l'insertion>. (2 6 8 12 23) (23 12 8 6 2)
> (tri-ins '("laetitia" "rachid" "kevin" "brice") string<?)
Or le coût d'une insertion dans une liste de longueur n est en O(n). ("brice" "kevin" "laetitia" "rachid")
Donc cn = cn-1 + O(n). Il en resulte que cn = O(n2). Pas fameux...
19 (tri-ins (...) (lambda (x1 x2) ...)) 20

Vous aimerez peut-être aussi