>>>>
Les symboles.
SYMBOLP:
(symbolp 'a)
T
toute lettre (ou groupe de lettres) peut être un symbole
'a
A
lisp convertit le symbole en majuscule automatiquement
(symbol-name 'a)
"A"
tout symbole a un nom
Cependant, ce qui suit est aussi un symbole:
'|a b&C|
|a b&C|
entre 2 barres verticales on peut placer tous les signes possibles
on obtient un nouveau symbole qui a, lui aussi, un nom:
SYMBOL-NAME:
(symbol-name '|a b&C|)
"a b&C"
le nom est une chaine de caractères
(symbol-name '||)
""
||
est un symbole vide dont le nom est une chaine vide
'|ABC|
ABC
abc, aBc, AbC et |ABC|
sont, heureusement, le même symbole avec le même nom
Les barres verticales ne font pas partie du symbole, elles ne servent qu'à le délimiter.
Chaque symbole possède une liste de propriétés ou plist (vide, probablement, au départ):
(setf (get 'abc 'vrai-nom) "les débuts")
"les débuts"
la valeur "les débuts" est attribuée à la clé vrai-nom
(get 'abc 'vrai-nom)
"les débuts"
get prend en argument le symbole et la clé, et rend la valeur de cette clé
(setf (get 'abc 'définition) "On dit, par exemple, l'abc du métier, pour parler de la base de connaissances.")
"On dit, par exemple, l'abc du métier, pour parler de la base de connaissances."
une autre clé: définition
SYMBOL-PLIST:
(symbol-plist 'abc)
(DÉFINITION
"On dit, par exemple, l'abc du métier, pour parler de la base de connaissances."
VRAI-NOM "les débuts")
rend la plist du symbole abc
On peut donc appliquer la fonction get-properties déjà vue:
(get-properties (symbol-plist 'abc) '(définition))
DÉFINITION
"On dit, par exemple, l'abc du métier, pour parler de la base de connaissances."
(DÉFINITION
"On dit, par exemple, l'abc du métier, pour parler de la base de connaissances."
VRAI-NOM "les débuts")
Un symbole est un véritable objet: il possède un nom, un package, une valeur comme variable, une valeur comme fonction et une liste de propriété.
C'est un peu plus compliqué d'obtenir un symbole à partir de son nom: cela fait intervenir la notion de package (voir LES PACKAGES:).
Un package est une table de symboles mettant en relation les noms et les symboles.
Un symbole qui est dans cette table est dit interne au package.
Un symbole lu la première fois devient automatiquement interne au package courant.
INTERN:
prend comme argument une chaine de caractères et un optionnel nom de package:
FIND-SYMBOL: (même chose, mais ne rend pas le symbole interne)
(intern "ABC")
ABC
rend le symbole de nom "ABC"
:INTERNAL
indique que le symbole est interne au package courant (en effet, nous l'avons déjà utilisé)
ou bien:
(find-symbol "ABC")
ABC
:INTERNAL
indique, aussi, que le symbole est interne, puisqu'il l'était précédemment
Remarque: nous avons écrit "ABC" en majuscules, ce qui correspond bien au symbole déjà utilisé. Sinon:
(intern "abc")
|abc|
rend un symbole qui vient d'être créé
NIL
ce symbole n'était pas dans le package courant
Mais:
(intern "abc")
|abc|
:INTERNAL
le symbole est, maintenant, interne au package
INTERN fait en sorte que le symbole devient interne au package
alors que FIND-SYMBOL ne permet pas de rendre le symbole interne
Entendons-nous bien, |abc| ce n'est pas abc!
En effet, |abc| a pour nom "abc", tandis que abc a pour nom "ABC" (nous l'avons dit plus haut: abc c'est ABC).
Pour s'en persuader un peu plus:
(defparameter ABC 47)
ABC
(defparameter |abc| 23)
|abc|
abc
47
|abc|
23
abc et |abc| sont bien deux variables différentes
Faire appel à une variable non définie crée une erreur, ouvre le débogueur et...rend le symbole interne!
inconnu
; Evaluation aborted on #
. à la sortie du débogueur
Rappelons que Clisp convertit les noms de variables en majuscules (donc lors de l'appel à inconnu).
(find-symbol "INCONNU")
;notons que nous écrivons, ici, inconnu en majuscules
INCONNU
:INTERNAL
l'appel précédent à la variable inconnu(e) a rendu le symbole interne
(ce n'est pas FIND-SYMBOL qui rend le symbole interne)
Un symbole avec des barres verticales:
'|a\|b\|c|
|a\|b\|c|
le backslash nécessaire pour introduire une barre verticale est (euh!... évidemment...) conservé dans le symbole.
Par contre, pas dans son nom:
(symbol-name '|a\|b\|c|)
"a|b|c"
(intern "a|b|c")
|a\|b\|c|
:INTERNAL
Que se passe-t-il pour le nom d'une fonction?
(intern "CONS")
CONS
nous obtenons bien le symbole de la fonction
:INHERITED
ce symbole, utilisable dans le package courant, est un héritage d'un autre package.
(voir LES PACKAGES:)
La 2ème valeur retournée par INTERN peut aussi être :EXTERNAL.
Le symbole est alors externe au package courant, mais pas hérité.
Il faut signaler, également, que certains symboles sont non-internes et, ceci ne veut pas dire qu'ils sont hérités ou externes!
Ce sont les symboles gensyms que nous avons déjà rencontrés.
Un symbole gensym peut prendre la valeur d'une variable x à un moment donné et la conserver, alors que la variable x est amenée à évoluer.
SYMBOL-VALUE:
(symbol-value 'abc)
47
rend la valeur du symbole s'il représente une variable spéciale (variable globale)
ce n'est pas le cas des variables lexicales (variables locales),
car dans un code compilé, il n'y a aucune trace du symbole
Nous avons déjà vu, précédemment, SYMBOL-FUNCTION qui rend la fonction associée au symbole:
(symbol-function 'cons)
#<FUNCTION CONS>
On peut aussi demander le package associé au symbole:
(symbol-package 'cons)
#<PACKAGE "COMMON-LISP">
Pour résumer, un symbole possède un nom, un package, une valeur, une fonction et une plist.
...Enfin, quand tout cela est défini!
On y accède avec les fonctions: SYMBOL-NAME, SYMBOL-PACKAGE, SYMBOL-VALUE, SYMBOL-FUNCTION et SYMBOL-PLIST respectivement.
Attention! Certains symboles sont protégés dans leur package:
Il peut être difficile, alors, de déclarer cons, par exemple, comme une variable spéciale.
Pour trier les noms des symboles du package courant (voir LOOP et les packages: plus haut):
(sort (loop for s being the symbols in *package*
collecting (symbol-name s)) #'string<)
("&ALLOW-OTHER-KEYS" "&AUX" "&BODY" "&ENVIRONMENT" "&KEY" "&OPTIONAL" "&REST"
"&WHOLE" "*" "*" "**" "***" "*AFTER-GC-HOOKS*" "*BACKTRACE-FRAME-COUNT*"
...
le reste a été coupé
_____________________________________________
Ce qui suit est inspiré du travail de Paul Graham dans son ANSI-Common-Lisp.
J'ai ajouté quelques amménagements et commenté les fonctions.
Les fonctions suivantes opèrent sur des symboles obtenus à partir d'un texte (Le Petit Prince de Saint-Exupéry, par exemple)
(defparameter *mots* (make-hash-table :size 10000))
;définition d'une table de hachage *mots* de 10000 clés
(defconstant maximot 100)
;définition d'une constante valant 100
(defparameter *petit_pr* (merge-pathnames #p"lepetitprince.txt"))
Le texte que l'on veut traiter doit être placé dans un fichier .txt
*petit_pr* est un pathname vers ce fichier.
(defparameter *néo-texte* (merge-pathnames #p"nouveau.txt"))
*néo-texte* est un pathname vers un nouveau fichier
On définit, ici, deux fermetures dans un LET: intro-table et initialisation.
(let ((précédent '|.|))
;la variable locale précédent est initialisée avec le symbole |.|
(defun intro-table (symbole)
;définition d'une première fermeture intro-table
(let ((doublet (assoc symbole (gethash précédent *mots*))));doublet prend une valeur (symbole . alist), ou NIL si symbole n'est pas dans la table
(if (null doublet)
;si le doublet est NIL...
(push (cons symbole 1) (gethash précédent *mots*));...(symbole . 1) est introduit dans la alist de précédent
(incf (cdr doublet))))
;sinon le CDR du doublet est incrémenté de 1
(setf précédent symbole))
;précédent prend la valeur de symbole pour le prochain appel à intro-table
(defun initialisation ()
;définition d'une deuxième fermeture initialisation
(setf précédent '|.|)
;réinitialise précédent
(clrhash *mots*)))
;réinitialise la table de hachage *mots*
Remarques: on peut dire que précédent retient le symbole précédent.
Dans la 1ère fermeture, tous les symboles qui sont précéder par précédent sont enregistrés dans une alist qui est la valeur de la clé précédent.
Chaque clé de la table de hachage pointe sur cette alist qui se construit de proche en proche.
Si symbole est dans la alist pointée par précédent, le doublet correspondant est récupéré (sinon doublet est NIL).
si doublet est NIL, (symb . 1) est ajouté à la alist pointée par précédent.
sinon la valeur associée à précédent est incrémentée de 1.
La fonction ponctuation permet de transformer les signes de ponctuation en symbole:
(defun ponctuation (c)
(case c
(#\: '|:|)
(#\» '|»|)
(#\« '|«|)
(#\. '|.|)
(#\, '|,|)
(#\; '|;|)
(#\! '|!|)
(#\? '|?|)))
;ponctuation rend un symbole correspondant au caractère
La fonction lit-texte construit des listes associatives placées dans une table de hachage:
(defun lit-texte (pathname)
(with-open-file (flux pathname :direction :input) ;un flux est ouvert en entrée en provenance d'un fichier .txt définit par pathname
(let ((tampon (make-string maximot)) ;tampon est une chaine de maximot caractères
(pos 0))
;pos est un index initialisé à zéro
(do ((c (read-char flux nil :eof) ;itération sur le caractère c lu à partir du fichier .txt
(read-char flux nil :eof)))
((eql c :eof))
;test de fin de fichier
(if (or (alpha-char-p c) (string= c #\-) (string= c #\RIGHT_SINGLE_QUOTATION_MARK))
(progn
;si c est un caractère alphabétique ou un trait d'union ou une apostrophe
(setf (aref tampon pos) c) ;c est placé dans le tampon à la position pos
(incf pos))
;pos est incrémenté d'une unité
(progn
;sinon...
(unless (zerop pos)
;sauf si pos égale zéro
(intro-table (intern (subseq tampon 0 pos))) ;le mot constitué par l'itération est transformé en symbole et envoyé à la table de
(setf pos 0))
;hachage, puis pos est réinitialisée à zéro
(let ((p (ponctuation c)))
;le signe de ponctuation éventuel est traité
(if p (intro-table p)))))))))
;et envoyé dans la table de hachage
La fonction générer-texte fait appel à la fonction aléa-suivant pour construire un nouveau texte:
(defun aléa-suivant (précédent)
;fonction déterminant le symbole qui suit précédent
(let* ((liste-assoc (gethash précédent *mots*))
;on récupère la liste associative de précédent dans la table de hachage
(x (random (reduce #'+ liste-assoc :key #'cdr)))) ;x valeur aléatoire entre 0 et la somme des CDR des éléments de liste-assoc
(dolist (doublet liste-assoc)
;itération sur tous les éléments de liste-assoc à l'aide de la variable doublet
(if (minusp (decf x (cdr doublet)))
;si x moins la valeur du CDR de doublet devient négatif...
(return (car doublet))))))
;...la fonction rend le symbole contenu dans doublet (CAR) avec interruption de l'itération
(defun générer-texte (n &optional (précédent '| |))
;on peut imposer le premier mot à afficher avec l'argument optionnel précédent
(format t "~a " précédent)
;affichage du symbole sous la forme d'une chaine de caractères
(if (zerop n)
;si n atteind zéro...
(terpri)
;...arrêt de l'affichage
(let ((néo (aléa-suivant précédent)))
;le prochain symbole est placé dans néo
(générer-texte (1- n) néo))))
;récursion: précédent prend la valeur de néo
Il ne faut pas s'attendre à obtenir un texte généré parfait, loin de là.
Mais, parfois, on trouve un sens à certaines phrases.
Les guillemets sont reconnus, mais non traités.
N'oubliez pas que vous pouvez utiliser la fermeture INITIALISATION pour réinitialiser la table de hachage et recommencer.
Comme le résultat n'est pas, forcément satisfaisant, je vous propose une modification des deux dernières fonctions.
Vous aurez, ainsi, la possibilité de choisir le mot suivant au lieu de le calculer de façon aléatoire.
(defun suivant (précédent)
(let ((liste-assoc (gethash précédent *mots*)) ;liste-assoc est la alist du symbole précédent
(i 0))
;i est initialisé à zéro
(dolist (doublet liste-assoc)
;itération sur la variable doublet
(format t "~a: ~a~%" i doublet)
;affichage itératif d'un numéro et d'un doublet de liste-assoc
(incf i))
;incrémentation de i de une unité
(format t "votre choix: ")
;message affiché
(let ((j (read)))
;j prend la valeur attendue par read
(if (< j (length liste-assoc))
;si j est inférieure à la longueur de liste-assoc...
(car (nth j liste-assoc))
;...la fonction rend le symbole du jème élément de liste-assoc
'|.|))))
;sinon le symbole . est rendu, ce qui permet de terminer une phrase
Avec la fonction suivante, le texte généré est envoyé dans un fichier nouveau.txt déterminé par le pathname *néo-texte*:
(defun générer-texte2 (n pathname &optional (précédent '|.|))
(with-open-file (flux2 pathname :direction :output :if-exists :supersede) ;un flux est ouvert en sortie vers le fichier .txt
(if (gethash précédent *mots*)
;si le symbole optionnel est dans la table de hachage...
(format flux2 "~a " précédent)
;...le symbole précédent est inscrit dans le fichier sous la forme d'une chaine de caractères
(progn
;sinon...
(setf précédent '|.|)
;...précédent reprend sa valeur par défaut
(format flux2 "~a " précédent))) ;et est inscrit dans le fichier
(labels ((gén-text (n flux2 précédent) ;définition d'une fonction récursive
(if (zerop n)
;si n atteint zéro...
(terpri)
;fin de l'inscription
(let ((néo (suivant précédent))) ;néo prend le symbole rendu par la fonction suivant
(format flux2 "~a " néo)
;néo est inscrit dans le fichier
(gén-text (1- n) flux2 néo))))) ;appel récursif à la fonction en décrémentant n
(gén-text n flux2 précédent))))
Prochainement: LES PACKAGES.