Étude de cas: nombre de jours d'un mois

 

 
Avertissement 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.
 
Le problème à résoudre 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:
  1. Cas 1: en faisant l'hypothèse que m est un nombre valide  
    ;;; nb-jours : nat/entre 1 et 12/ -> nat  
    ;;; (nb-jours m) retourne le nombre de jours du mois «m» dans une année bissextile 
    ou de façon équivalente  
    ;;; nb-jours : nat -> nat  
    ;;; HYPOTHESE : «m» est un entier entre 1 et 12 
    ;;; (nb-jours m) retourne le nombre de jours du mois «m» dans une année bissextile 

    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.
     

  2. Cas 2: renvoyer une erreur lorsque m n'est pas un nombre valide  
    ;;; nb-jours : Valeur -> nat  
    ;;; ERREUR lorsque «m» n'est pas un entier entre 1 et 12 
    ;;; (nb-jours m) retourne le nombre de jours du mois «m» dans une année bissextile 

    Dans ce cas quels sont les tests nécessaires pour signaler des erreurs (d'autres erreurs étant directement signalées par Scheme)


 
Étude du cas 1 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.
  1. 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.  
    ;;; nb-jours : nat -> nat  
    ;;; HYPOTHESE : «m» est un entier entre 1 et 12 
    ;;; (nb-jours m) retourne le nombre de jours du mois «m» dans une année bissextile 
    (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).
     
  2. 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)))

     
  3. 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)))

     
  4. 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)))

     
  5. 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)))

     
  6. 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)))))

     
  7. 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))))))

     
  8. 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) ) )

 
Étude du cas 2 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.
  1. 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"))))))))))))))

     
  2. 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"))))

     
  3. 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")))))

     
  4. 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"))))

     
  5. 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)))

     
  6. On laisse au lecteur le soin de modifier nb-jours-v6, nb-jours-v7 et nb-jours-v8.