samedi 27 décembre 2014

3-Fibonacci

>>>>
Suite de Fibonacci.

Nous allons voir, ici, mille et une façons de calculer les nombres de la suite de Fibonacci.
Enfin... Mille, peut-être pas, mais quelques-unes en utilisant Common Lisp.
Ensuite, on évaluera le temps de calcul pour chaque méthode.
Et….Vous en déduirez ce que vous voudrez !
Bon, très bien. Mais qu’est-ce que la suite de Fibonacci ?
Non, non ! Je ne cherche pas à vexer qui que ce soit, mais simplement à établir les conditions de départ pour nous engager sur une bonne programmation.

La suite de Fibonacci est une suite de nombres, disons, u0, u1, u2, u3, … , un, … (on peut aller jusqu’à l’infini, si vous avez le temps), définis de la façon suivante :
u0 = 0, u1 = 1, u2 = 1 (càd u0 + u1), u3 = 2 (càd u1 + u2), … , un = un-2 + un-1, …
C’est-à-dire que chaque nombre de la suite est égal à la somme des deux précédents.

«u0» vous fait faire la grimace : pourquoi ne pas commencer à 1, comme on fait en général ?
Question de programmation. Avec Common Lisp, les index commencent souvent à zéro. On pourrait commencer à 1, mais j’ai choisis la simplicité.
Donc quand je dirais « Le 1000ème nombre de la suite de Fibonacci », en fait ce sera « le 1001ème ». Que ce soit bien entendu !

Commençons la programmation !

Je vous propose quelques méthodes sans rien vous expliquez : c'est trop bon de découvrir par soi-même ! Reportez-vous aux fonctions données dans un autre article de ce blog ou bien à l’index des fonctions que vous trouverez sur internet : http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/index.html .

J’attire, aussi, votre attention, sur le commentaire inclus dans la définition des fonctions : vous voyez, immédiatement, ce que fait la fonction.
À signaler, également, les commentaires sur la ligne de programme : juste après le « ; ».

