Précédent Index

Types string, Ligne et Paragraphe

 

 
String  
Nous utilisons des valeurs de type « string » presque depuis le début de ce cours. En effet, en Scheme, lorsque nous écrivons "toto", nous écrivons un « littéral string » (chaîne de caractères en français).
 
Fonctions primitives  
Dans la suite du cours, nous écrirons des définitions de fonctions qui manipulent des chaînes de caractères et, pour ce faire, nous utiliserons les primitives Scheme (i.e. les fonctions fournies par DrScheme) suivantes:
 
Pour connaitre la longueur d'une chaîne:
 
;;; string-length : string -> nat 
;;; (string-length s) rend la longueur de la chaîne «s» 
;;; Par exemple, (string-length "abc") rend 3 et (string-length "") rend 0 
 

 
Une fonction pour concaténer des chaînes:
 
;;; string-append : string * ... -> string 
;;; (string-append s1 ...) rend la chaîne de caractères obtenue en mettant 
;;; « bout à bout » les différentes chaînes données 
;;; Par exemple, (string-append "abc" "de") rend la chaîne "abcde" 
 

 
On peut considérer qu'une chaîne de caractères est une suite de caractères, le premier caractère ayant comme indice 0, le second caractère ayant comme indice 1,... et le dernier caractère ayant comme indice la longueur de la chaîne moins un. On dispose alors d'une fonction qui rend une sous-chaîne de la chaîne donnée, sous-chaîne spécifiée par l'indice de son premier caractère et un de plus que l'indice, dans la chaîne donnée, de son dernier caractère dans la chaîne donnée:
 
;;; substring : string * nat * nat -> string 
;;; ERREUR lorsque les indices ne sont pas corrects 
;;; (substring s i j) rend la sous-chaîne de caractères de la chaîne «s» d'indices « [i ... j[ » 
;;; Par exemple, (substring "abc" 1 3) rend la chaîne "bc" 
 

 
Exemples  
La fonction prem rend la chaîne de caractères qui ne comporte qu'un caractère, le premier de la chaîne donnée. Notons que cette spécification n'a de sens que si la chaîne donnée a, au moins, un caractère, c'est-à-dire si elle n'est pas vide:
 
