2.3. Le probleme de Comar 2.3.1 But du chapitre Nous allons traiter un (petit) problÉme complÉtement. Nous appliquerons les conseils des chapitres prÅcÅdents pour en faire une analyse approfondie avant de traduire les algorithmes en Rexx, Pascal et Prolog. Les premiÉres solutions Rexx et Pascal seront "naturellement" non rÅcursives, la solution Prolog sera forcÅment rÅcursive. Toutefois, pour bien montrer que la rÅcursivitÅ est un mode de programmation et non pas un "gadget", nous rÅÅcrirons les solutions Rexx et Pascal en mode rÅcursif. Un lecteur attentif pourra au passage (re)dÅcouvrir Pascal, rÅflÅchir È l'ordre de prÅsentation des programmes (maquettage en Rexx, reprise en Pascal etc.), essayer de dÅfinir ce qui est spÅcifique È Prolog, se faire une idÅe de l'importance des idÅes algorithmiques et des dÅtails dus aux langages...Si l'intÅrÁt pratique de cet exercice est limitÅ (si ce n'est d'utiliser des chaines de caractÉres), il n'en va pas de mÁme pour la culture et le recul qu'il est censÅ apporter sur les langages et les modes de p rogrammation rÅcursif et itÅratif. Nous avons choisi cet exemple parce qu'il traite de chaines de caractÉres. Il est donc comprÅhensible par tous, contrairement aux exercices rÅputÅs "simples" d'algorithmique numÅrique traditionnels. Un exercice d'arithmÅtique "ÅlÅmentaire" comme "tester si un nombre est parfait" n'est pas compris par qui a depuis longtemps oubliÅ les diviseurs, les nombres premiers... D'autre part, la gestion des chaines de caractÉres et des mots posent des problÉmes escamotÅs par la reprÅsentation des nombres : "oË commence et ou finit un mot ?", "comment reconnaitre qu'un mot est valide ?" sont des questions qui mettent en lumiÉre l'orientation des langages : Rexx et Awk sont clairement orientÅs texte, Pascal est orientÅ numÅrique, Prolog n'est ni l'un ni l'autre mais plutÂt orientÅ "symbole". Les autres traductions (en Apl,C,Dbase, Cobol, Lisp* etc.) sont donnÅes en fin de chapitre. 2.3.2 Position du problÉme 2.3.2.1 EnoncÅ Le problÉme de Comar consiste È Åcrire un programme qui demande une phrase et qui la "no‰lise", c'est-È-dire qui la rÅÅcrit en "arbre de No‰l" avec des mots inversÅs. Voici un exemple du dÅroulement du programme Quelle est votre phrase ? ceci est un test simple. Merci. Voici son arbre de No‰l:
      nu
   tse      tset
icec         .elpmis
2.3.2.2 Remarques sur l'ÅnoncÅ
Un problÉme bien posÅ est souvent presque rÅsolu, ce qui n'est pas le cas ici ! Il faut donc bien avoir compris

   -   que chaque mot doit Átre retournÅ sur lui-mÁme
   -   que la phrase est Åcrite È partir du mot central puis de ses voisins immÅdiats,
      puis des voisins des voisins etc.
   -   qu'on Åcrit sur la premiÉre ligne le mot central, et ensuite deux mots seulement par
      ligne et en descendant.

On ne sait pas ce qu'il faut faire s'il y a un nombre pair de termes ; nous utiliserons un deuxiÉme exemple pour prÅciser ce qu'on attend alors du programme :

Quelle est votre phrase ?
   quand on a un nombre pair de termes
Merci. Voici son arbre de No‰l:
            erbmon
         nu      riap
      a            ed
   no                  semret
dnauq

Le "flou" de l'ÅnoncÅ fait partie du problÉme (y compris la notion de mot, de phrase...). En fait, l'ÅnoncÅ complet Åtait "comment rÅsoudriez-vous ce problÉme si vous deviez l'enseigner en classe d'informatique (sans prÅcision du niveau des Åtudiants ou ÅlÉves) ?". Ce problÉme faisait partie d'une thÉse de troisÉme cycle (de Monsieur Cyril Comar (d'oË le nom du problÉme) sur l'informatisation de l'enseignement, l'enseignement de l'informatique et l'enseignement assistÅ (ou surveillÅ ?) par ordinateur dans l
e cadre de travaux È l'Ecole Centrale de Paris.

2.3.3 Indication de solutions
2.3.3.1 IdÅes gÅnÅrales ("informelles")

    Deux  thÉmes  semblent  se  dÅgager facilement, È savoir
    RETOURNER  chacun  des  mots  de  la phrase sur lui-mÁme
    (c'est-È-dire remplacer "tse'c" par "c'est", "lÅon"  par
    "noÅl"), ECRIRE ces mots retournÅs en partant du  MILIEU
    (È la fois de l'Åcran et de la phrase), en DESCENDANT et
    en  ESPACANT   vers  la   DROITE  et   vers  la  GAUCHE,
    c'est-È-dire Åcrire le mot c  puis les mots c-1 et  c+1,
    puis les mots c-2 et c+2 etc.  En tous cas, c'est ce que
    suggÉre  le  texte  ;  mais  on pourrait se contenter de
    retourner les mots  juste avan t  de les Åcrire  dans un
    premier  temps.    Si  ces  deux idÅes (qui rÅsument des
    actions) s'expriment par des VERBES, elles ne sont  sans
    doute pas au mÁme niveau :  le problÉme du placement des
    mots est primordial,  celui du retournement  secondaire.
    On peut  donc se  concentrer sur  l'ESSENTIEL et ignorer
    les DETAILS dans un premier temps, È moins que le but de
    l'exercice  ne  soit  de  traiter  ces deux problÉmes en
    parallÉle, ou de faire dÅcouvrir la notion de HIERARCHIE
    entre problÉme et sous-problÉme.  La premiÉre partie  du
    travail È accomplir est  donc d'Åcr ire l'arbre  de No‰l
    des  mots  normaux,  la  seconde  sera  de  traiter  les
    retournements des mot sur eux-mÁmes.

On peut se restreindre È formuler la mÅthode de rÅsolution ainsi :

   ECRIRE tous les mots de la phrase en partant du MILIEU, en    DESCENDANT et en ESPACANT vers la DROITE et vers la GAUCHE.

En fait, dans un tout premier temps, on peut mÁme se contenter d'afficher des nombres en arbre de No‰l, donc essayer de rÅaliser

         3
     2        4
1                 5

ou mÁme encore, si on a peur se s'embrouiller aussi avec les numÅros des mots

         1
     2       3
4                5
2.3.3.2 Discussion

    Transformer  ces  idÅes  en  programmes  (ou  fonctions,
    procÅdures, prÅdicats, car bien sÃr certains langages ne
    connaissent pas la notion  de programme) ne peut  PAS se
    faire sans "idÅe prÅconµue" ou au moins inconsciente  du
    langage  sous-jacent.    Mais  il  faut  se  mÅfier  des
    habitudes  que  l'on  peut  avoir  acquises.   Ainsi, le
    problÉme  de  retourner  un  mot  sur lui-mÁme peut Átre
    considÅrÅ  comme  :    instantanÅ,  si  on dispose d'une
    fonction comme reverse en Rexx ; fastidieux, si on  doit
    se  situer   au  niveau   du  caractÉre   dans  le  mot.
    DÅtaillons ce qu'on  doit Åcrire dans  ce cas.   On peut
    travailler :  a) ÅlÅgamment, en Apl par exemple, car  on
    peut "swapper" (permuter) 2 caractÉres d'un mÁme tableau
    en une affectation simple mais multiple, sans une tierce
    variable de sauvegarde,  ce qui s'Åcrit  :  MOT  [ I,J ]
    <-- MOT [
    J,I ] ou en utilisant  les indices gÅnÅrÅs par irMOT  et
    retournÅs par  É. On  retourne alors  un mot  entier sur
    lui-mÁme  par  :    MOT[  ÉINDICE  ]  <--  MOT[ INDICE  irM OT ] ou, encore plus
    simplement par ÉMOT car Apl sait renverser les mots ! b)
    bÁtement en boucle, dans un langage plus classique ;  il
    faut alors  un minimum  de 3  instructions pour permuter
    MOT[i]  et  MOT[j],  comme  en  Pascal,  par  exemple en
    Åcrivant :

(* permutons les caractÉres d'indice i et j
   dansla chaine MOT : *)
    sauvegarde := MOT [ i ] ;
    MOT [ i ]  := MOT [ j ] ;
    MOT [ j ]  := sauvegarde
et le problÉme est ensuite de calculer correctement la valeur de l'indice j en fonction de i (ce qui doit Átre prÅsentÅ comme "facile" car ces indices sont symÅtriques par rapport au milieu du mot ; "prÅsentÅ" comme facile seulement car l'idÅe est simple, mais l'Åcriture complÉte est plus calculatoire).

On Åcrit alors quelquechose comme :
(* retournons  MOT  sur  lui  mÁme  en permutant les
   caractÉres  symÅtriques  par rapport au milieu du
   mot ; on  utlise un caractÉre de sauvegarde et on
   effectue une boucle qui va jusqu'au milieu du mot *)

     longueur := length( MOT ) ;
     milieu   := longueur div 2 ;
     for i := 1 to milieu do begin
          j := 1 + longueur - i
          sauvegarde := MOT [ i ] ;
          MOT [ i ]  := MOT [ j ] ;
          MOT [ j ]  := sauvegarde
     end ; (* for i := 1 to milieu *)

Il ne faut pas oublier de remarquer pÅdagogiquement que pour un nombre impair de caractÉres, celui du milieu n'est pas modifiÅ.

c) plus lourdement, en utilisant non plus un caractÉre de sauvegarde mais une chaine entiÉre et avec une boucle plus longue, en recopiant les ÅlÅments È partir de la fin de la chaine :

    for i := 1 to longueur do begin
        j := 1 + longueur - i
        MOT_RENVERSE [ i ]  := MOT [ j ] ;
    end ; (* for i := 1 to longueur *)
    MOT := MOT_RENVERSE

d) rÅcursivement, si on est capable de le faire et surtout de l'expliquer. Pour cela, on peut dire dire que retourner un Mot c'est

      permuter le Premier et le Dernier caractÉre de mot
   (puis)    retourner le Reste du Mot

On peut aussi rajouter : retourner un seul caractÉre, c'est ne rien faire. DÅtaillant ces actions et nommant les variables associÅes, on dira que retourner Mot en Tom

   - c'est appeler DÅbut le premier caractÉre de Mot
   - puis appeler Fin le dernier caractÉre de Mot
   - puis retourner le reste du Mot en Etser
   - et enfin concatÅner Fin, Etser et DÅbut en Tom

