Le
texte qui suit peut être collé à la suite des fonctions Clisp.
Ma
source d'inspiration a été :
Practical
Common Lisp de Peter Seibel
dont
vous pouvez visiter le site :
Mais,
là, tout est en anglais.
_____________________________
>>>>
Liste des symboles rencontrés dans ce chapitre:
loop return for collect collecting from downfrom upfrom to upto below downto above repeat in on by across compile disassemble sum summing count counting find then with append nconc maximize minimize it gethash initially finally named while until always never thereis digit-char-p loop-finish time
Macro
LOOP:
(defparameter n 0)
N
RETURN:
(loop ;version loop simple
(when (> n 10) ;quand n est supérieur à 10 (n = 11)
(return)) ;il y a sortie d'itération (n vaut alors 11)
(format t "~a " n) ;la valeur de n est affichée
(incf n)) ;n est incrémentée
0 1 2 3 4 5 6 7 8 9 10
NIL
(attention: (1+ n) n'incrémente pas n, mais rend n + 1.
n
resterait, alors, à zéro. Préalable: (setf n 0).
Remarque: pour remettre n à zéro, on peut ajouter (setf n 0) juste avant (return).
version
loop étendue:
du
vocabulaire à retenir: do, return, across, in, on, of, as, and, collecting,
counting,
finally,
for, from, downfrom, upfrom, to, upto, below, downto, above,
by,
summing, then, it
FOR:
COLLECTE:
COLLECTING:
FROM:
DOWNFROM:
TO:
UPTO:
BELOW:
DOWNTO:
ABOVE:
(loop
for i from 1 to 10 collecting i)
(1
2 3 4 5 6 7 8 9 10) la valeur incrémentale est 1 par défaut
(loop
for i from 10 to 1 collecting i)
NIL la
valeur incrémentale étant 1 par défault et 10 étant déjà
plus grand que 1
il
faut écrire:
(loop
for i from 10 downto 1 collecting i)
(10
9 8 7 6 5 4 3 2 1)
ou
bien:
(loop
for i downfrom 10 to 1 collecting i)
(10
9 8 7 6 5 4 3 2 1)
(loop
for i upto 10 collect i)
(0
1 2 3 4 5 6 7 8 9 10) la valeur de départ est 0 par défaut.
(loop
for i downto -10 collect i) est une erreur, il faut
écrire:
(loop
for i from 0 downto -10 collect i)
(0
-1 -2 -3 -4 -5 -6 -7 -8 -9 -10) ici, il faut préciser la valeur de
départ
(loop
for i from 5 below 10 collect i)
(5
6 7 8 9) les itérations s'arrêtent à 9 (sous 10)
(loop
for i from 5 above 2 collect i)
(5
4 3) les itérations s'arrêtent à 3 (au dessus de 2)
(loop
for i from 5 above 10 collect i)
NIL il
n'y a rien entre 5 et 10 (très proche de 10 et
au
dessus)
REPEAT:
A
la place de:
(loop
for i from 1 to 3 do (format t "coucou~%"))
coucou
coucou
coucou
NIL
on
peut écrire:
(loop
repeat 3 do (format t "coucou~%"))
coucou
coucou
coucou
NIL
La clause repeat ne crée pas de variable de boucle explicitement.
(loop
for
item in '(a b c d e f g h)
for
i from 1 to 10
do
(format t "~a: ~a; " i item))
1:
A; 2: B; 3: C; 4: D; 5: E; 6: F; 7: G; 8: H;
NIL on
peut avoir plusieurs clauses for dans un LOOP
ici,
c'est la liste qui conditionne la fin du loop (8 éléments)
LOOP
et les collections:
les
listes:
IN:
(loop
for i in '(10 20 30 40) collect i)
(10
20 30 40) à chaque itération, i prend la valeur de chaque élément
de la liste (c'est à dire le CAR de la liste, puis le CAR du
CDR, etc
ON:
(loop
for i on '(10 20 30 40) collect i)
((10
20 30 40) (20 30 40) (30 40) (40))
à
chaque itération, i prend pour valeur la liste, puis le CDR
de
la liste, etc
BY: est
suivi d'une fonction qui prend une liste et rend une sous-liste.
Le
loop s'applique à la première liste, puis à la sous-liste, etc
(loop
for i in '(10 20 30 40 50 60) by #'cddr collect i)
(10
30 50) à
chaque itération i prend pour valeur le 1er élément de la
liste puis le 1er élément du CDDR, etc
(loop
for i on '(10 20 30 40) by #'cddr collect i)
((10
20 30 40) (30 40)) à chaque itération i prend pour valeur la liste
puis le CDDR
de la liste, etc
Les
vecteurs (incluant les strings et les bit-vectors):
ACROSS:
(loop
for x across "arbre" collect x)
(#\a
#\r #\b #\r #\e) rend la liste des caractères du mot
(loop
for x across #*10110 collect x)
(1
0 1 1 0) rend la liste des bits composants le vecteur-bit
(loop
for x across #(a 1 2 b c) collect x)
(A
1 2 B C) rend la liste des coordonnées du vecteur
LOOP
et table de hachage: voir "tables de hachage:". (dans un prochain article)
LOOP
et packages: voir "LOOP et les packages:". (dans un prochain article)
AND: (clause loop à ne pas confondre avec la fonction Clisp)
(defun nbs-fibo (min max)
"rend les nombres de Fibonacci entre min et max." ;min et max représentent les rangs
(if (and (<= 0 min) (> max min)) ;min doit être positif et max plus grand que min
(let ((min (ceiling min)) (max (floor max))) ;min et max sont transformés en entiers
(loop for i from 1 to max ;on fait varier i de 1 à max
for x = 0 then y ;la valeur initiale de x est 0 et x prend la valeur de y à la prochaine itération
and y = 1 then (+ x y) ;la valeur initiale de y est 1 et y prend la valeur x+y à la prochaine itération
when (>= i min) collect y)) ;rend la liste des y à partir de i=min
(error "le 1er argument doit être plus grand ou égale à zéro et le 2ème plus grand que le 1er.")))
(voir THEN: plus loin)
(defun
suite-fibo (p)
"rend
les p premiers nombres de la suite de Fibonacci dans une liste."
(loop
for i from 0 to p
collecting
(do
((n 0 (1+ n))
(cur
0 next)
(next
1 (+ cur next)))
((=
i n) cur))))
SUITE-FIBO
(suite-fibo
15)
(0
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610)
rend
la suite de Fibonacci jusqu'au 15ème rang (16 nombres)
COMPILE:
(compile
'suite-fibo)
SUITE-FIBO
NIL
NIL compile
la fonction suite-fibo
Pour
voir le code résultant de cette compilation:
DISASSEMBLE:
(disassemble
'suite-fibo)
;
disassembly for SUITE-FIBO
;
0DE0204E: 8B4DE8
MOV ECX, [EBP-24] ; no-arg-parsing
entry point
;
051: F6C103
TEST CL, 3
;
054: 7416
JEQ L0
;
056: 8D41F9
LEA EAX, [ECX-7]
;
059: A807
TEST AL, 7
;
05B: 0F85D3000000
JNE L8
;
061: 8A41F9
MOV AL, [ECX-7]
;
064: 3C16
CMP AL, 22
;
066: 0F87C8000000
JNBE L8
;
06C: L0: C745F000000000 MOV DWORD
PTR [EBP-16], 0
;
073: E8A8EA1FF3
CALL #x1000B20 ;
ALLOCATE-CONS-TO-EAX
;
078: C740FD0B001001
MOV DWORD PTR [EAX-3], 17825803
;
07F: C740010B001001
MOV DWORD PTR [EAX+1], 17825803
;
086: 8945E4
MOV [EBP-28], EAX
;
089: 8945FC
MOV [EBP-4], EAX
;
08C: E987000000
JMP L7
;
091: L1: 8B45FC
MOV EAX, [EBP-4]
;
094: 8945EC
MOV [EBP-20], EAX
;
097: 31C0
XOR EAX, EAX
;
099: C745F800000000
MOV DWORD PTR [EBP-8], 0
;
0A0: C745F404000000
MOV DWORD PTR [EBP-12], 4
;
0A7: EB2E
JMP L5
;
0A9: L2: BF04000000
MOV EDI, 4
;
0AE: 8BD0
MOV EDX, EAX
;
0B0: E8A3E01FF3
CALL #x1000158 ;
GENERIC-+
;
0B5: 7302
JNB L3
;
0B7: 8BE3
MOV ESP, EBX
;
0B9: L3: 8955FC
MOV [EBP-4], EDX
;
0BC: 8B55F8
MOV EDX, [EBP-8]
;
0BF: 8B7DF4
MOV EDI, [EBP-12]
;
0C2: E891E01FF3
CALL #x1000158 ;
GENERIC-+
;
0C7: 7302
JNB L4
;
0C9: 8BE3
MOV ESP, EBX
;
0CB: L4: 8B45FC
MOV EAX, [EBP-4]
;
0CE: 8B4DF4
MOV ECX, [EBP-12]
;
0D1: 894DF8
MOV [EBP-8], ECX
;
0D4: 8955F4
MOV [EBP-12], EDX
;
0D7: L5: 8945FC
MOV [EBP-4], EAX
;
0DA: 8B55F0
MOV EDX, [EBP-16]
;
0DD: 8BF8
MOV EDI, EAX
;
0DF: E8F6E21FF3
CALL #x10003DA ;
GENERIC-EQL
;
0E4: 8B45FC
MOV EAX, [EBP-4]
;
0E7: 75C0
JNE L2
;
0E9: E832EA1FF3
CALL #x1000B20 ;
ALLOCATE-CONS-TO-EAX
;
0EE: 8B4DF8
MOV ECX, [EBP-8]
;
0F1: 8948FD
MOV [EAX-3], ECX
;
0F4: C740010B001001
MOV DWORD PTR [EAX+1], 17825803
;
0FB: 8945FC
MOV [EBP-4], EAX
;
0FE: 8B4DEC
MOV ECX, [EBP-20]
;
101: 894101
MOV [ECX+1], EAX
;
104: 8B55F0
MOV EDX, [EBP-16]
;
107: BF04000000
MOV EDI, 4
;
10C: E847E01FF3
CALL #x1000158 ;
GENERIC-+
;
111: 7302
JNB L6
;
113: 8BE3
MOV ESP, EBX
;
115: L6: 8955F0
MOV [EBP-16], EDX
;
118: L7: 8B55F0
MOV EDX, [EBP-16]
;
11B: 8B7DE8
MOV EDI, [EBP-24]
;
11E: E88BE21FF3
CALL #x10003AE ;
GENERIC->
;
123: 0F8E68FFFFFF
JLE L1
;
129: 8B45E4
MOV EAX, [EBP-28]
;
12C: 8B5001
MOV EDX, [EAX+1]
;
12F: 8BE5
MOV ESP, EBP
;
131: F8
CLC
;
132: 5D
POP EBP
;
133: C3
RET
;
134: L8: 8B45E8
MOV EAX, [EBP-24]
;
137: 8B0D1820E00D
MOV ECX, [#xDE02018] ; 'REAL
;
13D: CC0A
BREAK 10
; error trap
;
13F: 03
BYTE #X03
;
140: 1F
BYTE #X1F
; OBJECT-NOT-TYPE-ERROR
;
141: 10
BYTE #X10
; EAX
;
142: 50
BYTE #X50
; ECX
;
143: CC0A
BREAK 10
; error trap
;
145: 02
BYTE #X02
;
146: 18
BYTE #X18
; INVALID-ARG-COUNT-ERROR
;
147: 4F
BYTE #X4F
; ECX
NIL
SUMMING:
(loop
for x from 1 to 10 summing (expt x 2)) >> 385
rend
la somme des carrés des entiers de 1 à 10
Rappel :
(exp
1) >> 2.7182817 (e)
(expt
3 2) >> 9
SUITE DE SYRACUSE: (la suite de Syracuse "finit" (toujours?) par ...2,1,4,2,1,4,2,1,...)
Toujours? Cela reste à démontrer!
(defun suite_syracuse (nb max) ;nb est le premier terme et max est le nombre de termes
"rend la suite de Syracuse jusqu'au rang max à partir du nombre nb."
(if (and (integerp nb) (< 1 max)) ;nb doit être un entier et max est supérieur à 1
(loop for i from 1 to max
for y = nb then (if (= 0 (mod y 2)) (/ y 2) ;y prend la valeur de nb, puis la valeur de y/2 si y est pair
(+ (* 3 y) 1)) ;ou bien la valeur de 3y+1 si y est impair
collect y)
(error "Le 1er argument doit être un entier et le 2ème plus grand que 1.")))
(suite_syracuse 19 30)
(19 58 29 88 44 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1 4 2 1 4 2 1 4 2 1)
Pour améliorer la fonction précédente, il peut être utile de vérifier si la suite est bien de syracuse.
Voici un pseudo-prédicat qui le vérifie.
Attention, cependant, car NIL ne veut pas dire que ce n'est pas une suite de syracuse.
(defun syracusep (n max)
"Le nombre n commence-t-il une suite de siracuse? T si un nombre de la suite est 1.
NIL signifie qu'un nombre de la suite dépasse max: il faut alors augmenter max. Il s'agit donc d'un pseudo-prédicat."
(if (and (integerp n) (< 0 n)) ;n doit être un entier positif
(labels ((suitep (i max) ;définition d'une fonction locale
(if (> i max) nil ;rend NIL si i>max
(if (= i 1) t ;rend T si i=1 (c'est bien une suite de syracuse)
(suitep ((lambda (p) (if (= (mod p 2) 0) (/ p 2) ;calcule du nombre suivant
(+ (* p 3) 1))) i) max)))))
(suitep n max)) ;appel de la fonction locale
(format t "Le 1er argument doit être un entier positif."))) ;affichage si n n'est pas un entier positif
Je vous laisse faire les modifications nécessaires.
(defun nb_syracuse (min top max1) ;attention min doit être un entier positif!
"Teste chaque nombre entier positif entre min et top pour savoir s'il commence une suite de syracuse.
max1 limite les calculs quand des nombres trop grands apparaissent dans la suite."
(do ((j min (1+ j))) ;itération sur j à partir de min et incrémentation de 1
((>= j top) 'terminé) ;affichage si i>=top
(if (syracusep j max1) (format t "~a " j) ;affichage de j s'il commence bien une suite de syracuse,sinon:
(format t "Soit ~a ne commence pas une suite de syracuse (peu probable), soit l'argument ~a est trop faible." j max1))))
Essayez:
(nb_syracuse 900 1000 100000)
puis:
(nb_syracuse 900 1000 1000000) ;donc un zéro de plus dans le 3ème argument
Les nombres d'une suite de syracuse peuvent être grands.
La fonction suivante permet de trouver le plus grand nombre d'une suite de syracuse.
Le 1er argument est le 1er nombre de la suite.
Le 2ème argument est le nombre au delà duquel les calculs s'arrêtent (mais il peut être augmenté).
(defun nbmaxsyracuse (n max)
"rend le plus grand nombre de la suite de syracuse obtenu à partir du nombre n (1er argument).
le 2ème argument est une puissance de 10 qui limite les calculs."
(if (and (integerp n) (< 0 n)) ;n doit être un entier positif
(if (syracusep n max) ;n doit débuter une suite de syracuse, sinon augmenter max
(let ((k n)) ;initialisation de k à n
(labels ((suitep (i max) ;définition d'une fonction locale
(cond ((> i max) nil) ;cette condition rend NIL si syracusep (au dessus) n'est pas utilisé
((= i 1) (print k)) ;imprime k (le plus grand nombre) quand i=1
((< k i) (setf k i) (suitep ((lambda (p) (if (= (mod p 2) 0) (/ p 2) ;change la valeur de k si i est plus grand
(+ (* p 3) 1))) i) max)) ;puis calcule le nombre suivant
(t (suitep ((lambda (p) (if (= (mod p 2) 0) (/ p 2)
(+ (* p 3) 1))) i) max))))) ;calcule le nombre suivant dans tous les autres cas
(suitep n max))) ;appel à la fonction locale
(format t "Il peut être nécessaire d'augmenter le 2ème argument max.")) ;affichage si syracusep est NIL
(format t "Le 1er argument doit être un entier positif."))) ;affichage si n n'est pas un entier positif
Essayez:
(nbmaxsyracuse 937 100000)
Il peut être nécessaire d'augmenter le 2ème argument max.
NIL
(nbmaxsyracuse 937 1000000) ;on ajoute un zéro au deuxième argument
250504
250504
La suite de syracuse commençant à 937 monte jusqu'à 250504 et redescend à 1!!!
COUNTING:
FIND:
(loop
for x across "combien cette phrase contient-elle de voyelles?"
counting
(find x "aeiou")) >> 16
Pour compter chaque lettre dans un texte (attention! Les majuscules ne sont pas prises en compte et des symboles particuliers dans le texte à analyser peuvent provoquer une erreur. En cherchant bien, vous pourrez résoudre ces problèmes-là.) :
(loop for x across "abcdefghijklmnopqrstuvwxyz"
do (format t "~a: ~a; " x (loop for y across "combien de fois cette phrase contient-elle les lettres de l'alphabet?"
counting (find y (list x)))))
a: 3; b: 2; c: 3; d: 2; e: 13; f: 1; g: 0; h: 2; i: 3; j: 0; k: 0; l: 6; m: 1; n: 3; o: 3; p: 2; q: 0; r: 2; s: 4; t: 7; u: 0; v: 0; w: 0; x: 0; y: 0; z: 0;
NIL
NIL
(loop
for i below 10
and
a = 0 then b
and
b = 1 then (+ b a)
finally
(return a)) >> 55 nombre de
fibonacci de rang 10 (11ième nombre)
curiosité? L'itération sur i devrait s'arrêter sur 9 (voir below plus haut).
Et pour i=9 a vaut 34 !!!
finally semble imposer une itération de plus.
Comparez avec ce qui suit:
(loop for i below 10
and a = 0 then b
and b = 1 then (+ b a)
collecting (list i a b))
((0 0 1) (1 1 1) (2 1 2) (3 2 3) (4 3 5) (5 5 8) (6 8 13) (7 13 21) (8 21 34) (9 34 55))
A la dernière itération, a vaut bien 34.
Pour bien comprendre, voici un tableau
qui montre chaque itération du loop :
i
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
a=0
|
1
|
1
|
2
|
3
|
5
|
8
|
13
|
21
|
34
|
55
|
b=1
|
1
|
2
|
3
|
5
|
8
|
13
|
21
|
34
|
55
|
89
|
Les valeurs de a et b sont les valeurs
en fin d'itération (donc finally).
THEN:
Clause
equals-then: (loop for var = initial-value-form [then step-form] ...)
(loop
repeat 5
for
x = 2
for
y = 1 then (+ x y)
collect
y)
(1
3 5 7 9) ici, il n'y a pas de clause then après le 1er for (x est
réinitialisé à 2 à chaque itération)
(loop
repeat 5
for
x = 0 then y
for
y = 1 then (+ x y)
collect
y)
(1
2 4 8 16) le couple (x,y) prend à chaque itération les valeurs
successives suivantes: (0,1) (1,2) (2,4) (4,8) (8,16)
y
est donc calculée avec la nouvelle valeur de x
si
on échange les lignes "for x" et "for y":
(loop
repeat 5
for
y = 1 then (+ x y)
for
x = 0 then y
collect
y)
(1
1 2 4 8) c'est x, cette fois, qui prend la nouvelle valeur de y
Si
on ne veut pas prendre la nouvelle valeur, mais l'ancienne, après la
1ère clause for on utilise and à la place de for:
(loop
repeat 10
for
x = 0 then y
and
y = 1 then (+ x y)
collect
y)
(1
1 2 3 5 8 13 21 34 55) on obtient la suite de Fibonacci (sauf 0)
WITH:
permet de créer une variable locale dans
LOOP
(loop
repeat 5
with
a = 10
for
x = 1 then 2
for
y = 2 then (* x y)
collect
(* a y))
(20
40 80 160 320) rend la suite des puissances de 2 multipliées par 10
Variables
destructurantes: une caractéristique de LOOP.
(loop
for (c (a b)) in '((h (1 2)) (j (3 4)) (k (5 6)))
do
(format t "~a => a: ~a; b: ~a~%" c a b))
H => a: 1; b: 2
J
=> a: 3; b: 4
K
=> a: 5; b: 6
NIL LOOP
a la propriété de destructurer: toute variable qui suit for ou
with peut être remplacée par un arbre et la liste qui suit in
sera destructurée selon l'arbre (si possible).
On
peut ignorer une valeur dans la liste destructurée:
(loop
for (c (a nil)) in '((h (1 2)) (j (3 4)) (k (5 6)))
do
(format t "~a => a: ~a;~%" c a))
H
=> a: 1;
J
=> a: 3;
K
=> a: 5;
NIL
On
peut utiliser les listes pointées:
Au
lieu d'écrire:
(loop
for cons on '(a b c d) ;cons représente une liste (sous-liste de (a b c d))
do
(format t "~a" (car cons)) ;(car cons) est le premier élément de la sous-liste cons
when
(cdr cons) do (format t ", ")) ;tant que (cdr cons) vaut T, on affiche une virgule
A,
B, C, D
NIL
... On
peut faire:
(loop
for (item . rest) on '(a b c d) ;(item . rest) représente, successivement, (a . (b c d)), (b . (c d)), (c . (d)), (d . nil)
do
(format t "~a" item) ;item est un élément de (a b c d)
when
rest do (format t ", ")) ;quand rest est NIL, la virgule n'est pas affichée
A,
B, C, D
NIL
LOOP
et les clauses d'accumulation de valeur:
APPEND:
NCONC:
MAXIMIZE:
MINIMIZE:
Chaque
clause a la structure suivante: verbe form [into var], le verbe
pouvant être: collect, append, nconc, count, sum, maximize, minimize
(les participes présents sont synonymes: collecting, etc). Si into
est présent la valeur est sauvée dans var.
(defparameter
*random20* (loop repeat 30 collect (random 20)))
*RANDOM20* définit
un paramètre qui contient une liste de 30 nombres aléatoires compris
entre 0 et 20 (20 non compris)
(loop for i in *random20*
count
i into N ;place dans N le nombre d'entiers de la liste *random*
count
(evenp i) into evens ;place dans evens le nombres d'entiers pairs
count
(oddp i) into odds ;place dans odds le nombre d'entiers impairs
sum
i into total ;place dans total la somme des entiers de la liste *random*
maximize
i into max ;place dans max le plus grand des entiers
minimize
i into min ;place dans min le plus petit des entiers
finally
(return (list min max (/ total N) evens odds))) ;finalement, rend une liste d'éléments calculés
(0
19 301/30 15 15) accumule le nombre de valeurs de la liste du
paramètre
*random20*, le nombre de valeurs paires,
le nombre de valeurs impaires, le total, le maximum,
le minimum. Puis rend sous forme d'une liste
le minimum, le maximum, la
moyenne (total/N), le nombre de valeurs paires, le nombre
de valeurs impaires.
Bien
noter la clause finally qui retourne le résultat en fin d'itération
Sortie
inconditionnelle:
(block
sortie
(loop
for i from 0 to 10 return 100 do (print i))
(print
"c'est une sortie inconditionnelle du LOOP qui rend 100. Le code
qui suit le LOOP est évalué et cela rend: ")
200)
"c'est
une sortie inconditionnelle du LOOP qui rend 100. Le code qui suit le
LOOP est évalué et cela rend: "
200
Remarque: ici, RETURN est une clause LOOP et "return 100" dans le code est équivalent à: do (return 100).
(block
sortie
(loop
for i from 0 to 10 do (return-from sortie 100) (print i))
(print
"Rien ne s'imprimera.")
200)
100 cette
fois, c'est une sortie inconditionnelle du bloc "sortie"
qui
rend 100
Exécution
conditionnelle:
Une
clause DO peut contenir toute expression Lisp, en particulier des IF
et des WHEN.
(loop
for i from 1 to 10 do (when (evenp i) (format t "~a; " i)))
2;
4; 6; 8; 10;
NIL seuls
les nombres pairs sont imprimés
Mais
si on veut la somme des nombres pairs, on ne peut pas utiliser sum
dans une clause DO: sum n'a pas de sens au sein d'une forme Lisp.
Heureusement, LOOP a ses propres expressions conditionnelles IF, WHEN
et UNLESS:
(loop
for i from 1 to 10 when (evenp i) sum i)
30
La
construction d'une condition LOOP a la structure suivante:
mot-condition
test clause-loop
où:
- mot-condition est IF ou WHEN ou UNLESS,
- test est une form Lisp,
- clause-loop est une clause d'accumulation de valeurs (count, collect, etc), ou une clause d'exécution inconditionnelle, ou une autre clause d'exécution conditionnelle. On peut relier plusieurs clauses LOOP dans une même condition avec AND.
(loop
for i from 1 to 100
if
(evenp i)
minimize
i into pair-min and
maximize
i into pair-max and
unless
(zerop (mod i 4))
sum
i into total-non-multiple-de-quatre-pair
end
and
sum i into total-pair
else
minimize
i into impair-min and
maximize
i into impair-max and
when
(zerop (mod i 5))
sum
i into total-multiple-de-cinq-impair
end
and
sum i into total-impair
finally
(format t "Minimum pair: ~a,~%Maximum pair: ~a,~%Minimum impair:
~a,~%Maximum impair: ~a,~%Total pair: ~a,~%Total impair: ~a,~%Total
des multiples de cinq impairs: ~a,~%Total des non multiples de 4
pairs: ~a." pair-min pair-max impair-min impair-max total-pair
total-impair total-multiple-de-cinq-impair
total-non-multiple-de-quatre-pair))
Minimum
pair: 2,
Maximum
pair: 100,
Minimum
impair: 1,
Maximum
impair: 99,
Total
pair: 2550,
Total
impair: 2500,
Total
des multiples de cinq impairs: 500,
Total
des non multiples de 4 pairs: 1250.
NIL
la clause
finally permet de retourner le résultat en fin d'itération, alors
que DO, à la place, afficherait un résultat à chaque itération
IT : (clause
de LOOP)
GETHASH:
(defparameter
*h* (make-hash-table))
*H* définit une table de hachage
(voir tables de hachage: plus loin)
(setf
(gethash 'foo *h*) 'toto)
TOTO
(setf
(gethash 'bar *h*) 'tonton)
TONTON
(setf
(gethash 'baz *h*) 'tata)
TATA
définit
des couples (clé valeur)
(loop
for key in '(foo bar) when (gethash key *h*) collect it)
(TOTO
TONTON) collecte les valeurs des clés foo et bar et
la
variable
IT
prend la valeur rendue par le test (gethash key *h*)
INITIALLY:
FINALLY:
deux mots-clés de LOOP, ce sont les clauses de prologue et
d'épilogue
on
peut initialiser des variables en prologue et rendre un calcul du
loop en épilogue
(defparameter
var1 0)
VAR1 définition
d'une variable pour éviter l'erreur dans ce qui suit:
(loop for i from 1 to 10
initially
(setq var1 100)
if
(evenp i)
sum
i into total-paire
finally
(incf var1 total-paire)(format t "Variable + total = ~a."
var1))
Variable
+ total = 130.
NIL en
prologue, var1 est initialisée à 100, juste après l'initialisation
des
variables locales du LOOP et avant le corps du LOOP.
En
épilogue, juste après la dernière itération du LOOP, var1 est
incrémentée de total-paire et le résultat est affiché.
Le prologue a lieu même s'il n'y a pas d'itération.
L'épilogue
n'a pas lieu dans les cas suivants:
-
une clause return s'exécute
-
un appel à RETURN, RETURN-FROM ou une autre structure de contrôle
de saut a lieu depuis une forme Lisp dans le corps du LOOP
-
le LOOP se termine par les clauses always, never ou thereis
Dans
le code de l'épilogue, on peut utiliser RETURN et RETURN-FROM pour
fournir une valeur de retour au LOOP. Cette
valeur s'imposera sur toute autre valeur qui aurait pu être fournie
par une clause d'accumulation ou de fin de test
NAMED:
clause permettant de nommer un LOOP
pour
utiliser RETURN-FROM lorsqu'il y a des LOOPs emboîtés
la
clause NAMED doit être la première dans le LOOP
(loop
named sortie for liste in '((1 5 3 7) (5 6 7) (8 9) (10 11 12 13)) do
(loop
for item in liste do
(if
(evenp item)
(return-from
sortie item))))
6 rend
le premier nombre paire d'une sous-liste en sortant du LOOP
nommé
sortie, sinon, rend NIL
TESTS
d'INTERRUPTION:
WHILE:
UNTIL:
ALWAYS:
NEVER:
THEREIS:
sont des clauses d'interruption d'un LOOP.
(loop
for i from 1 to 1000
while
(< (* i i) 100)
collect
(* i i))
(1
4 9 16 25 36 49 64 81) les itérations se poursuivent tant que
le carré de
i
reste inférieur à 100
(loop
for i from 1 to 1000
until
(>= (* i i) 100)
collect
(* i i))
(1
4 9 16 25 36 49 64 81) les itérations se poursuivent jusqu'à ce
que le
carré
de i devient supérieur ou égal à 100
Dans
ce qui suit, la liste quotée est supposée être une liste de
nombres entiers (sinon evenp produirait une erreur):
(loop
for n in '(0 2 4 6 8) always (evenp n))
T
permet de faire
un test sur tous les éléments d'un ensemble
T,
si tous les éléments vérifient le test
NIL,
s'il existe un élément (au moins) ne le vérifiant pas
Ajoutons un épilogue au LOOP:
(loop
for n in '(0 2 4 6 8)
always
(evenp n)
finally
(print "Tous les nombres sont pairs."))
"Tous
les nombres sont pairs."
T
l'épilogue (finally) est bien évalué si tous les nombres sont
pairs.
Mais:
(loop
for n in '(0 2 4 5 6 8)
always
(evenp n)
finally
(print "Tous les nombres sont pairs."))
NIL
l'épilogue n'est pas évalué, s'il existe un nombre
impair
si
on veut un résultat rendu au lieu de NIL:
(if
(not (loop for n in '(0 2 4 5 6 8)
always
(evenp n)
finally
(print "Tous les nombres sont pairs.")))
"Il
existe un nombre impair.")
"Il
existe un nombre impair."
Un
équivalent à ce qui précède:
(loop
for n in '(0 2 4 6 8) never (oddp n))
T
... Et les mêmes
remarques.
Si
aucun élément n'est impair: T
S'il
existe un élément impair: NIL
THEREIS
rend la valeur évaluée qui suit si non NIL:
(loop
thereis "voici ma valeur."
finally
(print "Ceci ne sera pas rendu."))
"voici
ma valeur."
L'épilogue n'est pas rendu
(loop
for n from 1 to 10
thereis
(> n 10)
finally
(format t "Entre 1 et 10, il n'y a pas de valeur strictement
supérieure à 10 et n prend la valeur ~a en fin d'itération."
n))
Entre
1 et 10, il n'y a pas de valeur strictement supérieure à 10 et n
prend la valeur 11 en fin d'itération.
NIL
Cette
fois, l'épilogue est rendu, après le déroulement complet de toutes
les itérations sans interruption
(loop
for i from 0
thereis
(when (> i 10000) i))
10001 Ici,
thereis permet de sortir de la boucle LOOP (qui pourrait
être
sans fin)
DIGIT-CHAR-P: le 1er argument est un caractère, le 2ème est une base numérique optionnelle (10 par défaut)
rend le caractère si c'est un chiffre reconnu dans la base dite base.
rend NIL sinon. C'est donc un prédicat.
(undefine function dans portacle!)
(digit-car-p #\5)
5 5 est bien un chiffre de la base 10 (valeur de vérité: T)
(digit-char-p #\5 2)
NIL 5 n'est pas un chiffre binaire
(digit-char-p #\B 16)
11 B est bien un chiffre en base hexadécimale (valeur de vérité: T)
remarquez que le chiffre est rendu en base 10.
(digit-car-p #\5)
5 5 est bien un chiffre de la base 10 (valeur de vérité: T)
(digit-char-p #\5 2)
NIL 5 n'est pas un chiffre binaire
(digit-char-p #\B 16)
11 B est bien un chiffre en base hexadécimale (valeur de vérité: T)
remarquez que le chiffre est rendu en base 10.
(loop
for char across "OOO0OO" thereis (digit-char-p char))
0 rend
le premier chiffre rencontré, sinon NIL
il
y a bien un zéro dans la suite des caractères
Pour récupérer tous les chiffres de la chaîne:
(loop for char across "OOBO10" when (digit-char-p char 16) collecting it)
(11 1 0) soit (B 1 0) en hexadécimal
LOOP-FINISH:
macro utilisée dans une forme Lisp d'une clause DO qui produit un
saut immédiat vers l'épilogue du LOOP.
(loop
for i in '(0 1 2 3 4 arrêt 5 6)
when
(symbolp i) do (loop-finish)
count
i)
5
TIME: permet de mesurer la durée d'exécution d'une fonction.
Exemple: soit la fonction suivante:
(defun produit-scalaire (l1 l2)
"rend le produit scalaire de deux listes de nombres."
(loop for x1 in l1
for x2 in l2
sum (* x1 x2)))
(defparameter *p* (make-list 1000000 :initial-element 1))
pour définir une liste avec 1000000 d'éléments initialisés à 1.
(time (produit-scalaire *p* *p*))
Evaluation took:
0.012 seconds of real time
0.012000 seconds of total run time (0.012000 user, 0.000000 system)
100.00% CPU
35,018,585 processor cycles
0 bytes
1000000 résultat de l'opération
le travail a été fait en 0.012 secondes
LOOP semble très rapide: je vous propose de chercher d'autres méthodes
de calcul d'un produit scalaire. Puis comparez avec TIME.
_______________________________________________________
Règles
à suivre pour utiliser LOOP:
-
la première clause est named si elle est nécessaire,
-
après viennent les clauses initially, with, for et repeat,
-
puis viennent les clauses du corps du LOOP: d'exécution
conditionnelle et inconditionnelle, d'accumulation, et test de fin,
-
enfin, les clauses finally.
LOOP
permet:
-
d'initialiser et de déclarer les variables locales du LOOP avec les
clauses with ou for, même si elles sont implicitement créée par
les clauses d'accumulation. Les formes des valeurs initiales sont
évaluées dans l'ordre d'apparition des clauses du LOOP.
-
d'exécuter les formes fournies par les clauses initialy, s'il y en a
dans le prologue, dans l'ordre d'apparition.
-
d'effectuer les itérations et l'exécution du corps du LOOP, comme
indiqué dans le paragraphe suivant.
-
d'exécuter les formes fournies par les clauses finally, s'il y en a
dans l'épilogue, dans l'ordre d'apparition.
Pendant
les itérations, l'exécution du corps du LOOP met, d'abord, à jour
les variables participant aux itérations, en tenant compte des
incréments respectifs, ensuite, lance les exécutions
conditionnelles et inconditionnelles, les
accumulations
et test de fin dans l'ordre du code. Si une clause demande
l'interruption du LOOP, le reste du corps est ignoré et il y a
sortie du LOOP, parfois après l'exécution de l'épilogue.
________________________________________________________
Application de quelques notions précédentes au calcul statistique de la moyenne et de l'écart-type:
(defun liste-simple-p (lst)
"Vérifie si la liste est formée d'atomes numériques entiers."
(cond
((endp lst) t)
((integerp (car lst)) (liste-simple-p (cdr lst)))
(t nil)))
(defun compte-occur (lst)
"Transforme une simple série de nombres (liste-simple) en série statistique ((nb . eff) ...) où nb est un nombre de la série et eff son effectif."
(if (liste-simple-p lst)
(cond
((endp lst) nil)
(t (cons (cons (car lst) (count (car lst) lst)) (compte-occur (remove (car lst) lst)))))
(error "Les nombres de la série doivent être des atomes entiers.")))
(defun moy-stat (lst-stat)
"calcule et affiche l'effectif total et la moyenne de la série statistique passée en argument. Puis en rend la liste (eff-tot moy)."
(loop for (nb . eff) in lst-stat
sum eff into eff-tot
sum (* eff nb) into total
finally (let ((moy (/ total eff-tot))) (format t "Effectif total: ~a.~%Moyenne: ~a.~%" eff-tot moy) (return (list eff-tot moy)))))
(defun variance (lst-stat)
"Calcule et affiche l'effectif total, la moyenne et la variance de la série statistique passée en argument. Rend également la variance."
(multiple-value-bind (eff-tot moy) (values-list (moy-stat lst-stat))
(loop for (nb . eff) in lst-stat
sum (* eff (- nb moy) (- nb moy)) into VARxN
finally (let ((var (float (/ VARxN eff-tot)))) (format t "Variance: ~a~%" var) (return var)))))
(defun écart-type (lst-stat)
"Calcule et affiche l'effectif total, la moyenne, la variance et l'écart-type d'une série statistique. Rend également l'écart-type."
(block sortie
(let ((sigma (sqrt (caddr (variance lst-stat))))) (format t "Ecart-type: ~a." sigma) (return-from sortie sigma))))
(defun dispersion (lst-stat)
"Calcule et affiche l'effectif total, la moyenne, la variance et l'écart-type d'une série statistique."
(let ((sigma (sqrt (caddr (variance lst-stat))))) (format t "Ecart-type: ~a." sigma)))
(dispersion '((5 . 2) (7 . 3) (9 . 1) (10 . 5) (11 . 7) (12 . 7) (13 . 4) (15 . 1) (17 . 2)))
Effectif total: 32.
Moyenne: 11.
Variance: 7.5
Ecart-type: 2.738613.
NIL
________________________________________________________
La prochaine fois, nous verrons Les MACROS.
Aucun commentaire:
Enregistrer un commentaire