Le problème 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.