Une rÅsolution algorithmique correcte du problÉme secondaire du retournement d'un mot sur lui-mÁme est bien sÃr de supposer qu'on dispose d'une fonction Retourne(mot) qui renvoie le mot retournÅ et de repousser È plus tard sa traduction (ou sa dÅfinition si besoin est). Nous escamotons ici le fait qu'il semble plus "naturel" de choisir une fonction plutÂt qu'une procÅdure, selon une terminologie pascaliste. Toutefois, le point important est la dÅfinition de la notion de mot ; son retournement, pour techniqu
e qu'il soit reste comme nous l'avons indiquÅ en dÅbut de paragraphe, secondaire.

2.3.3.3 Niveaux de rÅsolution et registres de langues

Ces quelques indications de solution montrent un des problÉmes fondamentaux de l'analyse et de "l'algorithmisation" des solutions : le niveau de reprÅsentation et son expression en terme d'instructions, d'actions.  Si on enseigne en 6Éme ou en Terminale, les ÅlÉves ne disposent pas des mÁmes outils intellectuels que des Åtudiants du SupÅrieur ( ainsi l'appel d'une procÅdure dans une boucle peut Átre perturbant ) ; c'est aussi le cas d'un programmeur dÅbutant ou peu È l'aise dans le langage considÅrÅ. Si les
 ÅlÉves ont derriÉre eux un passÅ, une expÅrience informatique, programmatique de 10 h, 100 h... ils ne seront pas rÅceptifs au mÁme vocabulaire ; ainsi une solution fastidieuse mais simple se comprend en gÅnÅral mieux et se programme, se " dÅbogue " plus facilement qu'une solution rÅcursive pourtant plus intrinsÉque (ce qui d'ailleurs n'est pas toujours possible ou parfois long... ) mais difficile È vÅrifier "pas È pas È la main" ; ceci se rÅfÉre aussi aux habituÅs du Basic, du Pascal, en opposition aux pr
ogrammeurs en Lisp, en Prolog

Le travail de l'enseignant ou du programmeur qui va communiquer son programme doit donc se situer entre ces diffÅrents registres, simplifiant puis dÅtaillant... Le travail de l'algorithmicien aussi, puisqu'il doit procÅder de la mÁme dÅmarche pour rendre son programme accessible aux autres (et È lui-mÁme au fil des annÅes !). Par exemple, on peut (et nous l'avions dÅja dit) rÅsoudre le problÉme sans retourner les mots, rÅsoudre le probleme en supposant que le retournement se fait par appel d'une fonction di
sponible dans une bibliothÉque.

Pour un programmeur chevronnÅ, les mÁmes questions se posent, suivant que les algorithmes (et donc les programmes) devront Átre relus ou non par d'autres personnes, qu'ils doivent Átre facilement modifiables ou transportables, etc. Ce problÉme de transmission est bien sÃr extra informatique, comme la notion de mot. Il ne peut donc pas Átre rÅsolu par des mÅthodes strictement informatiques. Il faut, comme souvent un consensus, de faµon È ce que le programme "couvre" une bonne parie de la rÅalitÅ. Songeons, p
ar exemple, È tous les essais non encore transformÅs de faire lire, parler, voir et traduire les ordinateurs.

2.3.3.4 Progression dans la rÅsolution


Pour rÅsoudre le problÉme de Comar, nous Åcrirons donc de faµon progressive

- un algorithme principal
- un sous algorithme qui dÅcoupe une phrase en mots
- un sous algorithme qui retourne un mot sur lui-mÁme
- un sous algorithme qui trouve le XiÉme mot
- un sous algorithme qui noÅlise (affiche les mots comme demandÅ)

Suivant le temps dont on dispose et le niveau de programmation requis, on pourra corriger ou modifier l'algorithme pour plus de efficacitÅ, de robustesse, de gÅnÅralitÅ. On pourrait notamment gÅnÅraliser È un texte, introduire des sÅparateurs autres que l'espace blanc... DÅtaillons maintenant la mise en oeuvre d'un algorithme principal basÅ sur la mÅthode :

dÅcouper la phrase en mots
afficher chaque mot (retournÅ)
   - È partir du milieu (de la phrase et de l'Åcran)
   - en descendant

Il va de soi qu'il faut compter les mots pour connaitre le milieu de la phrase (ou plus exactement le mot du milieu de la phrase). On doit donc Åcrire un sous algorithme que l'on nommera Compter_les_Mots. L'idÅe "dÅcouper la PHRASE en MOTS" montre la variable d'entrÅe : PHRASE, qui sera une chaine de caractÉres. On suppose ici que le langage sous-jacent connait cette notion ; par exemple en Prolog ou en Lisp, on peut traiter et raisonner sur PHRASE en tant que liste, chaine ou symbole, mais cela ne change p
as l'idÅe d'accÅder et de traiter chaque ÅlÅment de la structure...)

La (ou les ???) variable de sortie peut Átre une matrice (une liste È double entrÅe) de caractÉres dans laquelle une ligne (un ÅlÅment) represente un mot (une chaine de caractÉres). Convenons d'appeler MOT cette structure. Ce pourrait Átre un tableau indicÅ par I et J par exemple et MOT[i,j] dÅsignera donc le JÉme caractÉre du IÉme mot. Cela pose dÅjÈ quelques petits problÉmes : ou est stockÅe la longueur de chaque mot ? Faut-il la recalculer È chaque fois ? On pourrait aussi dÅfinir MOT comme un tableau È
un seul indice I ; MOT[I] dÅsignerait alors le I-Éme mot. Ou encore on peut prendre une structure d'enregistrement : MOT[I].texte dÅsignera le I-Éme mot et MOT[I].longueur sera sa longueur. La encore, se pose le problÉme de dimension ....
Une fonction "professionnelle" comme word(str,n) en REXX peut aider È comprendre que renvoyer le niÉme mot de la chaine str peut se faire en "dynamique" au lieu de dÅcouper les mots puis de les utiliser. C'est cette solution que nous utiliserons, car elle semble la plus simple È mettre en oeuvre ; de plus, cela fera moins de discussions sur les variables en entrÅe, en sortie... car il n'y a alors plus de dÅcoupage explicite È faire ! Ce n'est certainement pas la solution la plus efficace car trouver le niÉm
e mot doit certainement repasser par l'exploration de la chaine, le passage et le redÅcoupage en mots numÅro 1,2...n-1.

Bien sÃr, peu de langages ont cette fonction en interne, mais rien n'empÁche de la rÅ-Åcrire : le -Éme mot est une suite de caractÉres commencant apres le premier caractÉre non blanc suivant le mot prÅcÅdent et finissant au prochain caractÉre blanc. Cette dÅfinition (peu comprÅhensible au premier abord) est plus correcte que "toute chaine dÅlimitÅe par des espaces blancs", ou que "un mot commence aprÉs une espace blanc". Le raffinement de ces dÅfinitions a pour but de traiter des cas particuliers qui ne se
rencontrent pas È l'oral. Le tableau suivant

