Vidéo 41 : Fonction comme boîte noire

\(\newcommand{\ds}{\displaystyle}\) \(\newcommand{\Frac}{\ds\frac}\) \(\renewcommand{\r}{\mathbb{ R}}\) \(\newcommand{\C}{\mathbb{ C}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\z}{\mathbb{ Z}}\) \(\newcommand{\Q}{\mathbb{ Q}}\) \(\newcommand{\N}{\mathbb{ N}}\) \(\newcommand{\n}{\mathbb{ N}}\) \(\newcommand{\ol}{\overline}\) \(\newcommand{\abs}[1]{\left| \,{#1} \right|}\) \(\newcommand{\pv}{\;;\;}\) \(\newcommand{\ens}[1]{\left\{ {#1} \right\}}\) \(\newcommand{\mens}[1]{\setminus\left\{ {#1} \right\}}\) \(\newcommand{\Par}[1]{\left({#1}\right)}\)

Vidéo 41 : Fonction comme boîte noire

Formule de Keith et Craver

La formule de Keith et Craver permet de déterminer le jour de la semaine (ie lundi, mardi, etc) correspondant à une date donnée (par exemple, le 14 juillet 1789 qui était un mardi).

Ci-dessous, en voici une implémentation sous forme de fonction Python. La fonction kc retourne sous forme d’entier (\(\texttt{1 = lundi, 2 = mardi, ..., 7 = dimanche}\)) le jour de la semaine correspondant à une date passée en paramètre comme suit : j (jour), m (mois) et a (année).

1
2
3
def kc(j, m, a):
    z = a - (m<3)
    return (j + 23*m//9 + 3 -2*(m>=3) + a + z//4 - z//100 + z//400)%7 +1

Voici un exemple d’utilisation :

1
2
3
4
5
6
def kc(j, m, a):
    z = a - (m<3)
    return (j + 23*m//9 + 3 -2*(m>=3) + a + z//4 - z//100 + z//400)%7 +1

# Test du jour de la semaine du 14 juillet 2018
print(kc(14, 7, 2018))
7
6
  • Lignes 6 et 7 : le 14 juillet 2018 est un samedi (6e jour de la semaine).

Cette fonction doit être utilisée telle quelle, sans chercher à comprendre comment elle fonctionne. Vous devez réécrire (copier-coller) une fois et une seule cette fonction pour pouvoir l’utiliser par la suite mais il est inapproprié de copier/coller le corps de la fonction lignes 2 et 3 dans votre propre code. Il est seulement attendu d”utiliser la fonction kc.

  1. Vérifier la validité des dates suivantes en calculant leur code entre 1 et 7 :

    • dimanche 13 janvier 2019
    • mardi 14 juillet 1789
    • dimanche 10 mai 1981
    • jeudi 16 juillet 1998
    • mardi 19 janvier 2038 (le bug de l’an 2038)
  2. En utilisant un appel à la fonction kc, écrire une fonction est_vendredi13(m,a) qui renvoie True si le 13 du mois m et de l’année a est un vendredi (et False sinon). Combien y-a-t-il de vendredis 13 dans l’année 2018 ?

  3. Ecrire et tester une fonction jour_semaine qui accepte en argument une liste date, supposée de la forme [jour,  mois,  année] et qui retourne le jour de la semaine correspondant en toutes lettres (ex : lundi, mardi, .., dimanche).

    Par exemple, jour_semaine([14, 7, 1789]) vaut la chaîne mardi.

    On pourra utiliser une liste JOURS_SEMAINE formée des noms des jours de la semaine.

Solution

  1. Il suffit d’appeler la fonction kc avec le jour du mois, le numéro de mois et l’année et de vérifier par lecture directe que le jour de la semaine correspond. Pour la première date, cela donne :
1
2
3
4
5
def kc(j, m, a):
    z = a - (m<3)
    return (j + 23*m//9 + 3 -2*(m>=3) + a + z//4 - z//100 + z//400)%7 +1

print(kc(13,1, 2017), "->", "vendredi ?")
6
5 -> vendredi ?

ce qui correspond au résultat annoncé.

Pour les autres dates, il est plus simple d’utiliser une boucle qui parcourt la liste des dates :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def kc(j, m, a):
    z = a - (m<3)
    return (j + 23*m//9 + 3 -2*(m>=3) + a + z//4 - z//100 + z//400)%7 +1

dates=[
[13,1, 2017, 5],
[14, 7, 1789, 2],
[10,5, 1981, 7],
[16,7, 1998, 4],
[1,5, 2018, 2],
[19,1, 2038, 2]
]

for i in range(len(dates)):
    d=dates[i]
    jourSemaine = kc(d[0], d[1], d[2])
    print(jourSemaine == d[3])
18
19
20
21
22
23
True
True
True
True
True
True
  • Lignes 5-11 : on place les 6 dates à tester dans une liste dates. Chaque date est codée par une liste (j, m, a, js)js est le code du jour de la semaine qu’il faut vérifier.
  • Lignes 13-16 : On parcourt la liste dates. Chaque date d est testée. Pour cela on extrait avec des indices le jour, le mois et l’année et on demande à la fonction kc de calculer le jour de la semaine (ligne 15). On compare ensuite la réponse donnée par kc avec le jour donné dans l’énoncé.
  • Lignes 18-22 : tous les tests sont positifs.
  1. Pour tester si le mois m de l’année a comporte un vendredi 13, on demande à la fonction kc de nous calculer le jour de la semaine du 13 du mois m de l’année a. Si le réponse est 5, c’est que c’est un vendredi. D’où le code suivant :
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def kc(j, m, a):
    z = a - (m<3)
    return (j + 23*m//9 + 3 -2*(m>=3) + a + z//4 - z//100 + z//400)%7 +1

def estVendredi13(m, a):
    jourSemaine=kc(13, m, a)
    if jourSemaine == 5:
        return True
    else:
        return False

estVendredi13(1, 2017)
13
True

Il est largement possible (et préférable) de simplifier le code :

1
2
3
4
def estVendredi13(m, a):
    return kc(13, m, a) == 5

estVendredi13(1, 2017)

Pour déterminer le nombre de vendredis 13 de l’an 2017, il suffit de tester si le 13 janvier 2017 est un vendredi, si le 13 février 2017 est un vendredi et ainsi de suite jusqu’à décembre. Il suffit donc de tester tous les mois m de 1 à 12 avec un boucle for. D’où le code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def kc(j, m, a):
    z = a - (m<3)
    return (j + 23*m//9 + 3 -2*(m>=3) + a + z//4 - z//100 + z//400)%7 +1

def estVendredi13(m, a):
    if kc(13, m, a) == 5:
        return True
    else:
        return False

nbVendredis13=0

for m in range(1, 13):
    if estVendredi13(m, 2017):
        nbVendredis13 = nbVendredis13 + 1

print(nbVendredis13)
18
2
  • Ligne 11 : le compteur de vendredis 13.
  • Lignes 14-15 : si le 13 du mois m de 2017 est un vendredi, on incrémente le compteur.
  • Ligne 18 : il y a 2 vendredis 13 en 2017.
  1. Il suffit d’appeler la fonction kc et, en fonction du code qu’elle retourne (1, 2, 3, etc), la fonction jour_semaine renvoie le jour de la semaine adéquat, en toute lettres, sous forme de chaîne de caractères. D’où le code suivant :
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def kc(j, m, a):
    z = a - (m<3)
    return (j + 23*m//9 + 3 -2*(m>=3) + a + z//4 - z//100 + z//400)%7 +1

def jour_semaine(date):
    j=date[0]
    m=date[1]
    a=date[2]
    code=kc(j,m,a)
    if code==1:
        return "lundi"
    if code==2:
        return "mardi"
    if code==3:
        return "mercredi"
    if code==4:
        return "jeudi"
    if code==5:
        return "vendredi"
    if code==6:
        return "samedi"
    if code==7:
        return "dimanche"


for date in [[13,1,2017],[14,7,1789], [10,5,1981], [16,7,1998],
            [1,5,2017], [19,1,2038]]:
    print(date, "->", jour_semaine(date))
29
30
31
32
33
34
[13, 1, 2017] -> vendredi
[14, 7, 1789] -> mardi
[10, 5, 1981] -> dimanche
[16, 7, 1998] -> jeudi
[1, 5, 2017] -> lundi
[19, 1, 2038] -> mardi
  • Lignes 12-23 : la suite de if est légitime dans la mesure où chaque corps d’instruction if se termine par une instruction return et par suite, il n’y a aucun test inutile (la fonction s’interrompt dès qu’elle a trouvé la bonne chaîne).

Alternative

Il existe cependant une façon plus idiomatique de procéder. En effet, les codes de jours sont consécutifs de 1 à 7, donc si on dispose d’une liste L telle qu’à l’indice i figure la chaîne de caractères représentant le \(\mathtt{i^\text{e}}\) jour de la semaine, il suffit d’appeler L avec l’indice i. Comme le code 0 ne correspond à aucun jour de la semaine, on mettra n’importe quoi à l’indice 0 de la liste L. D’où la liste suivante qu’on va appeler plutôt JOURS_SEMAINES au lieu de L :

1
2
JOURS_SEMAINES = ["toto","lundi", "mardi", "mercredi",
                    "jeudi", "vendredi", "samedi", "dimanche"]

Ainsi, par exemple, pour obtenir le 3e jour de la semaine :

1
2
3
4
JOURS_SEMAINES = ["toto","lundi", "mardi", "mercredi",
                    "jeudi", "vendredi", "samedi", "dimanche"]

print(JOURS_SEMAINES[3])
5
mercredi

On en déduit le code plus simple suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def kc(j, m, a):
    z = a - (m<3)
    return (j + 23*m//9 + 3 -2*(m>=3) + a + z//4 - z//100 + z//400)%7 +1

def jour_semaine(date):
    j=date[0]
    m=date[1]
    a=date[2]
    code=kc(j,m,a)
    return JOURS_SEMAINES[code]

JOURS_SEMAINES = [  "toto", "lundi", "mardi", "mercredi",
                    "jeudi", "vendredi", "samedi", "dimanche"]


for date in [[13,1,2017],[14,7,1789], [10,5,1981], [16,7,1998],
            [1,5,2017], [19,1,2038]]:
    print(date, "->", jour_semaine(date))
19
20
21
22
23
24
[13, 1, 2017] -> vendredi
[14, 7, 1789] -> mardi
[10, 5, 1981] -> dimanche
[16, 7, 1998] -> jeudi
[1, 5, 2017] -> lundi
[19, 1, 2038] -> mardi