L'exercice sur le calcul du nombre de jours permet d'aborder plusieurs
questions intéressantes sur la syntaxe, la sémantique, la conception, la
lisibilité et l'efficacité des programmes Scheme.
Il s'agit d'écrire une fonction qui donne le nombre de jours du mois
m
dans une année bissextile (le mois est donné sous la forme d'un entier).
On peut résoudre ce problème soit en supposant que le nombre
m donné
représente toujours un mois valide (c'est-à-dire
m est un entier
entre 1 et 12), soit en vérifiant que le nombre donné est valide.
Cela donne deux spécifications différentes pour la fonction
nb-jours:
-
Cas 1: en faisant l'hypothèse que m est un nombre valide
;;;
;;;
ou de façon équivalente
;;;
;;;
;;;
Dans ce cas la fonction peut renvoyer n'importe quoi quand m n'est pas
valide : une erreur, une réponse erronée, pas de réponse.
- Cas 2: renvoyer une erreur lorsque m n'est pas un nombre valide
;;;
;;;
;;;
Dans ce cas quels sont les tests nécessaires pour signaler
des erreurs (d'autres erreurs étant directement signalées par Scheme)
Voici un certain nombre de solutions possibles, commentées du point de vue
de leur lisibilité, de leur efficacité...
Toutes les fonctions ci-dessous répondent à la première spécification.
-
nb-jours-v1 teste successivement toutes les valeurs de
m possibles en utilisant des formes if imbriquées. On peut
mesurer l'efficacité de cette fonction par le nombre de tests
effectués pour évaluer le résultat: ici il y a au plus 12 tests.
;;;
;;;
;;;
(define (nb-jours-v1 m)
(if (= m 1)
31
(if (= m 2)
29
(if (= m 3)
31
(if (= m 4)
30
(if (= m 5)
31
(if (= m 6)
30
(if (= m 7)
31
(if (= m 8)
31
(if (= m 9)
30
(if (= m 10)
31
(if (= m 11)
30
(if (= m 12)
31)))))))))))))
Remarque : au lieu
d'écrire à la fin
(if (= m 11) 30 (if (= m 12) 31
)))
on pourrait écrire
(if (= m 11) 30
31)
puisqu'on suppose que m est entre 1 et 12 (cette
façon de faire est adoptée dans la version nb-jours-v4).
- nb-jours-v2 effectue exactement les mêmes calculs que
nb-jours-v1 mais l'utilisation de la forme cond rend
sa présentation plus lisible.
(define (nb-jours-v2 m)
(cond ((= m 1) 31)
((= m 2) 29)
((= m 3) 31)
((= m 4) 30)
((= m 5) 31)
((= m 6) 30)
((= m 7) 31)
((= m 8) 31)
((= m 9) 30)
((= m 10) 31)
((= m 11) 30)
((= m 12) 31)))
- nb-jours-v3 regroupe, dans des formes or, les
valeurs de m donnant le même résultat. Cette version est plus
concise à la lecture, mais au niveau des calculs effectués elle est
à peine meilleure que les deux premières (au plus 8 tests).
Remarquez que puisqu'on suppose que m est entre 1 et 12, on n'a
pas explicitement testé les valeurs de m pour lesquelles le
résultat est 30.
(define (nb-jours-v3 m)
(if (= m 2)
29
(if (or (= m 1) (= m 3) (= m 5) (= m 7) (= m 8) (= m 10)(= m 12))
31
30)))
- nb-jours-v4 effectue exactement les mêmes calculs que
nb-jours-v3 mais l'utilisation de la forme cond rend
sa présentation plus lisible.
(define (nb-jours-v4 m)
(cond
((= m 2) 29)
((or (= m 1) (= m 3) (= m 5) (= m 7) (= m 8) (= m 10)(= m 12)) 31)
(else 30)))
- nb-jours-v5 explicite le calcul en fonction de la parité
de m et de sa position par rapport à 7. En effet, mis à part le
mois de Février, le nombre de jours d'un mois vaut 31 lorsqu'il
s'agit
-
soit d'un mois « pair » après Juillet,
- soit d'un mois « impair » avant Juillet;
et il vaut 30 sinon.
Cette fonction effectue au plus 5 tests pour évaluer le résultat,
elle est donc plus efficace que les précédentes.
(define (nb-jours-v5 m)
(if (= m 2)
29
(if (or
(and (> m 7) (even? m))
(and (<= m 7) (odd? m)))
31
30)))
- nb-jours-v6 traduit la parité de m en son reste dans la
division entière de m par 2, ce qui permet de calculer le résultat
en au plus 2 tests.
(define (nb-jours-v6 m)
(if (= m 2)
29
(if (<= m 7)
(+ 30 (remainder m 2))
(- 31 (remainder m 2)))))
- nb-jours-v7 est équivalente à nb-jours-v6, mais
montre une utilisation « vraiment » fonctionnelle de la forme
if: c'est le résultat des alternatives qui est ajouté au
nombre 30.
(define (nb-jours-v7 m)
( + 30
(if (= m 2)
-1
(if (<= m 7)
(remainder m 2)
(- 1 (remainder m 2))))))
- nb-jours-v8 est équivalente à nb-jours-v4, mais
utilise une autre syntaxe : la forme case
(define (nb-jours-v8 m)
(case m
((2) 29)
((1 3 5 7 8 10 12) 31)
(else 30) ) )
Dans cette section on transforme les fonctions
nb-jours-vi en
des fonctions
nb-jours-vi-bis, qui renvoient une erreur lorsque
le nombre donné n'est pas valide. On intègre uniquement les tests qui
permettent de signaler les erreurs qui ne sont pas signalées par
Scheme.
-
Remarquez que lorsque m n'est pas un nombre,
la fonction nb-jours-v1 signalera une erreur du fait de
l'utilisation de la fonction = dans les tests. Mais lorsque m est
un nombre qui n'est pas un entier entre 1 et 12, la fonction
nb-jours-v1 ne rend aucune valeur --- car le if le plus
interne n'a pas d'expression alternante. On rajoute donc une
expression alternante qui signale l'erreur.
(define (nb-jours-v1-bis m)
(if (= m 1)
31
(if (= m 2)
29
(if (= m 3)
31
(if (= m 4)
30
(if (= m 5)
31
(if (= m 6)
30
(if (= m 7)
31
(if (= m 8)
31
(if (= m 9)
30
(if (= m 10)
31
(if (= m 11)
30
(if (= m 12)
31
(erreur 'nb-jours-v1-bis "mois invalide"))))))))))))))
- De même la fonction nb-jours-v2 ne rend
aucune valeur lorsque m est un nombre non valide
--- car la conditionnelle ne comporte
pas de clause else. On rajoute donc une clause else qui
signale l'erreur.
(define (nb-jours-v2-bis m)
(cond ((= m 1) 31)
((= m 2) 29)
((= m 3) 31)
((= m 4) 30)
((= m 5) 31)
((= m 6) 30)
((= m 7) 31)
((= m 8) 31)
((= m 9) 30)
((= m 10) 31)
((= m 11) 30)
((= m 12) 31)
(else (erreur 'nb-jours-v2-bis "mois invalide"))))
- Lorsque m est un nombre qui n'est pas un entier entre 1 et 12,
la fonction nb-jours-v3 rend
la valeur de l'alternant du if le plus interne, soit 30.
Pour pouvoir signaler l'erreur, il faut tester explicitement les cas où
la valeur rendue est 30; c'est ce que fait la fonction nb-jours-v3-bis.
(define (nb-jours-v3-bis m)
(if (= m 2)
29
(if (or (= m 1) (= m 3) (= m 5) (= m 7) (= m 8) (= m 10)(= m 12))
31
(if (or (= m 4) (= m 6) (= m 9) (= m 11))
30
(erreur 'nb-jours-v3-bis "mois invalide")))))
- De même la fonction nb-jours-v4 rend la valeur 30
lorsque m est un nombre non valide ; on rajoute donc une clause qui
teste explicitement les cas où la valeur rendue est 30.
(define (nb-jours-v4-bis m)
(cond
((= m 2) 29)
((or (= m 1) (= m 3) (= m 5) (= m 7) (= m 8) (= m 10)(= m 12)) 31)
((or (= m 4) (= m 6) (= m 9) (= m 11)) 30)
(else (erreur 'nb-jours-v4-bis "mois invalide"))))
- Lorsque m n'est pas valide, la fonction nb-jours-v5 rend
-
une erreur (détectée par les relations de comparaison) lorsque m
n'est pas un nombre
- une erreur (détectée par les prédicats odd? et even?)
lorsque m n'est pas un entier
- la valeur 30 lorsque m est un entier impair supérieur à 7 (ou un
nombre négatif "impair")
- la valeur 31 lorsque m est un entier pair supérieur à 7 (ou un
nombre négatif "pair").
Pour traiter les deux derniers cas, il faut rajouter un test vérifiant
que m est bien dans l'intervalle [1 ... 12]. Il est plus simple de
placer ce test au début de la fonction, pour éviter de faire des tests
inutiles. La fonction nb-jours-v5-bis présente une solution
avec une forme conditionnelle.
(define (nb-jours-v5-bis m)
(cond ((or (< m 1) (> m 12))
(erreur 'nb-jours-v5-bis "mois invalide"))
((= m 2) 29)
((or (and (> m 7) (even? m))
(and (<= m 7) (odd? m)))
31)
(else 30)))
- On laisse au lecteur le soin de modifier nb-jours-v6,
nb-jours-v7 et nb-jours-v8.