;;; prem : string -> string 
;;; ERREUR lorsque la chaîne donnée est la chaîne vide 
;;; (prem s) rend la chaîne de caractères qui ne comporte qu'un caractère, 
;;; le premier de la chaîne «s» donnée 
 

 
L'implantation est simple, il suffit de considérer la sous-chaîne qui commence au premier caractère (i.e. celui d'indice 0) et qui se termine au second caractère (i.e. celui d'indice 1):
 
(define (prem s)
  (substring s 0 1))
 

 
Considérons maintenant la fonction qui rend la chaîne de caractères obtenue en ôtant, à la chaîne donnée, son premier caractère:
 
;;; sauf-prem : string -> string 
;;; ERREUR lorsque la chaîne donnée est la chaîne vide 
;;; (sauf-prem s) rend la chaîne de caractères obtenue en ôtant, à la chaîne «s» 
;;; donnée, son premier caractère 
 

 
Là encore l'implantation est facile, il suffit de remarquer que l'indice de fin est égal à la longueur de la chaîne:
 
(define (sauf-prem s)
  (substring s 1 (string-length s)))
 

 
On peut aussi avoir besoin du dernier caractère d'une chaîne donnée:
 
;;; der : string -> string 
;;; ERREUR lorsque la chaîne donnée est la chaîne vide 
;;; (der s) rend la chaîne de caractères qui ne comporte qu'un caractère, 
;;; le dernier de la chaîne «s» donnée 
 

 
(define (der s)
  (let ((ls (string-length s)))
    (substring s (- ls 1) ls)))
 

 
Enfin, considérons la fonction qui rend la chaîne de caractères obtenue en ôtant, à la chaîne donnée, son dernier caractère:
 
;;; sauf-der : string -> string 
;;; ERREUR lorsque la chaîne donnée est la chaîne vide 
;;; (sauf-der s) rend la chaîne de caractères obtenue en ôtant, à la chaîne «s» 
;;; donnée, son dernier caractère 
 

 
(define (sauf-der s)
  (substring s 0 (- (string-length s) 1)))
 

 
Comme dernier exemple, considérons la fonction permutation:  
;;; permutation : string -> string 
;;; (permutation s) rend la chaîne vide lorsque la chaîne donnée est vide, 
;;; sinon rend la chaîne de caractères obtenue en ôtant, à la chaîne «s» 
;;; donnée, son premier caractère et en le rajoutant à la fin  
;;; Par exemple, (permutation "abc") rend "bca" 
 

 
(define (permutation s)
  (if (equal? s "")
      ""
      (string-append (sauf-prem s) (prem s))))
 

 
Types Ligne et Paragraphe  
Les chaînes de caractères vues dans le paragraphe précédent peuvent comporter des caractères « fin de ligne ». Par exemple l'exécution de
 
(permutation "ab
c")

rend  

 
"b
ca"
 

 
Par la suite, nous considèrerons le type des chaînes de caractères qui ne comportent pas de caractères « fin de ligne » et nous nommerons « Ligne » le type de ces chaînes. Notons que le type « Ligne » possède donc toutes les fonctions du type « string ».
 
Un « Paragraphe » est alors une suite de lignes qui seront affichées les unes à la suite des autres, en passant à chaque fois à la ligne. Pour manipuler ces paragraphes, nous utiliserons les fonctions de base suivantes qui ont été ajoutées à la bibliothèque de DrScheme pour cet enseignement et se trouvent mentionnées dans la carte de référence:
 
;;; paragraphe : LISTE[Ligne] -> Paragraphe 
;;; (paragraphe L) rend le paragraphe formé des lignes de la liste «L» 
 
;;; paragraphe-cons : Ligne * Paragraphe -> Paragraphe 
;;; (paragraphe-cons ligne para) rend le paragraphe dont la première ligne est «ligne»  
;;; et dont les lignes suivantes sont constituées par les lignes du paragraphe «para» 
 
;;; lignes : Paragraphe -> LISTE[Ligne] 
;;; (lignes paragraphe) rend la liste des lignes contenues dans le paragraphe donné. 
 

 
Remarque  
Les fonctions paragraphe et lignes vérifient les propriétés suivantes:
 
Pour tout paragraphe para:
 
   (paragraphe (lignes para))   ->   para
 

 
Pour toute liste de lignes L:
 
   (lignes (paragraphe L))   ->   L
 

 
Exemple 1  
On voudrait écrire la définition d'une fonction qui, étant donnés deux paragraphes, les concatène c'est-à-dire rend le paragraphe qui contient les lignes du premier paragraphe puis les lignes du second paragraphe. Pour implanter cette fonction, on peut  
 
;;; paragraphe-append : Paragraphe * Paragraphe -> Paragraphe 
;;; (paragraphe-append P1 P2) rend le paragraphe qui contient les lignes de «P1» 
;;; puis les lignes de «P2» 
(define (paragraphe-append P1 P2)
  (paragraphe (append (lignes P1) (lignes P2))))
 

 
Exemple 2  
On voudrait écrire la définition d'une fonction qui, étant donnée une ligne, affiche ses caractères « en triangle »: elle rend un paragraphe dont la première ligne est la ligne donnée, la deuxième ligne est la ligne donnée hormis le dernier caractère, la troisième ligne est la ligne donnée hormis les deux derniers caractères... Par exemple:
 
(triangle1 "abc")

rend  

 
"
abc
ab
a
"
 

 
Sa spécification est donc:
 
;;; triangle1 : Ligne -> Paragraphe 
;;; (triangle1 ligne) rend un paragraphe dont la première ligne est la ligne 
;;; donnée, la deuxième ligne est la ligne donnée hormis le dernier caractère, 
;;; la troisième ligne est la ligne donnée hormis les deux derniers caractères... 
 

 
Pour l'implantation de la fonction triangle1, remarquons que la première ligne du résultat est la ligne donnée et que les lignes suivantes sont égales à l'application de cette même fonction sur la ligne obtenue en supprimant le dernier caractère de la ligne donnée. On peut donc écrire la définition récursive suivante:
 
(define (triangle1 ligne)
  (if (equal? ligne "")
      (paragraphe '())
      (paragraphe-cons ligne (triangle1 (sauf-der ligne)))))
 

 
Exemple 3  
On voudrait maintenant avoir un triangle « dans l'autre sens »:
 
(triangle2 "abc")

rend  

 
"
abc
 bc
  c
"
 

 
Sa spécification est donc:
 
;;; triangle2 : Ligne -> Paragraphe 
;;; (triangle2 ligne) rend un paragraphe dont la première ligne est la ligne 
;;; donnée, la deuxième ligne est la ligne donnée hormis le premier caractère, 
;;; la troisième ligne est la ligne donnée hormis les deux premiers caractères... 
;;; toutes ces lignes étant justifiées à droite 
 

 
L'implantation est un peu plus délicate. Faisons un dessin: (triangle2 "abcd") doit rendre le paragraphe  
 

 
la première ligne de ce paragraphe étant la donnée auquelle on a appliqué la fonction:  
 

 
et dans les lignes suivantes, on reconnait le résultat de l'application de la fonction à la ligne donnée hormis son premier caractère:  
 

 
mais en mettant un caractère espace devant chaque ligne:  
 

 
Ainsi, si l'on dispose d'une fonction ajout-prefixe de spécification
 
;;; ajout-prefixe : Ligne * Paragraphe -> Paragraphe 
;;; (ajout-prefixe pref p) rend le paragraphe obtenu en préfixant chaque ligne 
;;; du paragraphe «p» donné par «pref» 
 

 
la fonction triangle2 peut être définie par:
 
(define (triangle2 ligne)
  (if (equal? ligne "")
      (paragraphe '())
      (paragraphe-cons 
       ligne
       (ajout-prefixe " " (triangle2 (sauf-prem ligne))))))
 

 
Reste à implanter ajout-prefixe. Rappelons sa spécification:
 
;;; ajout-prefixe : Ligne * Paragraphe -> Paragraphe 
;;; (ajout-prefixe pref p) rend le paragraphe obtenu en préfixant chaque ligne 
;;; du paragraphe «p» donné par «pref» 
 

 
Comme il faut concaténer une chaîne devant chaque ligne du paragraphe, il suffit, à partir du paragraphe donné:  
D'où la définition:
 
(define (ajout-prefixe pref p) 
  ;; ajout-pref : Ligne -> Ligne 
  ;; (ajout-pref lig) rend la ligne obtenue en concaténant «pref» devant «lig» 
  (define (ajout-pref lig) 
    (string-append pref lig))
  
  ;; expression de (ajout-prefixe pref p)  
  (paragraphe (map ajout-pref (lignes p))))
 

 
Mais, avec la définition précédente, pour calculer (triangle2 "abcd"), après avoir calculé (triangle2 "bcd") qui renvoie le paragraphe comportant les trois lignes bcd, cd et d, on transforme ce paragraphe en une liste de trois lignes puis on reconstitue un autre paragraphe de trois lignes; et comme pour calculer (triangle2 "bcd") on transforme un paragraphe de deux lignes en une liste de deux lignes et qu'ensuite on reconstitue un paragraphe de deux lignes... Le calcul passe donc son temps à reconstruire un paragraphe à partir d'une liste de lignes obtenue en décomposant un paragraphe.
 
Pour l'efficacité, on peut donner d'autres définitions qui évitent ces décompositions--recompositions. La définition suivante utilise une fonction auxiliaire qui a une variable de plus que la fonction triangle2, variable qui contient une chaîne de caractères qui préfixera toutes les lignes du paragraphe résultat:
 
;;; triangle2 : Ligne -> Paragraphe 
;;; (triangle2 ligne) rend un paragraphe dont la première ligne est la ligne 
;;; donnée, la deuxième ligne est la ligne donnée hormis le premier caractère, 
;;; la troisième ligne est la ligne donnée hormis les deux premiers caractères... 
;;; toutes ces lignes étant justifiées à droite 
(define (triangle2 ligne)
  ;; triangle2aux : Ligne * Ligne -> Paragraphe 
  ;; (triangle2aux pref ligne) rend le paragraphe obtenu en préfixant chaque ligne de 
  ;; (triangle2 ligne) par le mot «pref» 
  ... à faire
  ... à faire
  ... à faire  
  ... à faire
  ... à faire
  ... à faire
  ;; expression de (triangle2 ligne) : 
  (triangle2aux "" ligne))
 

 
Pour l'implantation de la fonction triangle2aux, il suffit de remarquer que la première ligne est obtenue en concaténant le préfixe donné et la ligne donnée et que les lignes suivantes sont obtenues en appliquant (récursivement) la fonction  
D'où la définition:
 
;;; triangle2 : Ligne -> Paragraphe 
;;; (triangle2 ligne) rend un paragraphe dont la première ligne est la ligne 
;;; donnée, la deuxième ligne est la ligne donnée hormis le premier caractère, 
;;; la troisième ligne est la ligne donnée hormis les deux premiers caractères... 
;;; toutes ces lignes étant justifiées à droite 
(define (triangle2 ligne)
  ;; triangle2aux : Ligne * Ligne -> Paragraphe 
  ;; (triangle2aux pref ligne) rend le paragraphe obtenu en préfixant chaque ligne de 
  ;; (triangle2 ligne) par le mot «pref» 
  (define (triangle2aux pref ligne)
    (if (equal? ligne "")
        (paragraphe '())
        (paragraphe-cons (string-append pref ligne) 
                         (triangle2aux (string-append " " pref) 
                                       (sauf-prem ligne)))))
  ;; expression de (triangle2 ligne) : 
  (triangle2aux "" ligne))
 

 


Auteur(s): titou@ufr-info-p6.jussieu.fr.Mainteneur de la page: titou@ufr-info-p6.jussieu.fr.

Pour s'auto-évaluer
  • Exercices d'assouplissement

  • Précédent Index