Phrase   Nombre de mots
"oui"      1
"^non^^"      1
"et^alors?"      2
""      0
"^^^^"      0
n'est correctement rempli que si on utilise notre dÅfinition du mot (dans les phrases, le caractÉre ^ reprÅsente un espace blanc). Tout autre dÅfinition ne compte pas correctement les mots ; pourtant, le nombre de mots est "intuitivement Åvident" pour un humain.
LÈ encore, suivant le langage sous-jacent et aussi le but recherchÅ (trouver une solution, manipuler les chaines, utiliser la rÅcursivitÅ, faire une exercice, Åcrire une procÅdure parfaite" dans une bibliothÉque...), on ira plus loin, crÅant une fonction position ou en venant utiliser seulement une boucle de parcours...

2.3.3.5 Algorithme de rÅsolution

La version minimale de l'algorithme suppose qu'on connait le nombre de mots de la phrase ; on note nbm la variable associÅe. Elle suppose aussi qu'on connait le nombre de colonnes de l'Åcran en largeur et qu'on en dÅduit la valeur de me qui signifie milieu de l'Åcran. Pour ne pas s'encombrer avec la gestion des mots, la version minimale affichera les nombres 1,2,..nbm en descendant (voir le deuxiÉme arbre de No‰l prÅcÅdent).
De la mÁme faµon, on dispose de la fonction copies(c,n) qui renvoie n copies du caractÉre c ; ainsi copies('*',5) renvoie '*****' et copies(' ',me) positionne le curseur au milieu de la ligne. L'affichage en descente est automatiquement rÅglÅ par l'instruction Åcrire. Pour Åcarter l'affichage vers la droite et vers la gauche, on gÉre la colonne de dÅbut È gauche (notÅe cdg) et la distance entre les ÅlÅments È afficher (dee). D'oË la version succinte suivante.

{ version prÅliminaire }
  m     <--  nbm/2
  cdg     <--  me -1
  Åcrire copies(' ',cdg) , "1"
  dee     <--  1
  pour j de 1 È m
   cdg     <--  cdg - 3
   Åcrire copies(' ',cdg),2*j,copies(' ',dee),2*j+1
   dee     <--  dee + 3
  fin pour j de 1 È m
L'Åtape suivante consiste È sophistiquer ce premier jet, grÀce È une fonction mot(phrase,i) qui renvoie le i-Éme mot de phrase :
   -   on doit amÅliorer le calcul de nbm/2 qui n'est pas forcÅment un entier
   -   il faut remplacer "1", 2*j et 2*j+1 respectivement par le mot du centre, le
      mot voisin È gauche (mvg) et le mot voisin È droite (mvd).
   -   on doit mettre È jour dee en tenant compte de la longueur des mots voisins
      È gauche et È droite

On obtient alors la  version intermÅdiaire :

{ # 1. Initialisations }
m     <--  1 + partie_entiÉre(nbm/2)
cdg     <--  me -1
mdc     <--  mot(phrase,m)
dee     <--  longueur(mdc)
Åcrire copies(' ',cdg) , mdc
{ # 2. Passage en revue des mots }
pour j de 2 È m
   mvg     <--  mot(phrase,m-j+1)
   mvd     <--  mot(phrase,m+j-1)
   lmg     <--  longueur(mvg)
   cdg     <--  cdg - 1 - lmg
   Åcrire copies(' ',cdg),mvg,copies(' ',dee),mvf
   dee     <--  dee + lmd + 2 + longueur(mvd)
fin pour j

Enfin, il faut rendre les noms de variables explicites. On renommera donc
   nbmots   la variable    nbm,
   milieu    la variable    m,
   milieu_ecran    la variable    me,
   col_debut_mot_gauche    la variable    cdg,
   premier_mot    la variable    mdc
   mot_a_gauche    la variable    mvg
   mot_a_droite    la variable    mvd,
   distance_entre_deux_mots   la variable    dee
Il faut Ågalement prÅvoir la demande de la phrase et le comptage des mots. Nous supposerons qu'on dispose d'une fonction mots(p) qui renvoie le nombre de mots de la phrase p. Il reste ensuite, pour finir l'exercice, È Åcrire les fonctions que nous avons supposÅes existantes, si bien sÃr on sait qu'elles ne font pas partie du langage. La mÅthode que nous avons suivie est dite "programmation descendante". C'est une mÅthode sÃre, fiable et qui assure la cohÅsion de l'ensemble du programme, qui dÅfinit parfaite
ment chaque sous algorithme...On pourra chercher dans la littÅrature classique, comment les autres mÅthodes (programmaation ascendante, linÅaire, en arbre, systÉmique...) s'appliquent et pourquoi elles sont incorrectes.  Un algorithme complet possible est donc
{ Version Finale : ALGORITHME COMAR }
{ 1.   demande de la phrase }
   Åcrire " quelle est votre phrase ? "
   lire Phrase
   Nbmots  <--  mots(Phrase)
{ 2.1   on Åcrit le premier mot (celui du centre)
   È partir du Milieu d'un Åcran avec 80 colonnes }
   Milieu_Åcran  <--  40
   Milieu  <--  1 + partie_entiÉre(Nbmots/2)
   Col_dÅbut_Mot_gauche  <--  Milieu_Åcran - 1
   premier_mot  <--  mot(Phrase,Milieu)
   Åcrire copies(' ',Col_dÅbut_Mot_gauche,
          renverse(premier_mot)
   distance_entre_deux_mots  <--  longueur(premier_mot)
{ 2.2 boucle principale : on rÅcupÉre le mot È droite et le
      mot È gauche, on les Åcrit et on met È jour la
      distance entre les 2 mots }
   pour j de 2 È Milieu
      Mot_È_gauche     <--  mot(PHRASE,Milieu-j+1)
      Mot_È_droite     <--  mot(PHRASE,Milieu+j-1)
      lmg  <--  longueur(Mot_È_gauche)
      Col_dÅbut_Mot_gauche  <--  Col_dÅbut_Mot_gauche-1-lmg
      Åcrire   copies(' ',Col_debut_Mot_gauche),
         renverse(Mot_È_gauche),
         copies(' ',distance_entre_deux_mots),
         renverse(Mot_È_droite)
      distance_entre_deux_mots  <--  distance_entre_deux_mots
             + lmg + longueur(Mot_È_droite) + 2
finpour { j de 2 È Milieu }

2.3.4 Programmes solutions
2.3.4.1 Solution Rexx version 1
Quelques remarques prÅliminaires : 15 lignes (si on ne tient pas compte des commentaires) suffisent en REXX si on utilise un programme de type procÅdural sans rÅcursivitÅ. Cela est du au fait que REXX dispose de nombreuses "fonctions professionnelles" (Åcrites en gras). Nous avons adaptÅ le nom des variables È cause des accents (milieu_Åcran est un nom de variable incorrect, par exemple ; nous avons donc mis milieu_ecran) et au lieu d'une lecture de la phrase. Nous avons prÅfÅrÅ le passage d'un argument car
 cela est plus conforme È l'esprit de Rexx.
/************************************

     le problÉme de COMAR  - GH, Angers
     RÅf.  03/05/1987 12:34

**************************************/

/* 1) on rÅcupÉre la PHRASE, on compte le nombre de mots */

   arg PHRASE
   nbmots = words(PHRASE)

/* 2.1) on Åcrit le premier mot È partir du milieu de
        l'Åcran */

   milieu_ecran    = 40
   milieu    = 1 + trunc(nbmots/2)
   col_debut_mot_gauche = milieu_ecran - 1
   premier_mot = word(PHRASE,milieu )
   say copies(' ',col_debut_mot_gauche) reverse(premier_mot)

/* 2.2)   on initialise la distance entre 2 mots */

   distance_entre_deux_mots = length(premier_mot )

/* 3) boucle principale : on rÅcupÉre le mot È droite et le
      mot È gauche, on les Åcrit, on met È jour la distance
      entre les 2 mots */

do j = 2 to milieu
   mot_a_gauche   = word(PHRASE,milieu + 1 - j )
   mot_a_droite   = word(PHRASE,milieu + j - 1 )
   col_debut_mot_gauche = col_debut_mot_gauche - 1
         - length(mot_a_gauche)
   say copies(' ',col_debut_mot_gauche),
      reverse(mot_a_gauche) ,
       copies(' ',distance_entre_deux_mots) ,
       reverse(mot_a_droite)
   distance_entre_deux_mots = distance_entre_deux_mots ,
      + length(mot_a_gauche ) + length(mot_a_droite) + 2
end /* j = 2 to milieu */
Un lecteur attentif remarquera qu'entre l'algortithme et le programme il y a eu divergence. La variable lmg (pour longueur du mot gauche) n'a pas ÅtÅ introduite dans le programme Rexx, la numÅrotation des Åtapes n'est pas la mÁme non plus. Un tel dÅcalage (dà au dÅveloppement "È chaud" sans retour È l'algorithme) est une pratique malheureusement courante. Il faut se forcer È revenir sur l'algorithme pour Åcrire
{ 1.   demande de la phrase }
   Åcrire " quelle est votre phrase ? "
   lire Phrase
   Nbmots  <--  mots(Phrase)
{ on peut aussi rÅcupÅrer la phrase en tant que paramÉtre }
et corriger le programme Rexx avec les lignes
   lmg = length(mot_a_gauche)
   col_debut_mot_gauche = col_debut_mot_gauche - 1 - lmd
   say   copies(' ',col_debut_mot_gauche),
      reverse(mot_a_gauche) ,
      copies(' ',distance_entre_deux_mots) ,
      reverse(mot_a_droite)
   distance_entre_deux_mots = distance_entre_deux_mots ,
      + lmg + length(mot_a_droite) + 2
end /* j = 2 to milieu */
Le temps qu'on passe È garantir l'identitÅ entre algorithme n'est ni du temps gÀchÅ, ni du temps perdu ; c'est un temps nÅcessaire car obligatoire È une relecture future. Le calcul de la longueur du mot È gauche est stockÅ dans une variable car elle est utilisÅe au moins deux fois et cela Åvite de la recalculer. Par contre, celle du mot È droite n'Åtant utilisÅe qu'une seule fois, la stocker dans une variable ne prÅsente aucun intÅrÁt.
2.3.4.2 Solution Turbo Pascal version 1
Le programme suivant est Åcrit en TurboPascal et utilise des fonctions bibliothÉques (ou fonctions de fichier incluses) appelÅes : compte_mots, mot, copies, renverse. Ces fonctions resemblent Åtrangement aux fonctions Rexx. Et on pourrait dire que ce programme Pascal ressemble au programme Rexx, È moins que ce ne soit l'inverse ou, comme on le verra plus tard, tout se ressemble dans la diffÅrence ! Cela tient, rappelons-le È l'origine commune de ces programmes qui est l'algorithme de rÅsolution. La version
1 de ce programme est Ågalement non rÅcursive.
PROGRAM TEACHER1 ; (*************************************)
                   (*   ProblÉme de Comar -             *)
                   (*   Version 1 non rÅcursive         *)
                   (*************************************)

CONST milieu_ecran = 40 ;
      (* reprÅsente le milieu physique de l'ecran *)

TYPE chaine = string[130] ;

VAR  PHRASE : chaine  ; (* phrase È utiliser *)
     nbmots : integer ; (* nombre de mots dans la phrase *)
     milieu : integer ; (* numÅro du mot du milieu *)
VAR  j      : integer ; (* indice courant de la ligne *)
     premier_mot : chaine ; (* self explanatory ...
                               n'est-ce pas ? *)
     col_debut_mot_gauche : integer ;
     (* colonne È partir de laquelle on doit Åcrire le mot
        de gauche *)
     distance_entre_deux_mots : integer ; (* ok ?? *)
     mot_a_gauche,mot_a_droite : chaine ;

(*$I TEACHER.INC *)
     { contient la dÅfinition des fonctions utilisÅes }

BEGIN (* debut du programme principal *)
(* 1) on rÅcupÉre la PHRASE, on compte le nombre de mots *)
  WRITE( ' Quelle est votre phrase ? ')    ;
  READLN(PHRASE)          ;
  nbmots := compte_mots(PHRASE)    ;

(* 2) on Åcrit le premier mot au milieu de l'Åcran *)
  milieu := 1 + trunc(nbmots/2) ;
  col_debut_mot_gauche := milieu_ecran - 1 ;
  premier_mot := mot(PHRASE,milieu ) ;
  WRITELN ( copies(' ',col_debut_mot_gauche),
            renverse(premier_mot) ) ;

(*  on initialise la distance entre 2 mots *)
  distance_entre_deux_mots := length(premier_mot ) + 1 ;

(* 3) boucle principale : on rÅcupÉre le mot È droite et le
      mot È gauche, on les Åcrit, on met a jour la distance
      entre les 2 mots *)
  FOR j := 2 TO milieu DO BEGIN
    mot_a_gauche := mot(PHRASE,milieu + 1 - j ) ;
    mot_a_droite := mot(PHRASE,milieu + j - 1 ) ;
    col_debut_mot_gauche := col_debut_mot_gauche - 1
      - length(mot_a_gauche) ;
    writeln( copies(' ',col_debut_mot_gauche),
             renverse(mot_a_gauche) ,
             copies(' ',distance_entre_deux_mots),
             renverse(mot_a_droite) ) ;
    distance_entre_deux_mots := distance_entre_deux_mots
     + length(mot_a_gauche) + length(mot_a_droite) + 2
  END ; (* FOR j := 2 TO MILIEU *)

END. (* program TEACHER1 *)
Le fichier inclus comprend la dÅfinition des fonctions dans l'ordre oË elle sont rencontrÅes : la fonction compte_mots puis la fonction mot puis copie et enfin renverse. Le nom court et peu explicite des variables est criticable (i pour indice de boucle est "Åvidemment" un mauvais choix). C'est pourquoi nous avons documentÅ le rÂle de chacune de ces variables.
Bien sÃr, la fonction mot suit notre dÅfinition. On notera seulement qu'elle renvoie la chaÑne vide si le mot cherchÅ n'existe pas (par exemple le mot numÅro 5 d'une phrase ne comportant que 3 mots).
function compte_mots ( PHR : chaine ) : integer ;
(* compte le nombre de mots dans PHR *)
const sep = ' ' ;
     (* le seul separateur valide est " LE BLANC " *)
var nb   : integer ; (* compteur des mots *)
    i   : integer ; (* indice de caractere dans le mot *)
begin (* de la function compte_mots *)
   nb    := 0 ;
   PHR   := PHR + sep ;
   for i := 1 to length(PHR) do begin
      if (PHR[i]=sep) then begin
         nb := nb + 1
      end (* fin de si PHR[i]=sep) *)
   end ; (* for i := 1 to length(PHR) *)
   compte_mots := nb
end ; (* function compte_mots *)
function mot ( PHR : chaine ; X : integer) : chaine ;
(* renvoie le Xeme mot de PHR ou vide *)
const sep = ' ' ;   (*seul separateur valide "LE BLANC *)
var nb   : integer ; (* compteur des mots *)
    i   : integer ; (* indice de caractere dans le mot *)
    L   : integer ; (* longueur de la chaine PHR *)
    debut_mot, fin_mot :  integer ;
        (* indice de dÅbut et de fin du mot dans PHR *)
begin (* de la function mot *)
   nb    := 0 ;
   PHR   := PHR + sep ;
   L   := length(PHR) ;
   i   := 1 ;
   while (i <= L ) and ( NB < (X-1) ) do begin
      if (PHR[i]=sep) then begin
         nb := nb + 1
      end ; (* finsi PHR[i]=sep) *)
      i := i + 1
   end ; (* while ( i <=  L ) and ... *)
(* le dÅbut est donc trouvÅ ; cherchons la fin *)
   debut_mot := i ;
   while (i<=L ) do begin
      if PHR[i] <> sep   then    i := i + 1
            else    begin fin_mot    := i ;
                         i    := L + 1
               end ;
   end ; (* i<= L *)
   if fin_mot > debut_mot
      then mot := copy(PHR,debut_mot,
                               fin_mot -debut_mot)
      else mot := ''
end ; (* function  mot  *)
function copies(K : char ; N : integer) : chaine ;
(* renvoie N copies du caractÉre K *)
var i   : integer ; (*   indice de 1 a N *)
    C   : chaine  ; (*   variable locale de transfert qui
                       est remplie au fur et È mesure *)
begin (* de la function copies *)
      C := '' ;
      for i := 1 to n do C := C + K ;
      copies := C
end ; (* function  copies *)
function renverse( CH : chaine ) : chaine ;
(* retourne la chaine CH sur elle mÁme *)
var i   : integer ; (*   ira de 1 È N *)
    C   : chaine  ; (*   variable de transfert, locale,
                       remplie au fur et È mesure *)
    L   : integer ;
   (*   longueur de la chaine È retourner *)
begin (* de la function renverse *)
   L    := length(CH) ;
   C    := '' ;
   for i    := 1 to L do begin
      C := C + CH[ L + 1 - i]
   end ; (* for i := 1 to n *)
   renverse := C
end ; (* function  renverse *)
2.3.4.3 Solution Turbo Prolog
Si on entre maintenant dans le monde de la rÅcursivitÅ, parce qu'on doit Åcrire en Prolog notre programme) on peut utiliser le programme Turbo-Prolog suivant, avec lÈ encore des prÅdicats secondaires, È savoir :
   ndm      pour nombre de mot ;
   pm      pour premier mot ;
   dm      pour dernier mot ;
   copies   comme prÅcÅdemment
   renverse   aussi.
Le programme principal (sans ces prÅdicats) est le suivant :
/* le problÉme de COMAR en Turbo Prolog */
domains
Nombre, NombrePrecedent, Nmots, NbmoinsUn, Longueur,
LongueurMoins2, ColonneGauche, Distance, N, SousDistance,
SousLongueurGauche, LongueurGauche, SousLongueurDroite
   = integer
Phrase, Reste, Fin, Debut, Chaine, Kar, ChainePrecedente,
Mot, RevMot, PremierCar, FinDuMot, SousMot, DernierCar,
RevSousMot, OKsaufUncar, MotGauche, MotDroit,
BlancsaGauche, BlancsEntre, MotSansBlanc, Tom,
PresqueMotDroit, SousPhrase, SousMotGauche, SousMotDroit,
GaucheMot,DroitMot
   = string

predicates

/*   compter le nombre de mots dans une chaine */
/*   exemple : ndm(N,Phrase) */
/*   N est le nombre de mots dans Phrase  */
   ndm(integer,string)

/*   trouver le premier mot d'une chaine */
/*   exemple : pm(Phrase,DebutFin) */
/*   Debut est le premier mot de Phrase et Fin le reste */
/*   c'est le prÅdicat turbo-prolog prÅdÅfini fronttoken */
/*   avec un ordre diffÅrent pour les paramÉtres */
   pm(string,string,string)

/*   trouver le dernier mot d'une chaine qui en contient N */
/*   exemple : dm(Fin,Phrase,Nbmots) */
/*   Fin est le dernier mot de Phrase qui contient Nbmots */
   dm(string,string,integer)

/*   recopier Nb fois Kat (un caractÉre ou une chaine) */
/*   exemple : copies(Chaine,Nb,Kar) */
/*   Chaine contient Nb copies de Kar */
   copies(string,integer,string)

/*   retourner un mot */
/*   exemple : renverse(RenvMot,Mot) */
/*   RenvMot contient Mot renversÅ, ex leon pour noel ... */
   renverse(string,string)

/*   afficher 2 mots È partir d'une position gauche et
   sÅparÅs par une distance de n caractÉres blancs */
/*   exemple :
     afficher(Col_Gauche,Motgauche,Distance,Motdroit) */
/*   pour afficher Col_Gauche espaces blancs puis Motgauche,
   puis Distance espaces blancs puis Motdroit  */
   afficher(integer,string,integer,string)

/*   afficher tous les ÅlÅments d'une phrase : */
/* sert aussi È dÅcomposer une Phrase en ses composantes ;
   exemple : */
/* aff_tle(Phrase,MotGauche,ColGauche,MotDroit,Distance) */
   aff_tle(string,string,integer,string,integer)

/*   bien Åcrire ou "noÅliser" une phrase entrÅe */
/*   exemple : bienecrire(" voila leon et ada en noel") */
/*   pour rÅpondre au problÉme TEACHER de Comar */
   bienecrire(string)
clauses
afficher(ColonneGauche,MotGauche,Distance,MotDroit)  :-
   copies(BlancsaGauche,ColonneGauche," ") ,
   write(BlancsaGauche,MotGauche) ,
   copies(BlancsEntre,Distance," ") ,
   write(BlancsEntre,MotDroit) , nl .

aff_tle(Mot,MotSansBlanc,40,"",0) :-
   ndm(1,Mot) ,
   frontstr(1,Mot,_,MotSansBlanc) ,
   renverse(Tom,MotSansBlanc) ,
   afficher(40,Tom,0,"") .

bienecrire("") :-
   write( " Mais cette phrase est vide ! ") .
   bienecrire(Phrase ) :-
   aff_tle(Phrase,_,_,_,_)  .

aff_tle(Phrase,MotGauche,ColonneGauche,MotDroit,Distance)  :-
   pm(MotGauche,Phrase,Reste) ,
   ndm(N,Phrase) ,
   dm(PresqueMotDroit,Phrase,N) ,
   concat(SousPhrase,PresqueMotDroit,Reste) ,
   frontstr(1,PresqueMotDroit,_,MotDroit) ,
   aff_tle(SousPhrase,SousMotGauche,SousColonneGauche,
      SousMotDroit,SousDistance) ,
   str_len(SousMotGauche,SousLongueurGauche) ,
      str_len(MotGauche,LongueurGauche) ,
      ColonneGauche = SousColonneGauche - LongueurGauche ,
      str_len(SousMotDroit,SousLongueurDroite) ,
      Distance = SousDistance    + SousLongueurGauche
            + SousLongueurDroite ,
      renverse(GaucheMot,MotGauche) ,
      renverse(DroitMot,MotDroit) ,
      afficher(Colonnegauche,GaucheMot,Distance,DroitMot) .
Une personne qui n'a jamais programmÅ en Prolog doit Átre surprise È la lecture de ces lignes : on n'y voit aucun test explicite, ni aucune boucle. Et pourtant Prolog n'arrÁte pas d'en faire. Une instruction comme a,b,c,... correspond È si a alors si b alors si c...
De mÁme,  Åcrire
   x(a)    :- y.
   x(b)   :- z.
indique È Prolog que si x(a) peut Átre rÅalisÅ, alors y doit Átre exÅcutÅ, ou que sinon il faut essayer de voir si x(b) peut Átre rÅalisÅ. L'exÅcution du programme est rÅalisÅ en Prolog par la recherche de la satisfaction du but principal, par exemple bienecrire("Ceci est un exemple".). Prolog essaie alors "d'unifier" la dÅfinition des prÅdicats avec les valeurs rÅelles du but.
Les clauses relatives aux prÅdicats secondaires peuvent Átre les suivantes :
ndm(0,"").
ndm(Nombre,Phrase) :-
   fronttoken(Phrase,_,Reste) ,
   ndm(NombrePrecedent,Reste) ,
   Nombre = NombrePrecedent + 1 .
pm(Debut,Phrase,Fin) :-
   fronttoken(Phrase,Debut,Fin).
dm("",_,0).
dm(Fin,Fin,1).
dm(Fin,Phrase,2):-
   fronttoken(Phrase,_,Fin).
dm(Fin,Phrase,Nmots) :-
   fronttoken(Phrase,_,Reste) ,
   NbmoinsUn = Nmots - 1 ,
   dm(Fin,Reste,NbmoinsUn) .
copies("",0,_).
copies(Chaine,1,Chaine).
copies(Chaine,Nombre,Kar) :-
   trace(off) ,
   NbmoinsUn = Nombre - 1 ,
   copies(ChainePrecedente,NbmoinsUn,Kar),
   concat(ChainePrecedente,Kar,Chaine) , trace(on).
renverse("","").
renverse(Mot,Mot) :- str_len(Mot,1).
renverse(RevMot,Mot) :-
   frontstr(1,Mot,PremierCar,FinDuMot),
   str_len(Mot,Longueur),
   LongueurMoins2 = Longueur - 2 ,
   frontstr(LongueurMoins2,FinDuMot,SousMot,DernierCar),
   renverse(RevSousMot,SousMot),
   concat(RevSousmot,PremierCar,OKsaufUnCar),
   concat(DernierCar,OKsaufUnCar,Revmot).
2.3.4.4 Solution Turbo Pascal version 2
Et si maintenant nous essayons d'Åcrire un programme Pascal rÅcursif, on peut se servir de l'Åcriture des prÅdicats Prolog comme modÉles. Cela donne :
PROGRAM TEACHER2 ; (* version rÅcursive *)
type   chaine   = string[130]    ;
var    PHRASE     : chaine   ; (* phrase È utiliser *)
   nombre     : integer   ; (* de mots *)
   col_debut_mot_gauche   : integer   ;
      (* colonne È partir de laquelle on doit
         Åcrire le mot de gauche *)
   distance_entre_deux_mots   : integer   ; (* ok ?? *)
(*$I TEACHER.INC *)
(*$I TEACHER.REC *) { contient la procÅdure bienecrire }
BEGIN (* dÅbut du programme principal *)
   (*  on recupere la PHRASE et on appele la procedure bienecrire *)
   write( ' Quelle est votre phrase ? ') ;
   readln(PHRASE) ;
   nombre := compte_mots(PHRASE) ;
   if ( nombre mod 2 ) = 1
      then bienecrire(PHRASE)
      else bienecrire(PHRASE+' ')
END. (* program  TEACHER2  *)
La procÅdure rÅcursive est ici la procÅdure bienecrire, mise dans le fichier Teacher.Rec. On apprÅciera l'emploi des variables locales, globales et on comparera la facilitÅ È changer le contenu des variables par rapport È Turbo-Prolog. Les fonctions, par contre, n'ont peut Átre pas ÅtÅ rÅÅcrites en rÅcursif. Nous ne donnons volontairement pas le dÅtail de leur contenu qui est dans le fichier Teacher.Inc ; on peut, par exemple, reprendre le fichier associÅ È la version 1 du programme.
On pourrait croire que le langage Pascal est plus "riche" que le langage Prolog puisqu'il dispose des deux modes rÅcursif et itÅratif. Il n'en est rien. Prolog sait gÅrer des symboles alors que Pascal ne gÉre que nombres ou des chaines de caractÉre. De plus, prolog dispose d'un mÅcanisme de gestion (ou stratÅgie d'exploration) qui lui confÉre une autonomie dont ne dispose pas Pascal. Par contre, on peut facilement Åcrire un petit interprÅteur Pascal en Prolog et un petit interprÉte Prolog en Pascal, ce qui
montre bien la puissance des deux langages.
Enfin, un programme Turbo Pascal peut appeler un module compilÅ depuis Turbo Prolog et rÅciproquement de faµon È profiter des facilitÅs que chaque langage procure, au niveau du calcul ou du raisonnement. Cette interaction, facile puisque les deux langages sont produits par la mÁme sociÅtÅ, est parfois dure È rÅaliser. Il peut Átre plus dans certains cas plus intÅressant d'Åcrire un programme unificateur (ou mÁme un .Bat) qui appelle l'un et l'autre langage.

procedure bienecrire(PHRAZ:chaine) ;

const milieu_ecran = 40    ;

var nbmots : integer ; (* nombre de mots dans la phrase *)
    milieu : integer ; (* numÅro du mot du milieu *)
    premier_mot   : chaine   ; (* self explanatory ? *)
    mot_a_gauche,mot_a_droite : chaine ;
    longueur_droite,longueur_gauche : integer ;
   (* longueur des chaines prÅcÅdentes *)
    largeur   : integer   ; (* taille du mot *)
    sous_phrase : chaine ;
   (* phrase sans mot_droit ni mot_gauche *)

begin

 (* 1) on compte le nombre de mots *)
    nbmots := compte_mots(PHRAZ) ;

 (* s'il n'y a qu'un seul mot, c'est le milieu de la
    phrase qu'on Åcrit au centre de l'ecran  *)

    if (nbmots<=1) then begin
   col_debut_mot_gauche   := milieu_ecran - 1 ;
   premier_mot      := PHRAZ ;
   writeln (copies(' ',col_debut_mot_gauche) ,
   renverse(premier_mot)) ;
   distance_entre_deux_mots:= length(premier_mot )  ;
   end (* du alors *)

 (* sinon, on rÅcupÉre le mot È droite et le mot È gauche
    qu'on Åcrit, en mettant È jour la distance entre les
    2 mots *)
    else begin
   mot_a_gauche   := mot(PHRAZ,1) ;
   mot_a_droite   := mot(PHRAZ,nbmots) ;
   longueur_droite   := length(mot_a_droite) ;
   longueur_gauche   := length(mot_a_gauche) ;
   largeur    := length(PHRAZ) - longueur_droite
            - longueur_gauche-2  ;
   sous_phrase := copy(PHRAZ,longueur_gauche+2,
                         largeur) ;
   bienecrire(sous_phrase) ;
   col_debut_mot_gauche := col_debut_mot_gauche - 1
                - length(mot_a_gauche) ;
   writeln(copies(' ', col_debut_mot_gauche),
            renverse(mot_a_gauche),
            copies(' ',distance_entre_deux_mots),
            renverse(mot_a_droite) ) ;
   distance_entre_deux_mots := distance_entre_deux_mots
         + length(mot_a_gauche )
                    + length(mot_a_droite) + 2
    end (* finsi nbmots = 1 *)
end ; (* procedure  bienecrire  *)
2.3.4.5 Solution Rexx version 2
De mÁme, REXX est capable d'utiliser la version rÅcursive suivante :
/* COMAR : version 2  */
arg PHRASE
nombre = words(PHRASE)
if (nombre/2) <> trunc(nombre/2)
 then call bienecrire PHRASE
 else do ; PHRASE = PHRASE "."
           call bienecrire PHRASE end
/* endif */
exit

Le sous-programme est alors :
/* procÅdure rÅcursive bienecrire*/
bienecrire : procedure expose col_debut_mot_gauche,
                       distance_entre_deux_mots
   arg PHRAZ
   nbmots = words(PHRAZ)
   if nbmots <= 1
   then do ; milieu_ecran = 40
      col_debut_mot_gauche = milieu_ecran - 1
      premier_mot = PHRAZ
      say copies(' ',col_debut_mot_gauche),
        reverse(premier_mot)
      distance_entre_deux_mots = length(premier_mot )
   end
   else do
       mot_a_gauche = word(PHRAZ,1)
      mot_a_droite = word(PHRAZ,nbmots)
      longueur_gauche = length(mot_a_gauche)
      longueur_droite = length(mot_a_droite)
      debut = longueur_gauche + 2
      fin = length(PHRAZ)   - longueur_droite
            - longueur_gauche - 2 )
      sous_phrase = substr(PHRAZ, debut , fin )
      call bienecrire(sous_phrase)
      col_debut_mot_gauche = col_debut_mot_gauche - 1
         - length(mot_a_gauche)
      say copies(' ',col_debut_mot_gauche)
            reverse(mot_a_gauche) ,
            copies(' ',distance_entre_deux_mots)
            reverse(mot_a_droite)
      distance_entre_deux_mots = distance_entre_deux_mots ,
         + length(mot_a_gauche ) + length(mot_a_droite) + 2
   end
return
La rÅÅcriture rÅcursive en Pascal ou en Rexx est quasi-naturelle : les variables globales deviennent des paramÉtres, l'ordre des instructions de la boucle principale ne change pas. C'est en fait la faµon de contrÂler la boucle qui change.
2.3.4.6 Solutions en Apl, Awk, Dbase, Cobol, C, Lisp
Pour Átre complet, nous donnons aussi des solutions non fortement commentÅes du problÉme de Comar en Apl, Awk, Dbase, Cobol, C et Lisp. On pourra ainsi apprÅcier ressemblances et diffÅrences des traductions en divers langages È partir des mÁmes algorithmes. Par contre, comme nous l'avons dÅjÈ Åcrit, nous rÅservons la traduction en SmallTalk pour le chapitre sur les styles de programmation.
Pour Apl, nous pouvons adapter un algorithme non rÅcursif. Il serait possible de dÅfinir une fonctions copies(n) en Apl avec un rÅsultat explicite (ici la variable de transfert Z), par exemple en Åcrivant
[0] Z C COPIES N
[1] Z C N ‘ ' '
mais cela aurait allongÅ le temps de dÅveloppement et n'aurait rien apportÅ. Nous avons prÅfÅrerrÅ Åcrire directement N ‘ ' ' dans le texte.
COMAR ; Phrase ; NbMots ; MilieuEcran ; Milieu ;
      ColDebutGauche ; DistanceEntre ; PremierMot ; J ;
      Lmg ; Mot_a_Gauche ; Mot_a_Droite
„ 1. Demande de la phrase
  O C 'Quelle est votre phrase ?'
  Phrase C `
„ Admettant qu'un seul espace separe les mots ...
 NbMots C 1 + +/' '=Phrase
 'Il y a ',(rNbMots),' mots dans la phrase <<',Phrase,'>>'
„ 2.1 On ecrit le premier mot, celui du centre a
„      partir du milieu d'un ecran de 80 colonnes
  MilieuEcran     C 40
 Milieu          C 1 + ANbMotsö2
 ColDebutGauche  C MilieuEcran - 1
„ pour utiliser la fonction MOT, la syntaxe est
„ n MOT Phr au lieu de MOT(Phr,n)
 PremierMot     C Milieu MOT Phrase
 DistanceEntre  C 2 + ‘PremierMot
 (ColDebutGauche ‘ ' '), ÉPremierMot
„ 2.2 Boucle principale : on Åcrit le mot a droite, le
„  mot a et on met a jour la distance entre les mots
 DEBUT_DE_LA_BOUCLE_SUR_J:
 J C 2
 ECRITMOT:
 Mot_a_Gauche    C (Milieu+1-J) MOT Phrase
 Mot_a_Droite    C (Milieu+J-1) MOT Phrase
 Lmg             C ‘Mot_a_Gauche
 ColDebutGauche  C ColDebutGauche - (1 + Lmg)
 (ColDebutGauche ‘ ' '), (ÉMot_a_Gauche),
     (DistanceEntre ‘ ' '), ÉMot_a_Droite
 DistanceEntre   C DistanceEntre + Lmg + 2 + ‘Mot_a_Droite
D (Milieu 8 J C J + 1) / ECRITMOT
La fonction Mot dÅrive d'un agorithme prÅcÅdent ; les simplifications sont dues aux focntions de base Apl. Le symbole i correspond È la fonction position : P i ' ' donne le numÅro du premier caractÉre espace dans la chaine P ; k FTXT Âte k caractÉres en dÅbut de TXT, k ETXT vient extraire les k premiers caractÉres de TXT.
[0]   R C MOT P ; NBM
[1]   „ renvoie le mot numero N de la phrase P

[2]   NBM C 1 + +/' '=P
[3]   M  C''
[4]   D (N>NBM) / FIN
[5]   D (N=1)   / EXTRAIT_MOT
[6]   NESP C 1
[7]   RECHERCHE_DEBUT:
[8]      POSESP C P i ' '
[9]      P C POSESP F P
[10]  D (N>NESPCNESP+1) / RECHERCHE_DEBUT
[11]  D (N=NBM)          / RENVOI_DE_P
[12]  EXTRAIT_MOT: POSESP C P i ' '
[13]               P C POSESP E P
[14]  RENVOI_DE_P:
[15]    M C P
[16]  FIN: R C M
En Awk, par contre, on doit dÅfinir les fonctions copies et renverse. Par contre, on peut se dispenser de la variable Phrase, de la fonction nbmots et de la fonction mot car les variables prÅdÅfinies $0, NF et $i fournissent respectivement toute la phrase, le nombre de mots et le i-Éme mot.
# COMAR
BEGIN { ARGV[1] = ""
# 1.   demande de la phrase
   printf " quelle est votre phrase ? "
   getline < "CON"
   Nbmots = NF
# 2.1   on Åcrit le premier mot
   Milieu_ecran = 40
   Milieu = 1 + int(Nbmots/2)
   Col_debut_Mot_gauche = Milieu_ecran - 1
   premier_mot = $Milieu
   print copies(" ",Col_debut_Mot_gauche) \
     renverse(premier_mot)
   distance_entre_deux_mots = length(premier_mot)
# 2.2 boucle principale
   for (j=2;j<=Milieu;j++) {
      Mot_a_gauche    = $(Milieu-j+1)
      Mot_a_droite    = $(Milieu+j-1)
      lmg = length(Mot_a_gauche)
      Col_debut_Mot_gauche = Col_debut_Mot_gauche-1-lmg
      print copies(" ",Col_debut_Mot_gauche) \
         renverse(Mot_a_gauche) \
         copies(" ",distance_entre_deux_mots)  \
         renverse(Mot_a_droite)
      distance_entre_deux_mots = distance_entre_deux_mots \
           + lmg + length(Mot_a_droite) + 2
   } # finpour j de 2 a Milieu
}

# Sous-Programmes

function copies(car,nbcop,       i) { # i est locale
 rtc = ""
 for (i=1;i<=nbcop;i++) { rtc = rtc car }
 return rtc
}
function renverse(chen,          k,l) {
 nehc = "" ; l = length(chen)
 for (k=1;k<=l;k++) { nehc = nehc substr(chen,l-k+1,1)
 }
 return nehc
}
Pour Dbase, on utilise l'instruction @x,y pour positionner le curseur en ligne x, colonne y. Si cette instruction est agrÅable È manipuler, on peut lui reporcher de ne pas Átre trÉs portable, par exemple, en cas de redirection des sorties vers un fichier ou vers l'imprimante. Ce n'est pas le cas des fonctions Åcrire et copies de l'algorithme qui effectuent des affichages linÅaires et progressifs.
* ==================================================
*  LE PROBLEME DE COMAR EN DBASE
*===================================================

* rÅf. Algorithme COMAR, ALS - 1993,
* (voir tout le chapitre 3)

* options d'exÅcution
  clear all
  set talk off
  set echo off
  set step off
  set scoreboard off
  set bell off
  clear

* message d'accueil, saisie de la phrase au clavier
  accept 'entrer votre phrase' to phrase
  read

* initialisation de variables
  store 0 to nbmot
  store 40 to milieuecran
  clear
  @3,1 say 'Voici son arbre de No‰l :'

* dÅclaration du fichier des procÅdures
  set procedure to modules

* appel de la procÅdure pour compter le nombre de mots
do Comptemot with phrase,nbmot

if nbmot>0

* on initialise les variables pour afficher
* le premier mot :
  milieu=INT(nbmot/2)+1
  cdg=milieuecran-1
  mot=''
  inverse=''

* on affiche le premier mot
  do RenMOT with phrase, milieu,mot
  do RENVERSE with mot,inverse
  @5,cdg say inverse

* on initialise la distance entre deux mots :
  dee=LEN(mot)+1
  k=2  && compteur Åquivalent de la boucle pour
  cd=milieu+1
  cg=milieu-1

* indice de la ligne d'affichage sur l'Åcran
  ligne=6
  do while (k<= nbmot)

    if (cg>=1)
     * affichage du mot gauche
       motgauche=''
       inverseg=''
       do RenMOT with phrase,cg,motgauche
       do RENVERSE with motgauche,inverseg
       cdg=cdg-LEN(motgauche)-1
       @ligne,cdg say inverseg
     endif

     if (cd<=nbmot)
     * affichage du mot droit
       motdroit=''
       inversed=''
       DO RenMOT with phrase, cd,motdroit
       do RENVERSE with motdroit,inversed
       * initialisation de la colonne du mot È droite
         cdd=cdg+LEN(motgauche)+dee+2
         @ligne,cdd say inversed
       * incrÅmentation des compteurs
         dee=dee+LEN(motgauche)+LEN(motdroit)+2
     endif
     ligne=ligne+1
     cd=cd+1
     cg=cg-1
     k=k+1
  enddo * boucle tant que
endif * si nbmot > 1
close procedure
ligne = ligne + 1
@ligne, 1 say ""
return
clear all && fin du programme
Nous fournissons maintenant le dÅtail des procÅdures dans l'ordre oË elles sont apparues dans le corps du texte principal :
PROCEDURE Comptemot
PARAMETERS chaine,nbmot
* Ålimination des espaces inutiles en dÅbut et en fin
* de chaine :
  chaine=RTRIM (chaine)
  chaine=LTRIM (chaine)
  i=1
  chaine = chaine + ' '
  IF chaine=' '
     nbmot=0
     * cela veut dire que la chaine n'est constituÅe
     * que de l'espace introduit volontairement

  ELSE
    DO WHILE (i<=LEN(chaine))
       IF ( SUBSTR(chaine,i,1)=' ')
            nbmot=nbmot+1
            i=i+1
       ELSE
           i=i+1
       ENDIF
    ENDDO
  ENDIF
RETURN

PROCEDURE RenMOT
* renvoie le mot numÅro n de chaine
PARAMETERS chaine,n,mot
c=0
STORE LEN(chaine) TO l
i=1
DO WHILE (i<=l) .and. (c<(n-1))
   IF (SUBSTR(chaine,i,1)=' ')
       c=c+1
   ENDIF
   i=i+1
ENDDO
STORE i TO debut
DO WHILE (i<=l) .and. (SUBSTR(chaine,i,1)<> ' ')
       i=i+1
ENDDO
fin=i
IF (fin>debut)
  mot=SUBSTR(chaine, debut, fin-debut)
ENDIF
RETURN

PROCEDURE Renverse
PARAMETERS chaine,inverse
STORE LEN(chaine) TO j
* on concatÉne chacune des lettres en partant de la
* derniÉre et en allant È la premiÉre :
DO WHILE (j>0)
    inverse=inverse+SUBSTR(chaine,j,1)
    j=j-1
ENDDO
RETURN

En langage Cobol, le programme devient presque un problÉme de gestion.  Ce programme, comme le prÅcÅdent en Dbase est trÉs lisible et long, car il utilise des mots qu'on pourrait qualifier d'usuels. Ainsi, les symboles cabalistiques comme := ou  <--  sont remplacÅs par des MOVE ou des COMPUTE.

      * Comar en COBOL
      *
       IDENTIFICATION DIVISION.
       PROGRAM-ID. COMAR.
       AUTHOR. GH.
       DATE-COMPILED.
      *
       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       SOURCE-COMPUTER. IBM.
       OBJECT-COMPUTER. IBM.
      *
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       77 PHRASE PIC X(80) .
       77 CHAINE PIC X(80) .
       77 PHRASE2 PIC X(80) .

       01 CHAINEVIDE .
           02 ELT OCCURS 80 TIMES PIC X.
       01 MOT-PHRASE.
           02 MOT1 OCCURS 80 TIMES PIC X(80).
           02 MOT2 OCCURS 80 TIMES PIC X(80).
       01 MT.
           02 CHMT OCCURS 80 TIMES PIC X .
       01 INVERSE.
           02 CHINV OCCURS 80 TIMES PIC X.
       01 MOTGAUCHE.
           02 CHMG OCCURS 80 TIMES PIC X .
       01 MOTDROIT.
           02 CHMD OCCURS 80 TIMES PIC X .
       01 PREMIERMOT.
           02 CH1 OCCURS 80 TIMES PIC X .
       01  ESPACES.
           02 ESPACE OCCURS 80 TIMES PIC X .

       77  MOTCOUR PIC     X(80)  .
       77  NB      PIC     99     .
       77  NESPA   PIC     99     .
       77  LONG    PIC     99     .
       77  I       PIC     99     .
       77  N       PIC     99     .
       77  J       PIC     99     .
       77  ME      PIC     99 VALUE 40 .
       77  MILIEU  PIC     99     .
       77  CDG     PIC     99     .
       77  CDD     PIC     99     .
       77  DEE     PIC     99     .
       77  COLONNE PIC     99     .
       77  CG      PIC     99     .
       77  CD      PIC     99     .
       77  NBITER  PIC     99     .

      *
       PROCEDURE DIVISION.
       PRINCIPAL.
      * saisie de la phrase entrÅe par l'utilisateur
         DISPLAY  "ENTRER VOTRE PHRASE" .
         ACCEPT CHAINE .
         DISPLAY "VOICI SON ARBRE DE NOEL :".
         MOVE " " TO PHRASE  .
         STRING CHAINE "-" DELIMITED BY '  ' INTO CHAINE.
         STRING PHRASE CHAINE DELIMITED BY "" INTO PHRASE.
         MOVE 1 TO I.
         PERFORM INITIALISE 80 TIMES.
         PERFORM NBMOT.
         IF NB > 0
         PERFORM AFFICHAGE.
         STOP RUN.
Ce programme Cobol a lui aussi des sous-programmes :
   NBMOT.
  * module permettant de compter le nombre de
  * mots dans une phrase
     MOVE 0 TO NESPA.
     MOVE 0 TO NB.
     INSPECT PHRASE TALLYING NB FOR ALL SPACE BEFORE "-" .
     INSPECT CHAINE TALLYING NESPA FOR ALL SPACE.
     IF NESPA EQUAL 79
     COMPUTE NB = 0.

    MOT.
  * module renvoyant le n-iÉme mot d'une phrase
     MOVE CHAINEVIDE TO MOT1(1).
     IF N = 0 OR N = 1
          UNSTRING CHAINE DELIMITED BY SPACE OR "-"
                   INTO MOT1(1)
        ELSE
          MOVE CHAINEVIDE TO PHRASE2.
          MOVE "-" TO PHRASE2
          STRING PHRASE2 PHRASE DELIMITED BY "-"
                   INTO PHRASE2
                PERFORM DECOUPAGE N TIMES.

    DECOUPAGE.
   * sous_module du module mot servant È dÅcouper
   * une phrase en deux
      UNSTRING PHRASE2 DELIMITED BY SPACE OR "-"
                   INTO MOT1(2) MOT1(1).
      INSPECT PHRASE2 REPLACING FIRST SPACE BY "@" .


    RENVERSE.
   * renverse les lettres d'un mot
      MOVE CHAINEVIDE TO INVERSE.
      MOVE CHAINEVIDE TO MT.
      MOVE MOT1(1) TO MT.
      MOVE 0 TO LONG.
      INSPECT MT TALLYING LONG FOR CHARACTERS
                  BEFORE INITIAL SPACE.
      MOVE 1 TO I .
      MOVE LONG TO J .
      PERFORM RENVERSE1 LONG TIMES.

    RENVERSE1.
   * boucle servant È inverser les lettres
      MOVE CHMT(J) to CHINV(I).
      COMPUTE J = J - 1.
      COMPUTE I = I + 1.

    INITIALISE.
      MOVE SPACE TO ELT(I).
      COMPUTE I = I + 1.

    COPIES.
      MOVE "~" to ESPACE(colonne).
      STRING ESPACES MOTCOUR DELIMITED BY "~" INTO ESPACES.

    AFFICHAGE.
   * fonction principale (de l'affichage no‰lisÅ)
      COMPUTE MILIEU ROUNDED =  (NB + 1) / 2 .
      MOVE MILIEU TO N.
      COMPUTE CDG = ME - 1.
      PERFORM MOT .
      PERFORM RENVERSE.
      MOVE INVERSE TO PREMIERMOT.
      MOVE CDG TO COLONNE.
      MOVE PREMIERMOT TO MOTCOUR.
      MOVE CHAINEVIDE TO ESPACES.
      PERFORM COPIES.
      DISPLAY ESPACES .
      COMPUTE DEE = LONG.
      COMPUTE CDD = ME .
      COMPUTE CG = MILIEU .
      COMPUTE CD = MILIEU .
      COMPUTE NBITER = MILIEU - 1.
      PERFORM SAISIE NBITER TIMES.
Il reste enfin È donner le dÅtail du module de saisie. Nous avons Åcrit celui-ci comme les autres, avec peu de connaissances en Cobol, ce qu'auront remarquÅ les professionnels de ce langage. En particulier, nous n'avons utilisÅ aucune astuce, mais nous avons plutÂt voulu rester fidÉle aux algorithmes et concevoir un programme lisible.

    SAISIE.
      COMPUTE CDD = CDD + DEE + 1
      MOVE CHAINEVIDE TO ESPACES.
      COMPUTE CG = CG - 1.
      MOVE CG TO N.
      PERFORM MOT.
      PERFORM RENVERSE.
      MOVE INVERSE TO MOTGAUCHE.
      COMPUTE CDG = CDG - LONG - 2.
      MOVE CDG TO COLONNE.
      MOVE CHAINEVIDE TO MOTCOUR.
      MOVE MOTGAUCHE TO MOTCOUR.
      PERFORM COPIES.
      COMPUTE DEE = DEE + LONG.
      COMPUTE CD = CD + 1.
      IF CD < NB OR CD = NB
           MOVE CD TO N
           PERFORM MOT
           PERFORM RENVERSE
           MOVE INVERSE TO MOTDROIT
           MOVE CDD TO COLONNE
           MOVE CHAINEVIDE TO MOTCOUR
           MOVE MOTDROIT TO MOTCOUR
           PERFORM COPIES
           MOVE LONG TO DEE .
      DISPLAY ESPACES.

L'Åcriture en C itÅratif nous ramÉne È une traduction plus proche des algorithmes :
/*************************************/
   ProblÉme de Comar en C - Version 1 non rÅcursive
/*************************************/

#include 
#include 
#include 
#define milieu_ecran 40

typedef char chain[80] ;
         /* reprÅsente le milieu physique de l'ecran */

 chain   PHRASE ; /* phrase È utiliser */
 int     nbmots ; /* nombre de mots dans la phrase */
 int     milieu ; /* numÅro du mot du milieu */
 int     j ; /* indice courant de la ligne */
 chain   premier_mot ; /* self explanatory ...*/
 int     col_debut_mot_gauche ;
         /* colonne È partir de laquelle on doit Åcrire
            le mot de gauche */
 int     distance_entre_deux_mots ;
 chain   mot_a_gauche,mot_a_droite ;

void main()
{ /* 1) on rÅcupÉre la PHRASE, on compte ses mots */
  printf(" Quelle est votre phrase ? ")     ;
  gets(PHRASE)           ;
  nbmots = Compte_Mots(PHRASE)     ;
  /* 2) on Åcrit le premier mot au milieu de l'Åcran */
  milieu = 1 + nbmots/2  ;
  col_debut_mot_gauche = milieu_ecran - 1  ;
  premier_mot[0] = '\0'  ;
  strcat(premier_mot,mot(PHRASE,milieu))  ;
  printf("\n%s%s", Copies(" ",col_debut_mot_gauche),
      strrev(premier_mot))  ;
 /*  on initialise la distance entre 2 mots */
  distance_entre_deux_mots = strlen(premier_mot ) + 1  ;
 /* 3) boucle principale : on rÅcupÉre le mot È droite et
       le mot È gauche, on les Åcrit, on met a jour la
       distance entre les 2 mots */
  for (j = 2 ; j <= milieu ; j++) {
    mot_a_gauche[0] = '\0'  ;
    strcat(mot_a_gauche,mot(PHRASE,milieu + 1 - j) )  ;
    mot_a_droite[0] = '\0'  ;
    strcat(mot_a_droite,mot(PHRASE,milieu + j - 1) )  ;
    col_debut_mot_gauche = col_debut_mot_gauche - 1
      - strlen(mot_a_gauche)  ;
    printf("\n%s %s",Copies(" ",col_debut_mot_gauche),
        strrev(mot_a_gauche))  ;
    printf("%s %s",Copies(" ",distance_entre_deux_mots),
        strrev(mot_a_droite))  ;
    distance_entre_deux_mots = distance_entre_deux_mots
     + strlen(mot_a_gauche) + strlen(mot_a_droite) + 2 ;
  }  ;  /* FOR j := 2 TO MILIEU */
  printf("\n")  ;
} /* program TEACHER1 */

Les fonctions (sous-programmes) utilisÅes sont ici :
char* SousChaine(chain chai,int debut,int lon){
/* renvoie une souschaine de longueur lon È
   partir de la position debut */
chain substrg ;
int i ; /* indice courant dans chai */
int z ; /* indice courant dans substrg */
z=0 ;
i=debut ;
 while ( (i<=(debut+lon)) && (i<=strlen(chai) ) ){
     substrg[z]=chai[i] ;
     ++z ; ++i ;
 } /* fin du while */
 substrg[z] = '\0' ;
return substrg ;
} /* fin de la fonction SousChaine */


int Compte_Mots(PHR)
/* compte le nombre de mots de PHR */
chain  PHR ;
{ /* le seul separateur valide est " LE BLANC " */
#define sep  ' '
#define sepc " "
  int    nb ; /* compteur des mots */
  int    i ; /* indice de caractere dans le mot */
  nb    = 0  ;
  strcat(PHR,sepc) ;
  for (i = 1 ; i <= strlen(PHR) ; i++) {
      if ((PHR[i]==sep)) { nb = nb + 1 ; }
      /* fin de si PHR[i]=sep) */
   } /* for i := 1 to length(PHR) */
   return nb ;
}  /* function Compte_Mots */

char*  mot(chain PHR,int X)
{ /* renvoie le Xeme mot de PHR ou vide */
#define sep  ' '
#define sepc " "
  /*seul separateur valide "LE BLANC */
 int   nb ; /* compteur des mots */
 int   i ; /* indice de caractere dans le mot */
 int   L ; /* longueur de la chaine PHR */
 int   debut_mot, fin_mot ;
       /* indice de dÅbut et de fin du mot dans PHR */
 chain motr ; /* mot renovyÅ */
 nb    = 0  ;
 strcat(PHR,sepc)  ;
 L   = strlen(PHR)  ;
 i   = 0  ;
 while ((i <= L ) && ( nb < (X-1) ) ) {
      if ((PHR[i]==sep)) { nb = nb + 1 ; }
      /* finsi PHR[i]=sep) */
      i = i + 1 ;
 }  /* while ( i <=  L ) and ... */
/* le dÅbut est donc trouvÅ  ; cherchons la fin */
 debut_mot = i  ;
 while ((i<=L ) ) {
      if (PHR[i] != sep)    i = i + 1 ;
            else { fin_mot    = i  ;
                         i    = L + 1 ;
               }
 }  /* i<= L */
 motr[0]='\0' ;
 if (fin_mot > debut_mot) {
    strcat(motr,SousChaine(PHR,debut_mot,fin_mot-debut_mot))  ;}
 return motr ;
} /* function  mot  */


char* Copies(char* K,int N){
/* crÅe une chaine de n fois la chaine k */
int     i = 0  ; /* indice de 1 a N */
chain   C      ; /*  variable locale de transfert qui est
                    remplie au fur et È mesure */
printf("%s",K)  ;
C[0]='\0' ;
while (i!=N) { strcat(C,K) ;  i++ ;
} /* fin du while */
C[i]='\0' ;
return C ;
} /* fin de la fonction Copies */
Une remarque, toutefois pour le choix des fonctions : nous n'avons pas rÅÅcrit la fonction renverse car elle existe dÉjÈ en Turbo C (mais aussi dans d'autres versions) ; elle se nomme strrev et se trouve dans le "header" string.h ; puisque le langage C permet une Åcriture rÅcursive, pourquoi nous priver d'une telle version ? Nous ne donnons ici que le corps du programme principal, puisque les autres fonctions sont les mÁmes.
/******************************************/
   ProblÉme de Comar en C - Version 2 rÅcursive
/*****************************************/

#include 
#include 
#include 

typedef char chain[80];
#define milieu_ecran 40 /* reprÅsente le milieu physique de l'ecran */
chain   PHRASE; /* phrase È utiliser */
int     nombre; /* de mots */
int     col_debut_mot_gauche;
        /* colonne È partir de laquelle on doit
           Åcrire le mot de gauche */
int     distance_entre_deux_mots; /* ok ?? */
/*$I Comarc.Inc*/
/* on y trouve : SousChaine  CompteMots mot Copies */
void bienecrire(chain PHRAZ)
{   int   nbmots; /* nombre de mots dans la phrase */
    int   milieu; /* numÅro du mot du milieu */
    chain premier_mot; /* self explanatory ? */
    chain mot_a_gauche,mot_a_droite;
    int  longueur_droite,longueur_gauche;
   /* longueur des chaines prÅcÅdentes */
    int    largeur; /* taille du mot */
    chain  sous_phrase;
    chain rep ;
    chain PHRZ ;
   /* phrase sans mot_droit ni mot_gauche */
 /* 1) on compte le nombre de mots */
    nbmots = Compte_Mots(PHRAZ) ;

 /* s'il n'y a qu'un seul mot, c'est le milieu de la
    phrase qu'on Åcrit au centre de l'ecran  */

   if ((nbmots<=1)) {
   col_debut_mot_gauche   = milieu_ecran - 1 ;
   premier_mot[0]   = '\0' ;
   strcat(premier_mot,PHRAZ) ;
   printf("\n%s%s", Copies(" ",col_debut_mot_gauche),
      strrev(premier_mot)) ;
   distance_entre_deux_mots= strlen(premier_mot )  + 1 ;
   } /* du alors */

 /* sinon, on rÅcupÉre le mot È droite et le mot È
    gauche qu'on Åcrit, en mettant È jour la distance
    entre les 2 mots */

 else {
   mot_a_gauche[0] = '\0' ;
   strcat(mot_a_gauche,mot(PHRAZ,1) ) ;
   mot_a_droite[0] = '\0' ;
   strcat(mot_a_droite,mot(PHRAZ,nbmots) ) ;
   longueur_droite   = strlen(mot_a_droite) ;
   longueur_gauche   = strlen(mot_a_gauche) ;
   largeur = strlen(PHRAZ) - longueur_droite
             - longueur_gauche - 2  ;
   sous_phrase[0] = '\0' ;
   strcat(sous_phrase,SousChaine(PHRAZ,longueur_gauche+1,
                                 largeur-1)) ;
   bienecrire(sous_phrase) ;
   col_debut_mot_gauche = col_debut_mot_gauche - 1
                - strlen(mot_a_gauche) ;
   printf("\n%s%s", Copies(" ",col_debut_mot_gauche)
          ,strrev(mot_a_gauche)) ;
   printf("%s%s"  , Copies(" ",distance_entre_deux_mots)
          ,strrev(mot_a_droite)) ;
   distance_entre_deux_mots = distance_entre_deux_mots
     + strlen(mot_a_gauche )+ strlen(mot_a_droite) + 2;
    } /* finsi nbmots = 1 */
  return ;
}  /* procedure  bienecrire  */

void main()
{ /* dÅbut du programme principal */

  /*  on recupere la PHRASE et on appele la
      procedure bienecrire */
   printf(" Quelle est votre phrase ? ") ;
   gets(PHRASE) ;
   nombre = Compte_Mots(PHRASE) ;
   if (( nombre % 2 ) == 1) bienecrire(PHRASE);
      else bienecrire(strcat(PHRASE," ")) ;
} /* program  TEACHER2  */

Continuons maintenant par un programme (on dit plutÂt lÈ encore une fonction) Lisp ; l'appel de cette fonction se fait simplement par (comar) :
; COMAR en LELisp
;
( de comar ()
   ( print "quelle est votre phrase ? " )
   ( setq phrase  ( read ) )
   ( setq phrase ( catenate phrase ( readline ) " " ) )
   ( tycls)(tycursor 0 0)
   ( print "Merci. Voici votre phrase pour vÅrification")
   ( print phrase)
   ( print " Voici son arbre de no‰l")
   ( cond
        ( (= ( modulo (nb_mots phrase 0) 2 ) 1 )
          (affich phrase ) )
        ( t (affich (catenate phrase " ")) )
   ) ; fin du cond
     ( print "")
) ; fin de la fonction principale

Les fonctions (sous-programmes) associÅes sont
; comptage du nombre de mots dans la phrase: È partir du
; nombre d'espaces on dÅtermine le nombre de mots
( de nb_mots (chaine i)
; si la fonction est utilisÅe sans la fonction (comar)
; il faut concatÅner un espace au dÅbut de la phrase
 ( cond
   ( ( = i ( slen chaine ) ) 0 )
   ( ( eqstring " " ( substring chaine i 1) )
                    (+ 1 (nb_mots chaine (+ i 1))) )
   ( t (nb_mots chaine (+ i 1)) )
 ) ; fin du cond
) ; fin de la fonction nb_mots
;
; dÅtermine s'il existe un espace dans la chaine
( de espace (chaine i )
  ( cond
     ( (= i (slength chaine) ) 0 )
     ( (eqstring " " (substring chaine i 1) )  (+ i 1)  )
     ( t (espace chaine (+ i 1)) )
  ) ; fin du cond
) ; fin de la fonction espace
;
; position du premier caractÉre du niÉme mot d'une chaÑne
( de debut ( chaine n i )
 ( cond
      (( = n 1 ) i )
      (t (debut(substring chaine(espace chaine 0))(- n 1)
               ( + i ( espace chaine 0 ) ) ) )
 ) ; fin du cond
) ; fin de la fonction debut

; position du dernier caractÉre du niÉme mot d'une chaÑne
( de fin ( chaine n i )
    ( cond
        (( = ( + n 1 ) 1 ) ( - i 1 ) )
        (t (fin(substring chaine(espace chaine 0))(- n 1)
                   ( + i  ( espace chaine 0 ) ) ) )
    ))
;
; renvoie le niÉme mot d'une chaine
( de mot ( chain n )
   (substring chain(debut chain n 0)
      (- (fin (catenate chain " " ) n 0)
         (debut chain n 0 ) ) )
) ; fin de la fonction mot
;
; renverse les lettres d'un mot
( de renverse ( ch )
  ( cond
    (( = (slength ch ) 0 ) "" )
    (t (catenate (substring ch (-(slength ch) 1) 1)
       (renverse (substring ch 0 (- (slength ch )1)))))
   ) ; fin du cond
) ; fin de la fonction renverse
;
; crÅation de n espaces blancs
( de copies ( n )
     ( duplstring n " " ))
;
; affichage de la phrase no‰lisÅe
( de affich ( chaine )
(setq me 40) ; milieu de l'ecran
(cond ((<= (nb_mots chaine 0) 1)
      (setq cdg (- me 1)) ; cdg= colonne de gauche
      (setq mdc chaine )  ; mdc=mot du centre
      (setq dee(+(slength mdc)2)) ; distance entre 2 mots
      ( print (copies cdg)(renverse mdc))
 ) ; fin de la premiÉre condition
 (t (let ((motgauche (mot chaine 1))
  (motdroit (mot chaine (nb_mots chaine 0) ) )
  (lmg ( slen (mot chaine 1))); longueur du mot È gauche
  (lmd ( slen (mot chaine (nb_mots chaine 0))))
  ; lmd : longueur du motÈ droite
  ) ; fin du let
    (setq largeur (- (slen chaine ) lmg lmd 2))
    (setq sous_ch (substring chaine (+ lmg 1) largeur))
    (affich sous_ch) ; APPEL rÅcursiF
    (setq cdg (- cdg lmg 1))
    (print (copies cdg)(renverse motgauche)
           (copies dee)(renverse motdroit))
    (setq dee (+ dee (slength motdroit)
                     (slength motgauche)2)))
 ) ; fin de la seconde condition
) ; fin du cond
) ; fin de la fonction affich

AprÉs rÅflexion, il nous semble intÅressant ou pour le moins curieux d'essayer d'Åcrire le mÁme programme en Lisp mais en utilisant des fonctions non rÅcursives. Cet exercice de style que d'aucuns (puristes) trouveront "inÅlÅgante" montre cependant (et nous ne visons ici pas d'autre but) de montrer qu'on peut tout Åcrire avec un langage puissant, collant ainsi È un algorithme typique, mÁme si ce n'est pas dans l'esprit du langage.
; COMAR en LELisp itÅratif
( de comar ()
 (print "quelle est votre phrase ? " )
 (setq phrase (read) )
 (setq phrase (catenate  phrase ( readline)  ) )
 (affichage phrase ) ; appelle la fonction qui no‰lise
); fin de la fonction comar
De la mÁme faµon, on rÅÅcrit aussi les sous-programmes en itÅratif, car de nombreux Lisp possÉdent des boucles Pour, Tant que... :
( de nb_mots ( chaine )
; cette fonction n'est efficace que si la phrase est
; entr"e sans espace redondant entre les mots ou au dÅbut
; ou È la fin de la phrase
  (setq chaine ( catenate " " chaine ) )
  (setq i 0 )
  (setq nombre 0 )
  (for (i 0 1 (slen chaine ) )
              (if (eqstring " " (substring chaine i 1) )
                  (setq nombre ( + 1 nombre) ) )
   ) ; fin du for
   nombre
) ; fin de la fonction nb_mots
;
( de espace ( chaine pos_espace )
  (setq souschaine ( substring chaine pos_espace ) )
  (setq pos_espace 1 )
  (while (and (not
  (eqstring(substring souschaine pos_espace 1) " " ))
              ( <= pos_espace ( slen chaine ) ) )
              ( setq pos_espace ( + 1 pos_espace) )
   ) ; fin du while
   pos_espace ) ; fin de la fonction espace
;
( de fin ( chaine n )
  (cond ((= n (nb_mots chaine)) (setq i (slen chaine)))
        (t (setq i 0 )
          (setq compteur 0 )
          (while (and (<> n compteur)
                      (<= i (slen chaine) ) )
                 ( setq compteur ( + compteur 1 ) )
                 ( setq i ( + i (espace chaine i ) ) )
           ) ; fin du while
       )) ; fin du cond
   i) ; fin : renvoi de la position du caractÉre

; position du premier caractÉre du niÉme mot d'une chaÑne
( de debut ( chaine n )
    ( fin chaine ( - n 1 ) )
    ; explication: le dÅbut d'un mot c'est en fait la fin
    ; du mot prÅcÅdent
) ; fin de la fonction debut
;
; renvoie le niÉme mot d'une chaine
( de mot ( chaine n )
  ( substring chaine (debut chaine n)
    (- (fin chaine n)  (debut chaine n) ) )
) ; fin de la fonction mot
;
; renverse les lettres d'un mot
( de renverse ( mot )
  (setq inverse "" )
  (setq j ( slen mot ) ) ; initialisation du compteur
  (while ( <> j (- 1 ) )
    ; -1 est la condition d'arrÁt car la position du
    ; premier caractÉre d'une chaine de caractÉres est 0
    (setq inverse (catenate inverse (substring mot j 1)))
    ( setq j (- j 1) )
   ) ; fin du while
   inverse ; renvoie le mot renvers, sur lui-mÁme
) ; fin de la fonction renverse
;
; renvoie le milieu d'une chaine
( de milieu ( chaine )
     ( + ( div ( nb_mots chaine ) 2 ) 1 )
) ; fin de la fonction milieu
;
; crÅe une ligne de n espaces
( de copies ( n )
     ( duplstring n " " )
) ; fin de la fonction
;
; affichage dÅtaillÅ
( de affichage ( chaine)
  (tycls )
  (print "Merci.Voici votre phrase pour vÅrification" )
  (print phrase )
  (print " Voici son arbre de No‰l" )
  (terpri )
; dÅclaration de variables
  (setq m ( milieu chaine ) )
  ; m : mot du milieu de la chaine
  (setq me 40 )
  ; me : milieu de l'ecran
  (setq mdc ( mot chaine m ) )
  ; mdc : premier mot
  (setq cdg ( - me 1 ) )
  ; cdg : colonne dÅbut du mot gauche
  ( setq dee (+ ( slen mdc ) 1 ) )
  ; dee : distance entre 2 mots

; partie principale de la fonction affichage
  (print ( catenate ( copies cdg ) ( renverse mdc ) ) )
  ( setq m1 m )
  ( setq m2 m )
  ( repeat  ( - m 1)
    ; affectation des variables
    ( setq mot_a_gauche ( mot chaine ( decr m1 ) ) )
    ( setq mot_a_droite ( mot chaine ( incr m2 ) ) )
    ( setq lmg ( slen mot_a_gauche ) )
    ( setq cdg ( - cdg 1 lmg ) )
    ; fin de l'affectation des variables
    ( print ( copies cdg ) ( renverse mot_a_gauche )
            ( copies dee ) ( renverse mot_a_droite ) )
    ( setq dee (+ dee lmg ( slength mot_a_droite ) 2 ) )
   ) ; fin du repeat
   ( print "" )
) ; fin de la fonction affichage
* sauf  Smalltalk qui sera dÅtaillÅ dans le chapitre sur les styles de programmation.

{PAGE|35}