Bien sûr, vous avez ouvert EMACS et SLIME dans une fenêtre : quelques copier-coller feront l’affaire (avec quelques problèmes, parfois, comme le passage à la ligne au milieu d'un commentaire).

Avec Common Lisp, une fonction peut faire appelle à … elle-même !
C’est la récursivité.

La récursivité n’est pas toujours bonne à utiliser ; essayer (fibonacci 50), puis (fibo 50) pour comparer les deux fonctions suivantes :

(defun fibonacci (n)
"rend le nième élément de la suite de Fibonacci, mais le temps de calcul croît de façon exponentielle quand n augmente."
(if (= n 0) 0
(if (= n 1) 1
(+ (fibonacci (1- n)) (fibonacci (- n 2)))))) ;cette procédure recalcule fibonacci(n-1) 3 fois!

(defun fibo (n)
"rend le nième élément de la suite de Fibonacci, mais en construisant la suite dans une liste à partir des deux précédents éléments."
(let* ((liste '(1 0)) (num (gensym)) (num n))
(loop for i from 2 to num do
(setf liste (cons (+ (car liste) (car (cdr liste))) liste))) ;le résultat est placé dans la liste, donc il sera lu et non recalculé
(car liste)))

Remarque : la variable num générée par gensym prend la 1ère valeur de n et reste constante dans la boucle loop, même si  : n=(random 10)


Vous dites ? Ces programmes sont vraiment courts !
Oui, c'est comme ça, avec ce … Stop ! Je ne veux pas faire de polémique, mais il y aurait beaucoup à dire dans un sens ou dans l’autre…

(defun fibo2 (n)
"rend le nième élément de la suite de Fibonacci en utilisant la macro DO."
(let ((num (gensym)))
(do ((p 0 (1+ p))
(cur 0 next)
(next 1 (+ cur next))
(num n))
((= num p) cur))))

Même remarque et de plus il n'y a pas de saut prévu pour num dans DO.

Ouvrons une parenthèse : vous pouvez utiliser la fonction suivante (et l'améliorer!) pour présenter les calculs. A la place de « fibo » choisissez la fonction fibo* qui vous convient.

(defun question-fibo ()
(format t "Vous cherchez un nombre de la suite de Fibonacci? (o/n)")
(let ((p (read-char)))
(cond
((string= p "n") "au revoir")
(t (format t "Entrez un nombre entier: ")
(let ((n (read)))
(cond
((integerp n) (format t "Le ~aème nombre de la suite de Fibonacci est: ~a.~%" n (fibo n)) (question-fibo))
(t (question-fibo))))))))

Toujours le calcul d'un nombre de la suite, mais en utilisant une plist(La plist n'occupe-t-elle pas plus d'espace mémoire que la liste précédente dans FIBO?):

(defun mem-fib (n)
(cond
((= n 0) 0)
((= n 1) 1)
((get 'mem-fib n))
(t (setf (get 'mem-fib n) (+ (mem-fib (1- n)) (mem-fib (- n 2)))))))

Vos remarques et explications m'intéressent, débutants ou confirmés.

Même chose, mais, ici, mem-fib est une fonction locale de la fonction fibonacci1:

(defun fibonacci1 (p)
"Rend le pième élément de la suite de Fibonacci, en utilisant une fonction locale récursive mem-fib."
(if (integerp p)
(labels ((mem-fib (n) ; définition de mem-fib
(cond
((= n 0) 0)
((= n 1) 1)
((get 'mem-fib n))
(t (setf (get 'mem-fib n) (+ (mem-fib (1- n)) (mem-fib (- n 2))))))
))
(mem-fib p)) ; appel de mem-fib
(error "~a n'est pas un nombre entier" p)))

Voici deux autres méthodes, très intéressantes, qui font réfléchir. Ayez les définitions de DO et LOOP sous les yeux !

(defun fibo3 (p)
"rend le pième élément de la suite de Fibonacci."
(do ((n 0 (1+ n))
(cur 0 next)
(next 1 (+ cur next)))
((= p n) cur)))

Remarque: c'est fibo2 en moins bien!

(defun fibo4 (n)
"rend le nième élément de la suite de Fibonacci."
(loop for i below n
and a = 0 then b
and b = 1 then (+ b a)
finally (return a)))

Vous voulez me proposer d'autres fonctions fibo, n'hésitez pas : contactez-moi.

J'espère que vous avez passé un bon moment (disons un long plaisir).


Il me reste à parler de la durée d'exécution de ces fonctions :

(time (fibo3 10))

Si ça va trop vite, essayez :

(time (fibo3 10000))

… !!! Tout ça ! Oui, car le résultat de la fonction fibo s'affiche aussi. Et là, le résultat est très grand.

La prochaine fois, nous verrons: "LOOP: tout un programme!".

vendredi 26 décembre 2014

11-ARBRES, ENSEMBLES, TABLES

>>>>
Liste des symboles rencontrés dans ce chapitre:
COPY-LIST COPY-TREE TREE-EQUAL SUBST SUBSTITUTE-IF SUBST-IF SUBST-IF-NOT NSUBST NSUBST-IF NSUBST-IF-NOT ADJOIN PUSHNEW MEMBER MEMBER-IF MEMBER-IF-NOT INTERSECTION UNION SET-DIFFERENCE SET-EXCLUSIVE-OR NINTERSECTION NUNION NSET-DIFFERENCE NSET-EXCLUSIVE-OR SUBSETP ALIST PLIST ASSOC ASSOC-IF ASSOC-IF-NOT RASSOC RASSOC-IF RASSOC-IF-NOT COPY-ALIST PAIRLIS GETF REMF GET-PROPERTIES DESTRUCTURING-BIND &whole

Arbres, ensembles, tables.

 (autres utilisations des cellules CONS, en plus des listes):

FONCTIONS D'ARBRES:

COPY-LIST:
COPY-TREE:

(copy-list '((1 2) (3 4) (5 6)))
((1 2) (3 4) (5 6)) seule la liste est copiée et pointe sur les sous-listes non copiées
(copy-tree '((1 2) (3 4) (5 6)))
((1 2) (3 4) (5 6)) ici, liste et sous-listes sont copiés

TREE-EQUAL:   permet de comparer deux arbres (les arbres sont égaux s'ils ont la même
structure et des "feuilles" EQL ou satisfaisant au mot-clé :test)

SUBST:

(subst 10 1 '(1 2 (3 2 1) ((1 1) (2 2))))
(10 2 (3 2 10) ((10 10) (2 2)))
                 la substitution a lieu dans la liste et les sous-listes (donc dans l'arbre)
 Alors que:

(substitute 10 1 '(1 2 (3 2 1) ((1 1) (2 2))))
(10 2 (3 2 1) ((1 1) (2 2)))
  la substitution n'a lieu que dans la liste (pas dans les sous-listes)

SUBSTITUTE-IF:    (fonction de listes)

(substitute-IF 9 #'oddp '(1 2 4 1 3 4 5))
(9 2 4 9 9 4 9) remplace par 9 tous les nombres impairs

(substitute-IF 9 #'evenp '(1 2 4 1 3 4 5) :count 1 :from-end t)
(1 2 4 1 3 9 5)        remplace par 9 le premier nombre pair trouvé à partir de la fin

SUBST-IF:

(subst-if 5 #'listp '(1 2 (3 2 1) ((1 1) (2 2))))
5                            rend 5 si l'arbre, passé en argument est une liste


SUBST-IF-NOT:

(subst-if-not 5 #'listp '(1 2 (3 2 1) ((1 1) (2 2))))
(5 5 (5 5 5) ((5 5) (5 5)))     tout ce qui n'est pas une liste est remplacé par 5

NSUBST, NSUBST-IF, NSUBST-IF-NOT: sont les versions recyclantes de SUBST (la variable concernée change de valeur).  la structure de l'arbre peut être altérée.

LES ENSEMBLES:

ADJOIN:

(defparameter *set* ())
*SET* définition d'une variable globale

(adjoin 1 *set*)
(1) pour construire un ensemble

*set*
NIL *set* inchangé

(setf *set* (adjoin 1 *set*))
(1) pour modifié l'ensemble (obligation d'utiliser SETF)

on ne peut pas ajouter un élément s'il est déjà dans l'ensemble.
le test d'égalité est EQL par défaut, ou bien
précisé avec la clé :test ou la clé :test-not.
On peut appliquer une fonction sur l'élément à ajouter avec la clé :key.

(setf *set* (adjoin -3 *set* :key #'abs))
(-3 1)        l'élément -3 a été ajouté car sa valeur absolue n'est pas dans l'ensemble

(setf *set* (adjoin -1 *set* :key #'abs))
(-3 1)        par contre, -1 n'est pas ajouté car sa valeur absolue est dans l'ensemble

*set*
(-3 1) ensemble non modifié

(pop *set*)
-3         rend le premier élément de l'ensemble, mais:

*set*
(1) set modifié

PUSHNEW:

(pushnew 2 *set*)
(2 1) pour ajouter un élément dans l'ensemble:

*set*
(2 1)

(pushnew 2 *set*)
(2 1) on ne peut pas ajouter 2 fois le même élément avec PUSHNEW. Mais:
    attention, avec PUSH c'est possible!

MEMBER:

(pushnew 3 *set*)
(3 2 1) on ajoute un 3ème élément

(member 2 *set*)
(2 1) rend le sous-ensemble (sous-liste) à partir de l'élément trouvé
le test d'égalité par défault est EQL

(member 4 *set*)
NIL rend NIL si l'élément n'est pas trouvé.

(member '(3 4) '((1 2) (3 4) (5 6)))
NIL          Ce n'est pas EQL: les pointeurs ne sont pas identiques

    On peut changer le test à l'aide du mot-clé :test

(member '(3 4) '((1 2) (3 4) (5 6)) :test #'equal)
((3 4) (5 6))             avec EQUAL, ce sont les valeurs qui sont comparées (pas les pointeurs vers ces valeurs)
                                 Ne pas oublier #' devant EQUAL

    On peut, aussi, utiliser un autre mot-clé: :key, pour appliquer une fonction
sur chaque élément de la liste:

(member 3 '((1 2) (3 4) (5 6)) :key #'car)
((3 4) (5 6))           Le deuxième élément répond au critère: y a-t-il un élément commençant par 3 dans la liste?

(member 4 '((a b) (3 4 5) (c d e f) (k)) :key #'length)
((C D E F) (K))       Le troisième élément répond au critère: y a-t-il un élément de longueur 4 dans la liste?

Les deux mots-clés peuvent être utilisés ensemble dans un ordre indifférent:
(on peut aussi utiliser le mot-clé :test-not)

(member '(3 7) '((1 2) ((3 7) 4) (5 6)) :test #'equal :key #'car)
(((3 7) 4) (5 6))

pour chercher un élément répondant à un prédicat:

MEMBER-IF:
MEMBER-IF-NOT:

(member-if #'evenp *set*)
(2 1) rend le sous-ensemble à partir du 1er nombre pair rencontré
              (rappel: *set* valait (3 2 1) plus haut)

Autre exemple: soit la fonction prédicat suivante:

(defun divisible/3? (n)
           "rend l'argument s'il est divisible par 3, NIL sinon."
  (if (zerop (mod n 3))
      n
      nil))

(member-if #'divisible/3? '(10 11 12 13 14))
(12 13 14) rend le sous-ensemble à partir du premier nombre divisible par 3 de la liste

(member-if-not #'oddp *set*)
(2 1) rend le sous-ensemble à partir du 1er nombre non-impair rencontré

INTERSECTION:
UNION:
SET-DIFFERENCE:
SET-EXCLUSIVE-OR:

Si *set* est (3 2 1) et *ens* est (2 0 4 5):

(intersection *ens* *set*)
(2) rend un ensemble contenant les éléments communs aux deux ensembles

(union *ens* *set*)
(5 4 0 3 2 1) rend un ensemble contenant tous les éléments des deux ensembles

(set-difference *ens* *set*)
(5 4 0) rend un ensemble contenant les éléments de *ens* qui ne sont pas dans *set*

(set-difference *set* *ens*)
(1 3) rend un ensemble contenant les éléments de *set* qui ne sont pas dans *ens*

(set-exclusive-or *set* *ens*)
(5 4 0 1 3) rend un ensemble contenant tous les éléments de *set* et *ens* qui ne sont pas dans l'intersection de ces deux ensembles

Les fonctions recyclantes correspondantes sont:

NINTERSECTION:
NUNION:
NSET-DIFFERENCE:

NSET-EXCLUSIVE-OR:

(nintersection *ens* *set*)
(2)
*ens*
(2) le 1er ensemble a bien changé

NUNION peut modifier ses deux arguments.

SUBSETP: teste si un ensemble est une partie d'un autre. :key, :test peuvent être utilisés.

(subsetp '(3 2 1) '(1 2 3 4))
T le 1er ensemble est bien sous-ensemble du 2ème.

(subsetp '(4 3 2 1) '(1 2 3))
NIL ici, ce n'est pas le cas.

LISTES D'ASSOCIATION et LISTES de PROPRIETES:

ALIST:
PLIST:

Une alist est formée de couples pointés (on dit aussi des paires): ((A . 1) (B . 2) (C . 3)), par exemple.

(remarquons qu'une liste quelconque (a b c d) est aussi la paire (a . (b c d)).
Le CAR de chaque couple sert de clé d'accès à l'objet dans le CDR du couple:
Pour le couple (A . 1), A est la clé d'accès à l'objet 1.
Pour le couple (a . (b c d)), a est la clé d'accès à l'objet (b c d).

ASSOC:

(assoc 'b '((a . 1) (b . 2) (c . 3)))
(B . 2) rend le couple pointé correspondant à B (CAR). Par défaut, c'est EQL qui est utilisé.

(assoc 'd '((a .1) (b . 2) (c . 3)))
NIL d n'est pas une clé: rend NIL

(cdr (assoc 'b '((a .1) (b . 2) (c . 3))))
2 rend la valeur correspondante à la clé passée en argument

(assoc "b" '(("a" . 1) ("b" . 2) ("c" . 3)) :test #'string=)
("b" . 2)           ici le test est STRING=


Un exemple avec des listes:
On définit une alist sur les préfixes des unités du Système International.
Chaque sous-liste contient le préfixe, suivi de son symbole, et l'exposant de la puissance de 10 qu'il représente.

(defparameter préfixe
   '(("déca" "da" 1) ("hecto" "h" 2) ("kilo" "k" 3) ("méga" "M" 6) ("giga" "G" 9) ("téra" "T" 12) ("péta" "P" 15) ("exa" "E" 18) ("zetta" "Z" 21) ("yotta" "Y" 24) ("déci" "d" -1) ("centi" "c" -2) ("milli" "m" -3) ("micro" "μ" -6) ("nano" "n" -9) ("pico" "p" -12) ("femto" "f" -15) ("atto" "a" -18) ("zepto" "z" -21) ("yocto" "y" -24)))

(assoc "giga" préfixe :test #'string=)
("giga" "G" 9) on obtient la sous-liste correspondant à la clé "giga".

(cdr (assoc "giga" préfixe :test #'string=))
("G" 9)            ici la valeur correspondant à la clé "giga" est une liste.

Pour avoir le facteur multiplicatif correspondant à la clé "giga":

(expt 10 (caddr (assoc "giga" préfixe :test #'string=)))
1000000000

Voici une fonction qui va rendre les choses plus lisibles:

(defun préfixe-SI (clé)
   (multiple-value-bind (x y z) (values-list (assoc clé préfixe :test #'string=)) (format t "Le préfixe ~a a pour symbole ~a et son facteur multiplicateur est ~,,' ,3:d.~%" x y (expt 10 z))))

(préfixe-SI "téra")
Le préfixe téra a pour symbole T et son facteur multiplicateur est 1 000 000 000 000.
NIL            voir format: pour la mise en forme.

Revenons aux tables de hachage.
Nous avions vu une fonction qui permettait de convertir une alist en table de hachage.
Rappelons cette fonction:

(defun alist-vers-table-h (l)
   (if (endp l)
       t
       (progn (setf (gethash (caar l) *h*) (cdar l))
      (alist-vers-table-h (cdr l)))))

Nous allons appliquer cette conversion à la alist préfixe.
On définit, d'abord, une table de hachage:

(defparameter *h* (make-hash-table :test #'equal))
*H*         les clés seront du type string, donc on change le test: EQUAL au lieu de EQL.

(alist-vers-table-h préfixe)
T

(gethash "téra" *h*)
("T" 12)

(car (gethash "téra" *h*))
"T"

Et voici la fonction préfixe-SI modifiée qui utilise la hashtable.
Appelons-la préfixe-SI2:

(defun préfixe-SI2 (clé)
   (multiple-value-bind (x y z) (values-list (push clé (gethash clé *h*))) (format t "Le préfixe ~a a pour symbole ~a et son facteur multiplicateur est ~,,' ,3:d.~%" x y (expt 10 z))))

Elle affiche la même chose que la précédente:

(préfixe-SI2 "téra")
Le préfixe téra a pour symbole T et son facteur multiplicateur est 1 000 000 000 000.

Vous avez, maintenant, le choix entre utiliser une alist ou utiliser une table de hachage.


(defparameter alist '((a . 1) (b . 2) (c . 3)))
ALIST           définition d'une alist

(cons (cons 'a 10) alist)
((A . 10) (A . 1) (B . 2) (C . 3)) ajoute une clé, mais sans changer la alist, ou bien:

(acons 'a 20 alist)
((A . 20) (A . 1) (B . 2) (C . 3)) même chose la alist ne change pas:

alist
((A . 1) (B . 2) (C . 3))

(setf alist (acons 'a 30 alist))
((A . 30) (A . 1) (B . 2) (C . 3)) pour changer la alist

alist
((A . 30) (A . 1) (B . 2) (C . 3)) ou bien:

(push (cons 'd 35) alist)
((D . 35) (A . 30) (A . 1) (B . 2) (C . 3))

alist
((D . 35) (A . 30) (A . 1) (B . 2) (C . 3))

Pour changer la valeur d'une clé:

(setf (cdr (assoc 'b alist)) 25)
25       la place (cdr (assoc 'b alist)) contient, maintenant 25 (au lieu de 2)

alist
((D . 35) (A . 30) (A . 1) (B . 25) (C . 3)) vérification

Mais, attention! S'il y a plusieurs fois la même clé:

(setf (cdr (assoc 'a alist)) 33)
33

alist
((D . 35) (A . 33) (A . 1) (B . 25) (C . 3)) c'est la première occurence qui est changée

Construction d'une alist à partir d'une liste plate avec répétition d'éléments:

(defparameter l '(a e a d a c b d b c a b))
L

(defun occur (lst)
           "rend des doublets formés des éléments de la liste et de leur occurence."
  (if (null lst)
      nil
      (apply #'list (cons (car lst) (count (car lst) lst)) (occur (remove (car lst) lst)))))

(occur l)
((A . 4) (E . 1) (D . 2) (C . 2) (B . 3))

Ces doublets peuvent être rangés dans l'ordre croissant ou décroissant des occurences:
On récupère les fonctions INS et TRI définies précédemment (voir tri:), en les adaptant à la situation.

(defun ins-doublet (x l)
  "insère un doublet dans une liste déjà triée."
  (if (null l) (list x)
      (if (> (cdr x) (cdr (car l)))
  (cons x l)
  (cons (car l) (ins-doublet x (cdr l))))))

(defun tri-doublet (l)
  "trie une liste de doublets dont le 2ème élément est un nombre, dans le sens décroissant de ces nombres."
  (if (null l) nil
      (ins-doublet  (car l) (tri-doublet (cdr l)))))

(tri-doublet (occur l))
((A . 4) (B . 3) (C . 2) (D . 2) (E . 1))

________________________
Utilisation des alists et des fermetures (voir fermetures: ou closures:).
La fonction suivante permet de créer une alist et une liste de fermetures agissant sur la alist:

(defun make-alist (db)
  (setf
   (symbol-function 'pays-de) #'(lambda (key) ;chaque fermeture lambda est nommée à l'aide de la fonction SYMBOL-FUNCTION
    (cdr (assoc key db)))
   (symbol-function 'ajouter-ville-pays) #'(lambda (key val)
(push (cons key val) db) key)
   (symbol-function 'supprimer-ville) #'(lambda (key)
 (setf db (delete key db :key #'car)) key))
  (list 'pays-de 'ajouter-ville-pays 'supprimer-ville))

(defparameter villes nil)
VILLES      pour éviter les messages d'erreur

(setq villes (make-alist '((berlin . allemagne) (paris . france) (londres . angleterre))))
(PAYS-DE AJOUTER-VILLE-PAYS SUPPRIMER-VILLE)
                                         définit une alist explicitement donnée ici
    rend la liste des fermetures agissant sur la alist

(pays-de 'paris)
FRANCE rend le pays correspondant à la clé paris de la alist

(ajouter-ville-pays 'madrid 'espagne)
MADRID    ajoute (madrid . espagne) à la alist

(pays-de 'madrid)
ESPAGNE vérification

(supprimer-ville 'paris)
PARIS supprime (paris . france) de la alist

(pays-de 'paris)
NIL vérification

Construire une documentation des fonctions exportables de COMMON-LISP  en français:

Vous pouvez copier ce qui suit dans un fichier-texte appelé "docfr.lisp", entre
début du fichier et fin du fichier. Les ; sont là pour indiquer les commentaires.
Ce fichier n'est pas parfait. Mais le but est de vous faire réviser certaines notions.
Essayez de "décortiquer" les fonctions qu'il contient.
Revenez sur ce que vous avez déjà vu.
Améliorez! Simplifiez!
...Si vous le voulez bien.



;;;Début du fichier DOCFR.lisp
;;;Beginn der DOCFR.lisp-Datei

;;;Ces fonctions autorisent l'accès à la documentation des fonctions COMMON-LISP:
;;;Diese Funktionen ermöglichen den Zugriff auf die Dokumentation der COMMON-LISP-Funktionen:
;;;En français si elles existent dans le fichier "doc-fr-fonctions",
;;;Auf Deutsch, wenn sie in der Datei "doc-DE-Funktionen" vorhanden sind,
;;;sinon en anglais.
;;;Il y a possibilité de traduire en français et d'étoffer ce fichier.
;;;Es ist möglich, ins Deutsche zu übersetzen und diese Datei zu erweitern.

;;;La fonction suivante permet de voir les symboles internes d'un package:
;;;Il n'y en a pas dans "COMMON-LISP", Mais il y en a dans "CL-USER" si vous avez travaillé dans
;;;ce package. En général, "CL-USER" n'a pas de symboles exportables, sauf si vous en avez défini.

(defun symboles-internes (package)
           "Rend la liste des symboles internes du package."
   (let ((résultat nil)) ;la liste résultat est initialisée à vide (NIL)
     (do-symbols (symb package) ;itération sur tous les symboles du package
       (when (eq (second (multiple-value-list (find-symbol (symbol-name symb) package))) :internal)
;quand la 2ème valeur rendue par FIND-SYMBOL est :internal...
(push symb résultat)))       ;...le symbole est ajouté à la liste résultat
     (delete-duplicates résultat))) ;on supprime les résultats dupliqués

;;;La fonction suivante permet de voir les symboles exportables d'un package.
;;;Le package "COMMON-LISP" en possède plus de 900: pour ne pas encombrer votre écran,
;;;n'abusez pas de cette fonction. Utilisez plutôt la fonction aléa-doc-de qui n'affiche
;;;qu'une fonction à la fois.

(defun symboles-externes (package)
       "Rend la liste des symboles exportables du package"
      (let ((résultat nil))
(do-external-symbols (s package)
  (push s résultat))
résultat))

;;;initialisation

(defparameter fich nil)
(defparameter pack nil)
(defparameter clé nil)
(defparameter doc nil)
(defparameter r nil)
(defparameter mon-path nil)
(defparameter entrée nil)
(defparameter listef nil)
(defparameter sortie nil)
(defparameter fonctions-doc nil)
(defparameter caract nil)
(defparameter chaine nil)

;;;Fonction servant à fabriquer une alist et des fermetures (fonctions) agissant dessus:

(defun make-alist-fonctions (db)
           "Cette fonction est utilisée par ACCèS-DOC-FR pour définir 4 fermetures agissant sur la documentation
des fonctions COMMON-LISP en français."
   (setf     ;définition de 4 fermetures utilisant la lambda-fonction.
    (symbol-function 'doc-de) #'(lambda (clé)
  (cdr (assoc clé db)))
    (symbol-function 'ajouter-fonction-doc) #'(lambda (clé val)
(push (cons clé val) db) clé)
    (symbol-function 'retirer-fonction-doc) #'(lambda (clé)
(setf db (delete clé db :key #'car)) clé)
    (symbol-function 'voir-la-liste-complète) #'(lambda () db))
   (list 'doc-de 'ajouter-fonction-doc 'retirer-fonction-doc 'voir-la-liste-complète))  ;rend la liste des fonctions-fermetures ainsi définies

;;;Pour accéder au fichier "doc-fr-fonctions":
;;;Cette fonction accès-doc-fr utilise make-alist-fonctions:

(defun accès-doc-fr (fich) ;fich doit être le nom du fichier écrit entre guillemets
   "Définit une alist (fonctions-doc) à partir du fichier fich et rend la liste des fermetures qui agissent sur la alist."
   (labels ((accès-doc (fic)     ;la fonction accès-doc est définie dans le LABELS de la fonction accès-doc-fr
   "Cette fonction ouvre un flux vers le fichier fic, lit le fichier, referme le flux et rend le contenu du fichier fic."
      (defparameter listef ()) ;la variable listef est initialisée à vide
      (let ((*default-pathname-defaults* '#P"/home/trochet/LISP/Clisp/"))   ;à modifier suivant le chemin du fichier fich
(defparameter mon-path (make-pathname :name fic))
(defparameter entrée (open mon-path :direction :input))
(setq listef (read entrée))
(close entrée) listef))) ;la fonction accès-doc rend le contenu de listef lu dans le fichier
     (setq fonctions-doc (make-alist-fonctions (accès-doc fich)))))     ;la variable fonction-doc contient la alist définie par le fichier fich

;;;L'appel pour avoir accès au fichier de documentations en français et aux fonctions-fermetures est simple:

(accès-doc-fr "doc-fr-fonctions")

;;;La fonction aléa-doc-de a besoin de la fonction symboles-externes pour trouver les symboles du package:
;;;C'est la fonction principale à utiliser:
;;;elle affiche une fonction au hasard et sa documentation.

(defun aléa-doc-de (pack) 
           "Tire au hasard une fonction exportable du package passé en argument et affiche sa documentation."
           (setq *random-state* (make-random-state))
   (let* ((liste (symboles-externes pack)) (clé (nth (random (length liste)) liste))) ;clé contient un symbole tiré au hasard
     (if (doc-de clé) ;si le symbole a une documentation en français...
(format t "Doc de la fonction ~a: ~a~%" clé (doc-de clé)) ;on affiche le symbole et sa documentation
(if (fboundp clé) ;sinon si le symbole est bien une fonction...
     (format t "Doc de la fonction ~a: ~a~%" clé (documentation clé 'function)) ;on affiche le symbole et sa documentation en anglais
     (aléa-doc-de pack))))) ;sinon on fait un appel récursif de aléa-doc-de

(defun ad (package)
           "ad est un raccourci de aléa-doc-de."
   (aléa-doc-de package))

;;;Voici la fonction à ne pas oubier en fin de travail pour mettre à jour le fichier.
;;;Cette fonction utilise la fermeture voir-la-liste-complète.

(defun sauve-doc-fr (fich) 
           "Permet d'enregistrer les données saisies dans le fichier de documentation des fonctions
COMMON-LISP en français."
   (let ((*default-pathname-defaults* '#P"/home/trochet/LISP/Clisp/"))  ;à modifier suivant le chemin du fichier fich
     (defparameter mon-path (make-pathname :name fich))  ;définition du path vers vers le fichier fich
     (defparameter sortie (open mon-path :direction :output :if-exists :supersede))  ;définition du flux en sortie
     (prin1 (voir-la-liste-complète) sortie)  ;envoie de la alist vers le flux
     (close sortie)))  ;fermeture du flux et enregistrement du fichier fich.

;;;Pour se simplifier la vie:

(defun afd (clé val)
   (ajouter-fonction-doc clé val))

(defun rfd (clé)
           (retirer-fonction-doc clé))



;;;Pour éviter un démarrage identique dans SLIME:

(defun réglage-aléa ()
      "initialise la fonction random pour ne pas avoir les mêmes résultats."
      (do ((i 0 (1+ i))) ((>=  i (length (voir-la-liste-complète)))) (random 1.0)))

(réglage-aléa)

;;;Accès ergonomique:

(defun read-default (val-def) ;val-def contient la valeur par défaut (quand on tape Entrée sans rien saisir)
   (setq caract (read-char)) ;permet de lire le 1er caractère tapé et de le placer dans la variable caract
   (if (string= caract #\Newline) ;si caract contient Newline (donc si on a tapé Entrée)...
       (setq chaine val-def) ;...la fonction rend val-def
       (concatenate 'string (string caract) (string-downcase (string (read)))))) ;...sinon la fonction rend la chaine saisie entièrement

(defun o-ou-n-p (question)
   "Affiche une question dont la réponse sera oui ou non, et attend une réponse:
rend T si la réponse est affirmative, NIL sinon."
   (setq question (concatenate 'string question " ")) ;pour la présentation de l'affichage
   (format t "~%~a" question) ;affichage de la question
   (let ((rép (ignore-errors (read)))) ;attente de la réponse
     (case rép ;traitement de la réponse
       ((o oui yes y ja) t)
       ((n non no nein) nil)
       (t (princ "Répondez par oui ou non svp. ") (o-ou-n-p question))))) ;cas d'une réponse inattendue

;;;La fonction suivante permet de faciliter le travail.
;;;Attention: dans le cas où vous voulez ajouter une documentation (rép = 2), n'oubliez pas
;;;les guillemets, sinon le résultat sera très inattendu.
;;;Je vous laisse résoudre ce problème.

(defun gestion-doc-fr ()
   "Cette fonction permet de choisir ce que vous voulez faire."
   (format t "Quel fichier voulez-vous ouvrir? Par défaut: \"doc-fr-fonctions\" ~%")
   (setq fich (read-default "doc-fr-fonctions"))
   (format t "Sur quel package intervenez-vous? Par défaut: \"CL\" ~%")
   (setq pack (string-upcase (read-default "cl")))
   (accès-doc-fr fich)
           (tagbody reprise
      (format t "Choisissez une action parmi les suivantes:~%
          1- Une documentation au hasard.~%
2- Ajouter une documentation.~%
3- Retirer une documentation.~%
4- Voir la documentation d'une fonction.~%
5- Sauvegarder vos saisies.~%") ;;;block
      (do ((x 0 (1+ x))) ((> x 5))
                (if (= x 5) (format t "Je vous conseille de sauvegarder vos saisies.~%"))
(format t "Tapez au choix: 1, 2, 3, 4 ou 5 --> ")
(let ((rép (ignore-errors (read))))
  (case rép
    (1 (format t "~a ~%" (ad pack)))
    (2 (format t "Fonction: ") (setq clé (read)) (format t " Documentation (entre guillemets): ") (setq doc (read)) (afd (car (multiple-value-list (intern (string-upcase clé)))) doc))
    (3 (format t "Fonction: ") (setq clé (read)) (rfd clé))
    (4 (format t "Quelle fonction? ~%") (setq r (read)) (format t "~a ~%" (doc-de r)))
    (5 (sauve-doc-fr fich))
    (t (format t "Cette réponse n'est pas autorisée. ~%") (setq rép nil) (go reprise)))))
  (if (o-ou-n-p "voulez-vous continuer ce travail?~%")
      (go reprise)
      (progn (sauve-doc-fr fich) (format t "Vous avez ~a fonctions définies en français dans votre fichier.~%" (length (voir-la-liste-complète)))))))   ;sauvegarde automatique en fin de travail

;;;Pour afficher quelques explications:

(let ((flux (open "~/LISP/texte-doc-fr" :if-does-not-exist nil)))  ;à modifier suivant le chemin du fichier.
   (when flux
     (loop for ligne = (read-line flux nil) ;itération pour chaque ligne lue ou fin de fichier (NIL)
  while ligne do (format t "~a~%" ligne)) ;écriture tant que la fin de fichier n'est pas atteinte
     (close flux)))          ;fermeture du flux

(gestion-doc-fr)

;;;Fin du fichier
;;;Dateiende


Voici le fichier "texte-doc-fr":



Vous venez de charger le fichier \"docfr.lisp\". Cela vous permet de consulter de façon aléatoire
la documentation sur les fonctions exportables du package COMMON-LISP en français (si elle existe) ou
en anglais sinon.
Vous pouvez, si vous le voulez bien, traduire le texte en anglais pour étoffer le fichier en français.
Vous disposez des fonctions suivantes:
DOC-DE dont l'argument est une fonction (quotée). Cette fonction rend la documentation en français si elle existe.
sinon NIL.
AJOUTER-FONCTION-DOC ou AFD dont le 1er argument est une fonction (quotée) et le 2ème argument la documentation
en français sous forme d'une chaîne de caractères (donc entre guillemets).
RETIRER-FONCTION-DOC ou RFD dont l'argument est une fonction (quotée). Cette fonction permet de supprimer une documentation
du fichier (en cas d'erreur par exemple).
ALéA-DOC-DE ou AD dont l'argument est un package (:cl-user par exemple). Cette fonction affiche une documentation
d'une fonction tirée au hasard.
SAUVE-DOC-FR dont l'argument est le fichier de sauvegarde \"doc-fr-fonctions\". Attention de ne pas oublier cette
sauvegarde si vous modifiez le contenu.
Ces fonctions peuvent être utilisées à la volée, mais il est plus facile de gérer cela avec la fonction:
GESTION-DOC-FR qui est lancée au chargement du fichier DOCFR.LISP comme vous le constatez.



Vous devrez, fort probablement, modifier les chemins d'accès aux deux fichiers "docfr.lisp" et "texte-doc-fr" dans
les fonctions du fichier "docfr.lisp". Les lignes à modifier sont indiquées dans les commentaires.
Mieux! Vous pouvez sortir la variable *defaut-pathname-defaults* des fonctions et la définir selon le chemin que vous avez choisi.
Prenez alors le même chemin pour le fichier texte "texte-doc-fr" et, bien sûr, enregistrez vos deux fichiers dans le répertoire
qui se trouve au bout du chemin.
...Il faut bien que je vous fasse travailler un peu!

Si vous avez quitté la fonction gestion-doc-fr et que vous n'avez pas quitté SLIME-REPL, vous pouvez reprendre facilement ce travail.
Il suffit de taper: (gest et la touche de tabulation (dans EMACS) qui sert à la complétion, puis ) + ENTREE.
Je vous conseille d'utiliser Hyperspec et Google-traduction pour construire votre fichier.
Vous pouvez affiner la traduction dans Google-traduction pour que tout le monde en profite.
Avec quelques copier-coller votre documentation de fonctions prend forme petit à petit.

Si vous n'utilisez pas la fonction gestion-doc-fr qui est cependant bien pratique,
voilà ce que vous pouvez faire:

(accès-doc-fr "doc-fr-fonctions")  ;pour lancer l'application

(doc-de 'car)
("rend le 1er objet de la liste.")
Si la doc de CAR est déjà dans le fichier évidemment!

(ajouter-fonction-doc 'cdr "Rend la liste sans son 1er objet.")
CDR
Pour ajouter la doc d'une fonction


(retirer-fonction-doc 'car)
CAR
Pour retirer la doc d'une fonction
Dans ce cas:

(doc-de 'car)
NIL

(ajouter-fonction-doc 'car "Rend le 1er objet de la liste.")
CAR

(doc-de 'car)
"Rend le 1er objet de la liste."

La fonction voir-liste-complète n'est pas conseillée si le fichier est volumineux (toutes les documentations vont s'afficher).
(cette fonction n'a pas besoin d'argument.)
La fonction aléa-doc-de avec comme argument :cl permet d'afficher la documentation d'une fonction de "COMMON-LISP"
en français si elle existe, sinon en anglais (à vous alors de proposer une traduction avec ajouter-fonction-doc).
Comme ces deux fonctions sont sensées être souvent utilisées, on peut se munir de deux raccourcis: ad et afd.
La fonctions retirer-fonction-doc permet de supprimer la documentation d'une fonction (raccourci: rfd).
Enfin, vous avez la fonction sauve-doc-fr qui permet de sauvegarder le fichier "doc-fr-fonctions" immédiatement.
Son argument peut être la variable globale fich.
Par sécurité, en répondant "non" à la question "voulez-vous continuez...?", une sauvegarde automatique est effectuée.

Comme le fichier doit exister au départ, on peut, au début, construire la liste  de fermetures de cette façon:

(setq fonction-doc (make-alist-fonctions ((a . b) (c . d))))

Il suffira par la suite de retirer les clé a et c, juste avant de sauvegarder les nouvelles clés.


________________________


ASSOC-IF:
ASSOC-IF-NOT:

(assoc-if #'(lambda (x) (string> x "a")) '(("a" . 1) ("b" . 2)))
("b" . 2)       rend le 1er couple pointé qui vérifie la condition

(assoc-if-not #'(lambda (x) (string> x "a")) '(("a" . 1) ("b" . 2)))
("a" . 1)                   idem

RASSOC:

Rappel:
alist
((D . 35) (A . 30) (A . 1) (B . 25) (C . 3))       Vous pouvez redéfinir cette alist si vous l'avez perdue.

(rassoc 30 alist)
(A . 30) rend le 1er couple pointé dont la valeur (CDR) est passée en argument

RASSOC-IF:
RASSOC-IF-NOT:

(rassoc-if #'evenp alist)
(A . 30) rend le 1er couple pointé dont la valeur (CDR) est paire

(rassoc-if-not #'(lambda (x) (>= x 30)) alist)
(A . 1) rend le 1er couple pointé dont la valeur n'est pas supérieure ou égale à 30

COPY-ALIST:

(copy-alist alist)
((D . 35) (A . 30) (A . 1) (B . 2) (C . 3)) rend une copie de la alist

PAIRLIS:

(pairlis '(a b c) '(1 2 3))
((C . 3) (B . 2) (A . 1)) construit une alist à partir d'une liste de clés et d'une liste de valeurs

(reverse (pairlis '(a b c) '(1 2 3)))
((A . 1) (B . 2) (C . 3)) pour avoir la alist dans l'ordre initial des clés


Une plist est une simple liste où clés et valeurs alternent: (A 1 B 2 C 3), par exemple.
Il n'y a qu'une seule fonction fondamentale supportée par la plist: GETF.
Le test par défaut est EQ et non EQL. En conséquence, les clés de plist ne doivent pas être des nombres ou des caractères.
L'exemple, plus bas, montre des plistes qui semblent accepter des nombres comme clés.

GETF:

(getf '(a 1 b 2 c 3) 'a)
1 rend la valeur qui suit la clé A

(defparameter *plist* ())
*PLIST* définit une plist vide

*plist*
NIL

(setf (getf *plist* :a) 1)
1 C'est le premier argument de SETF qui est traité comme la place à modifier

*plist*
(:A 1)

(setf (getf *plist* :a) 2)
2 la valeur de la clé est changée

*plist*
(:A 2)

(setf (getf *plist* :b) 3)
3 une nouvelle clé et une nouvelle valeur sont placées devant la plist

*plist*
(:B 3 :A 2)

____________________

Pour savoir la date et l'heure de ce jour en ce lieu, on utilise, ici, deux plists: *pjour* et *pmois*.

(let ((*pjour* '(0 lundi 1 mardi 2 mercredi 3 jeudi 4 vendredi 5 samedi 6 dimanche))
(*pmois* '(1 janvier 2 février 3 mars 4 avril 5 mai 6 juin 7 juillet 8 août 9 septembre 10 octobre 11 novembre 12 décembre)))
(multiple-value-bind (secondes minutes heures date mois année jour) (get-decoded-time)
(format t "Il est ~a heures ~a minutes ~a secondes.~%" heures minutes secondes)
(format t "Nous sommes le ~(~a~) ~a ~(~a~) ~a.~%" (getf *pjour* jour) date (getf *pmois* mois) année)))
Il est 19 heures 34 minutes 37 secondes.
Nous sommes le dimanche 12 avril 2015.
NIL
____________________

REMF: (utilise, comme GETF, EQ pour comparer les clés)

(remf *plist* :a)
T supprime la clé :a ...

*plist*
(:B 3) ...et sa valeur!

GET-PROPERTIES: prend en argument une plist et une liste de clés

si *plist* vaut (:c 7 :b 3):

(get-properties *plist* '(:b :c))
:C     rend la première clé trouvée dans la plist, parmi celles proposées
7 rend, aussi, sa valeur
(:C 7 :B 3) et la sous-plist à partir de cette clé trouvée

Les plists ont des relations particulières avec les symboles: chaque symbole est associé à une plist utilisée pour garder des informations concernant ce symbole.
(Cela semble aussi fonctionner sur des nombres: essayez!)

(setf (get 'info :b) "information sur :b")
"information sur :b" définit une info sur le symbole :b

(setf (get 'info :c) "information sur :c")
"information sur :c" définit une info sur le symbole :c

(symbol-plist 'info)
(:C "information sur :c" :B "information sur :b") rend la plist donnant pour chaque symbole son info

(get 'info :c)
"information sur :c" rend l'info pour le symbole :c

(ou bien: (getf (symbol-plist 'info) :c))

(remprop 'info :c)
(:C "information sur :c" :B "information sur :b") supprime l'info concernant le symbole :c

(ou bien: (remf (symbol-plist 'info) :c))

(get 'info :c)
NIL en effet, il n'y a plus d'info sur :c

DESTRUCTURING-BIND: (macro)

(destructuring-bind (x y z) (list 1 2 3) (list :x x :y y :z z))
(:X 1 :Y 2 :Z 3)         rend une plist dont les clés sont :x :y :z et les valeurs 1 2 3 respectivement

(destructuring-bind (x y z) (list 1 (list 2 20) 3) (list :x x :y y :z z))
(:X 1 :Y (2 20) :Z 3)           rend une plist dont les clés sont :x :y :z et les valeurs 1 (2 20) 3

(destructuring-bind (x (y1 y2) z) (list 1 (list 2 20) 3) (list :x x :y1 y1 :y2 y2 :z z))
(:X 1 :Y1 2 :Y2 20 :Z 3)     rend une plist dont les clés sont :x :y1 :y2 :z et les valeurs 1 2 20 3

(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2 20) 3) (list :x x :y1 y1 :y2 y2 :z z))
(:X 1 :Y1 2 :Y2 20 :Z 3)     même chose, mais la valeur de y2 est optionnelle:

(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2) 3) (list :x x :y1 y1 :y2 y2 :z z))
(:X 1 :Y1 2 :Y2 NIL :Z 3)    ici, :y2 n'a pas de valeur

(destructuring-bind (&key a b c) (list :a 1 :b 2 :c 3) (list :x a :y b :z c))
(:X 1 :Y 2 :Z 3)

(destructuring-bind (&key a b c) (list :a 1 :c 3) (list :x a :y b :z c))
(:X 1 :Y NIL :Z 3)

(destructuring-bind (&key a b c) (list :c 1 :b 2 :a 3) (list :x a :y b :z c))
(:X 3 :Y 2 :Z 1)

&whole:   un autre paramètre.

(destructuring-bind (&whole whole &key a b c) (list :c 1 :b 2 :a 3) (list :x a :y b :z c :whole whole))
(:X 3 :Y 2 :Z 1 :WHOLE (:C 1 :B 2 :A 3))

Bientôt: "Les FICHIERS et les ENTREES/SORTIES: (input/output:)".

samedi 13 décembre 2014

10-Compléments sur quelques fonctions.

>>>>
Liste des symboles rencontrés dans ce chapitre:
CONS CAR CDR FIRST REST LIST NCONC APPEND SUBSTITUTE NSUBSTITUTE REVERSE NREVERSE TAILP LIST* CONSP ATOM LISTP NULL MAPCAR MAPLIST MAPCAN MAPCON MAPC MAPL 

Compléments sur quelques fonctions.

CONS:
CAR:
CDR:
FIRST:
REST:

(cons 1 2)
(1 . 2)

(car (cons 1 2))
1

(first (cons 1 2))
1 FIRST est synonyme de CAR (moins dénué de sens)

(cdr (cons 1 2))
2

(rest (cons 1 2))
2 REST est synonyme de CDR (moins dénué de sens)

CAR, CDR, FIRST, REST sont SETF-ables (pas LAST):

(defparameter *cons* (cons 1 2))
*CONS*

*cons*
(1 . 2)

(setf (car *cons*) 10)
10

*cons*
(10 . 2)

(setf (first *cons*) 15)
15

*cons*
(15 . 2)

(setf (cdr *cons*) 20)
20

*cons*
(15 . 20)

(setf (rest *cons*) 25)
25

*cons*
(15 . 25)

Lisp réduit automatiquement les valeurs pointées quand cela correspond à des listes.

'(1 . nil)
(1)

'(1 . (2))
(1 2)

(cons 1 nil)
(1)

(cons 1 (cons 2 nil))
(1 2)

(cons 1 (cons 2 (cons 3 nil)))
(1 2 3)

Ces 3 lignes précédentes s'écrivent plus simplement avec la fonction LIST:

(list 1)
(1)

(list 1 2)
(1 2)

(list 1 2 3)
(1 2 3)

Et aussi:

(cons 'a (cons 'b nil))
(A B)

(list 'a 'b)
(A B)

Les cellules cons peuvent contenir tout type de valeurs, donc les listes aussi.

NCONC:
APPEND:

(defparameter *x* (list 1 2 3))
*X*

(nconc *x* (list 4 5 6))
(1 2 3 4 5 6)

*x*
(1 2 3 4 5 6) *x* change

Alors que:

(append *x* (list 7 8 9))
(1 2 3 4 5 6 7 8 9)

*x*
(1 2 3 4 5 6) *x* ne change pas

SUBSTITUTE:
NSUBSTITUTE:

(substitute 10 1 *x*)
(10 2 3 4 5 6)

*x*
(1 2 3 4 5 6) *x* ne change pas

(nsubstitute 20 2 *x*)
(1 20 3 4 5 6)

*x*
(1 20 3 4 5 6) *x* change

REVERSE:
NREVERSE:

(reverse *x*)
(6 5 4 3 20 1)

*x*
(1 20 3 4 5 6) *x* ne change pas

(nreverse *x*)
(6 5 4 3 20 1)

*x*
(1) *x* change avec destruction


TAILP:

(tailp 2 '(3 4 1 . 2))
T     rend T si l'objet passé en 1er argument se trouve dans la dernière
  cellule de la liste passée en 2ème argument

(tailp nil (list 1 2))
T la dernière cellule d'une liste contient souvent NIL

(list* 1 2 3 4)
(1 2 3 . 4)     place le dernier argument dans la dernière cellule

(tailp 4 (list* 1 2 3 4))
T           4 est bien dans la dernière cellule

(tailp 4 '(1 2 3 4))
NIL              le dernier élément n'est pas dans la dernière cellule

CONSP:

(consp '(a b c))
T              teste si l'argument est un doublet (une liste non vide)

(consp ())
NIL              NIL n'est pas un doublet

(consp '(a . b))
T

ATOM: opérateur primitif n°2, c'est un prédicat qui rend T ou NIL
tout ce qui n'est pas un doublet est un atome et réciproquement.

(atom 1)
T            teste si l'argument est un atome (le chiffre 1 est un atome)
             c'est à dire n'est pas un doublet

(atom 'a)
T     la lettre a (quotée!) est un atome

(atom #(1 2 3))
T           un vecteur est un atome

(atom '(1 2 3))
NIL           une liste n'est pas un atome

(atom nil)
T           NIL n'est pas un doublet

On en déduit que (atom x) est équivalent à (not (consp x))

LISTP:

(listp '(a b c))
T          teste si l'argument est un doublet ou est NIL

(listp ())
T          NIL n'est pas un doublet, mais est bien une liste

(listp '(a . b))
T          Un doublet est une liste

NIL est donc un atome et une liste à la fois.

NULL:

(null '(a b c))
NIL          teste si l'argument est NIL

(null ())
T         (prédicat de vacuité)

(null '(nil))
NIL         la liste (nil) n'est pas vide


LES MAPs:

MAPCAR:

MAPCAR n'a pas besoin du type rendu comme avec MAP.
Il lui faut la fonction (1er argument), la ou les listes (les autres arguments) dont les éléments
sont les arguments de la fonction. MAPCAR rend une liste de résultats de la fonction.
MAPCAR s'applique au CAR des CDR successifs d'une liste propre (liste initiale comprise).
Il en est de même de MAPC et MAPCAN.
MAPC, MAPCAR et MAPCAN sont dits de la même famille.
MAPL, MAPLIST et MAPCON forme une autre famille: elles s'appliquent aux listes initiales, puis aux CDR successifs de ces listes.

(mapcar #'(lambda (x) (* 2 x)) (list 1 2 3))
(2 4 6)     rend le double de chaque élément de la liste

(mapcar #'+ (list 1 2 3) (list 10 20 30))
(11 22 33)  rend la somme des éléments des deux listes.

Si les listes n'ont pas le même nombre d'éléments, c'est une liste de
moindre éléments qui est rendue:

(mapcar #'+ (list 1 2 3) (list 10 20))
(11 22)     2 éléments rendus au lieu de 3

(mapcar #'car '((1 a) (2 b) (3 c)))
(1 2 3)     rend le premier élément de chaque liste de la liste

(mapcar #'abs '(3 -4 5 -8 -12))
(3 4 5 8 12)    rend la valeur absolue de chaque élément de la liste

(mapcar #'cons '(a b c) '(1 2 3))
((A . 1) (B . 2) (C . 3))     rend une liste de doublets pointés

(mapcar #'(lambda (x) (elt x 0)) '("arbre" "branche" "feuille"))
(#\a #\b #\f)   rend la liste des premières lettres de chaque mot de la
          liste de mots passée en argument

(mapcar #'(lambda (x) (subseq x 1)) '("arbre" "branche" "feuille"))
("rbre" "ranche" "euille")
          rend la liste des mots sans leur première lettre

(mapcar #'(lambda (x) (string x)) '(quote nil eval cons list car cdr append))
("QUOTE" "NIL" "EVAL" "CONS" "LIST" "CAR" "CDR" "APPEND")
              transforme la liste, quotée, de symboles en une liste de chaines de caractères

(sort (mapcar #'(lambda (x) (string x)) '(quote nil eval cons list car cdr append)) #'string<)
("APPEND" "CAR" "CDR" "CONS" "EVAL" "LIST" "NIL" "QUOTE")
        même chose avec rangement par ordre alphanumérique

Voici une fonction qui rend la somme des carrés des nombres passés en argument:

(defun Somme-carrés (&rest nb)
   (multiple-value-call #'+ (values-list (mapcar #'(lambda (x) (* x x)) nb))))

(somme-carrés 9 10 11)
302

Composition de fonctions appliquée à une liste:

(defun mapcompose (fn gn lst)
  (mapcar fn (mapcar gn lst)))

(mapcompose #'(lambda (x) (* 2 x)) #'(lambda (x) (+ x 3)) '(1 2 3))
(8 10 12)   applique la fonction 2(x+3) aux éléments de la liste

(mapcompose #'(lambda (x) (+ x 3)) #'(lambda (x) (* 2 x)) '(1 2 3))
(5 7 9)   applique la fonction 2x+3 aux éléments de la liste

Voici une fonction qui rend la composée de deux fonctions fn et gn à une variable:

(defun composée (fn gn)
  #'(lambda (x) (funcall fn (funcall gn x))))

(functionp #'composée)
T    composée est bien une fonction (de fonction!)

(funcall (composée #'(lambda (x) (+ 5 x)) #'(lambda (x) (* 2 x))) 7)
19 la 2ème fonction λ multiplie x par 2 et la 1ère ajoute 5.
la composée des deux calcule le résultat pour x=7 (donc 2x7+5=19)

(funcall (composée 'acos 'sin) pi)
1.5707963267948966d0 on obtient bien pi/2

MAPLIST:

MAPLIST n'a pas besoin du type rendu comme avec MAP.
Il lui faut la fonction (1er argument), la ou les listes (les autres arguments), elles mêmes arguments de la fonctions.
Rend la liste des résultats de la fonction passée en argument.
MAPLIST s'applique au REST successifs des listes.

(maplist #'append '(a b c d) '(1 2 3) '(e f g))
((A B C D 1 2 3 E F G) (B C D 2 3 F G) (C D 3 G))
      la fonction APPEND est appliquée aux 3 listes, puis au REST des
3 listes, et ainsi de suite jusqu'à ce que l'une des listes soit
vide. Elle rend la liste des résultats.

(maplist #'(lambda (x) (cons 'foo x)) '(a b c d))
((FOO A B C D) (FOO B C D) (FOO C D) (FOO D))
      lambda définie une fonction qui ajoute l'élément foo au début d'une
liste, puis au REST de la liste, et ainsi de suite jusqu'à ce que
la liste soit vide. Elle rend la liste des résultats.

(maplist #'(lambda (x) (if (member (car x) (cdr x)) 0 1)) '(a b a c d b c))
(0 0 1 0 1 1 1)
      lambda définie une fonction qui teste si le premier élément de la
liste passée en argument à MAPLIST est dans le REST de la liste.
lambda rend 0 si oui, 1 sinon. MAPLIST rend la liste des résultats.

(maplist #'(lambda (x) (subseq x 1)) '("arbre" "branche" "feuille"))
(("branche" "feuille") ("feuille") NIL)
      rend la liste des REST successifs de la liste passée en argument

MAPCAN:

(mapcan #'(lambda (x y) (if (null x) nil (list x y))) '(nil nil nil d e) '(1 2 3 4 5 6))
(D 4 E 5)
      rend la liste des résultats (listes) mais concaténés, alors qu'avec
MAPCAR on obtient:

(mapcar #'(lambda (x y) (if (null x) nil (list x y))) '(nil nil nil d e) '(1 2 3 4 5 6))
(NIL NIL NIL (D 4) (E 5))
      donc la liste des listes-résultats.

(mapcan #'(lambda (x) (and (numberp x) (list x))) '(a 1 b c 3 4 d 5))
(1 3 4 5)

(mapcar #'(lambda (x) (and (numberp x) (list x))) '(a 1 b c 3 4 d 5))
(NIL (1) NIL NIL (3) (4) NIL (5))
      même remarque

MAPCON: (de la famille MAPLIST)

(mapcon #'reverse '(1 2 3 4 5))
(5 4 3 2 1 5 4 3 2 5 4 3 5 4 5) rend la liste concaténée (avec NCONC) de (5 4 3 2 1), (5 4 3 2),
                                                        (5 4 3), (5 4) et (5)

(mapcon #'(lambda (x) (format t "~a " (+ 2 (car x)))) '(1 2 3 4 5))
3 4 5 6 7      simple affichage demandé par la fonction lambda qui s'applique à la liste initiale, puis à ses CDR
NIL              MAPCON rend NIL

MAPC: applique son premier argument (une fonction!) à chaque élément de la liste (2ème argument) et des listes suivantes s'il y en a.
      souvent, par nécessité, le premier argument est une fonction avec effet de bord, sinon le 2ème argument est rendu à l'identique.
      (voir le 2ème cas, ci-dessous, avec deux listes)

(defparameter nom '("Julien" "Jean" "Antoine"))
NOM              définition d'une variable nom

nom
("Julien" "Jean" "Antoine")

(mapc #'nstring-upcase nom)
("JULIEN" "JEAN" "ANTOINE") rend le 2ème argument modifié

nom
("JULIEN" "JEAN" "ANTOINE") la variable est modifiée également

Avec deux listes, la fonction doit présenter deux variables:

(mapc #'(lambda (x y) (format t "~a- ~a   " y x)) nom '(1 2 3))
1- JULIEN   2- JEAN   3- ANTOINE   la fonction affiche ce qui est demandé et...
("JULIEN" "JEAN" "ANTOINE")   ...rend le 2ème argument qui, ici, est inchangé

MAPL:

(mapl #'(lambda (x y) (format t "~a ~a   " x y)) '(a b c) '(1 2 3))
(A B C) (1 2 3)   (B C) (2 3)   (C) (3)
(A B C)

prochainement: "ARBRES, ENSEMBLES, TABLES."

mercredi 10 décembre 2014

9-TABLES de HACHAGE:

>>>>
Liste des symboles rencontrés dans ce chapitre:
make-hash-table hash-table-test hash-table-p gethash hash-table-count remhash push clrhash maphash 

Tables de hachage.

MAKE-HASH-TABLE:

(defparameter *h* (make-hash-table))
*H*             définit une table de hachage

(defparameter *h* (make-hash-table :test #'equal))
*H*             :test permet de préciser le test d'égalité des clés (par défaut: EQL)

(defparameter *h* (make-hash-table :size 5))
*H*        :size permet de préciser la taille de la table de hachage,en fait le nombre d'éléments (ici 5)

MAKE-HASH-TEST:

(hash-table-test *h*)
EQUAL rend le test d'égalité utilisé pour la table *h*

HASH-TABLE-P:

(hash-table-p *h*)
T pour vérifier si *h* est bien une table de hachage

GETHASH:        (permet de lire la valeur d'une clé d'une table de hachage)

(gethash 'foo *h*)
NIL                pas de valeur correspondant à la clé foo
NIL                NIL (le 2ème) indique que NIL n'est pas la valeur de foo

gethash est setfable: on peut définir une clé et lui donner une valeur.

(setf (gethash 'foo *h*) 'toto)
TOTO        définit la valeur TOTO pour la clé foo

(gethash 'foo *h*)
TOTO        rend la valeur TOTO à l'aide de la clé foo
T                T indique que la clé foo possède une valeur

Il faut bien noter que la 2ème valeur rendue par GETHASH indique si la clé est bien présente dans la table de hachage.
La valeur de cette clé pourrait être NIL.
La 2ème valeur est passée sous silence, à moins qu'un traitement explicite ne soit utilisé pour voir les valeurs multiples rendues. C'est le cas avec la macro MULTIPLE-VALUE-BIND qui crée des variables liées (comme LET):

(defun montre-valeur (clé table-hach)
  (multiple-value-bind (valeur present) (gethash clé table-hach)
    (if present
(format nil "La valeur ~a est présente dans la table de hachage." valeur)
(format nil "La valeur ~a est rendue car la clé n'est pas dans la table de hachage." valeur))))
MONTRE-VALEUR      définition de la fonction analysant la table de hachage

(setf (gethash 'bar *h*) nil)
NIL       introduction de la clé bar dans la table avec la valeur NIL

(montre-valeur 'foo *h*)
"La valeur TOTO est présente dans la table de hachage."

(montre-valeur 'bar *h*)
"La valeur NIL est présente dans la table de hachage."

(montre-valeur 'baz *h*)
"La valeur NIL est rendue car la clé n'est pas dans la table de hachage."

HASH-TABLE-COUNT:       (compte le nombre de clés dans la table de hachage)

(hash-table-count *h*)
2 il y a 2 clés dans la table de hachage *h*

REMHASH:        (pour effacer une clé)

(remhash 'bar *h*)
T                          efface la clé bar et sa valeur de la table de hachage
             NIL si la clé n'existe pas

(montre-valeur 'bar *h*)
"La valeur NIL est rendue car la clé n'est pas dans la table de hachage."

PUSH:   (utiliser pour donner une valeur à une clé)

(push "N'est pas habituellement utilisé comme fonction."
      (gethash 'boz *h*))
("N'est pas habituellement utilisé comme fonction.")
                        push permet d'entrer une clé et sa valeur

(gethash 'boz *h*)
("N'est pas habituellement utilisé comme fonction.")
T

CLRHASH:        (pour effacer le contenu d'une table de hachage)

(clrhash *h*)
#              efface toute la table

(montre-valeur 'foo *h*)
"La valeur NIL est rendue car la clé n'est pas dans la table de hachage."

Remarque: la table existe encore. Elle n'est pas supprimée.

Itération sur une table de hachage:

MAPHASH:      prend comme arguments une fonction à deux arguments et une table de hachage. Exemple:

(setf (gethash 'foo *h*) 'toto)     >>  TOTO
(setf (gethash 'bar *h*) 'tonton)   >>  TONTON
(setf (gethash 'baz *h*) 'tata)   >>  TATA

(maphash #'(lambda (clé val) (format t "~a => ~a~%" clé val)) *h*)
FOO => TOTO
BAR => TONTON
BAZ => TATA
NIL                           affiche toutes les clés et leurs valeurs

Autre exemple:

(setf (gethash 'foo *h*) 20)
20 On introduit des valeurs numériques

(setf (gethash 'bar *h*) 10)
10

(setf (gethash 'baz *h*) 5)
5

(maphash #'(lambda (clé val) (format t "~a => ~a~%" clé val)) *h*)
FOO => 20
BAR => 10
BAZ => 5
NIL                         On affiche ces valeurs

(maphash #'(lambda (clé val) (when (< val 10) (remhash clé *h*))) *h*)
NIL                         On supprime les valeurs inférieures à 10

(maphash #'(lambda (clé val) (format t "~a => ~a~%" clé val)) *h*)
FOO => 20
BAR => 10
NIL                         On affiche les valeurs restantes

Utilisation de LOOP:

(loop for clé being the hash-keys in *h* using (hash-value valeur)
     do (format t "~a => ~a~%" clé valeur))
FOO => 20
BAR => 10
NIL

Rem.: à la place de "the" on peut utiliser "each" et à la place de "in": "of".

_______________________________________________________
Exercice (résolu)
On construit une alist à partir d'une liste de clés et d'une liste de valeurs correspondantes.
Puis on cherche une fonction permettant la conversion de cette alist en table de hachage.
Voir alist: plus loin.

(defparameter l1 '(a b c d))
L1           définition de la liste de clés

(defparameter l2 '(1 2 3 4))
L2           définition de la liste de valeurs

(defparameter lst (mapcar #'cons l1 l2))
LST            construction de la alist

lst
((A . 1) (B . 2) (C . 3) (D . 4))                vérification

(defparameter *h* (make-hash-table))
*H* définition d'une table de hachage

(defun alist-vers-table-h (l)
  (if (endp l)
      t
      (progn (setf (gethash (caar l) *h*) (cdar l))
     (alist-vers-table-h (cdr l)))))
ALIST-VERS-TABLE-H     définition de la fonction convertissant la alist en table de hachage

(alist-vers-table-h lst)
T    rend T quand la table est construite

(gethash 'b *h*)
2
T vérification sur la clé b

(maphash #'(lambda (clé val) (format t "~a >>> ~a~%" clé val)) *h*)
A >>> 1
B >>> 2
C >>> 3
D >>> 4
NIL vérification avec MAPHASH

Autre exemple:

(defparameter lst1 '(0 1 2 3 4 5 6 7 8 9))
LST1

(defparameter lst2 '(zéro un deux trois quatre cinq six sept huit neuf))
LST2

(defparameter alst (mapcar #'cons lst1 lst2))
ALST
La alist est construite

(defparameter *hnb* (make-hash-table))
*HNB*

(alist-vers-table-h alst *hnb*)
T
La table de hachage est construite

(maphash #'(lambda (clé val) (format t "~a >>> ~a~%" clé val)) *hnb*)
0 >>> ZÉRO
1 >>> UN
2 >>> DEUX
3 >>> TROIS
4 >>> QUATRE
5 >>> CINQ
6 >>> SIX
7 >>> SEPT
8 >>> HUIT
9 >>> NEUF
NIL

prochainement: "Compléments sur quelques fonctions."