Consignes

Si certains exercices n'ont pas pu être terminés dans le cadre de cette séance, il est fortement conseillé de les terminer par vous-même chez vous ou en salle libre service.

Liens utiles

Objectifs du TP

Le but de ce TP est de commencer l'apprentissage d'une démarche d'analyse descendante d'un problème donné.

1. Dates

Le morceau de code suivant, à recopier tel quel dans votre fichier permet de gérer des dates "naïves" constituées d'un jours, d'un mois et d'une année.

Note : cette représentation des dates est volontairement naïve. Elle a pour but de vous obliger à utilisez les fonctions de création et d'accès aux dates...

import pickle

def nouvelle_date(jour, mois, annee):
    """créé une nouvelle date à partir de trois nombres entiers : un numéro de jours,
    un numéro mois et un numéro d'année.
    """
    return pickle.dumps((jour, mois, annee))

def no_jour(date):
    """renvoie le numéro du jours correspondant à la date donnée en argument
    """
    return pickle.loads(date)[0]

def no_mois(date):
    """renvoie le numéro du mois correspondant à la date donnée en argument
    """
    return pickle.loads(date)[1]

def no_annee(date):
    """renvoie le numéro de l'année correspondant à la date donnée en argument
    """
    return pickle.loads(date)[2]

La fonction nouvelle_date est la seule fonction qui permet de créer des dates.

Les fonction no_jour, no_mois et no_annee sont les seules fonctions qui permettent de récupérer l'information contenue dans une date.

Vous ne devez à aucun moment utiliser d'autres méthodes pour manipuler les dates elles même. En particuliers, les dates ne sont ni des nombres, ni des tableaux, ni des chaines de caractères.

Exemple :

>>> aujourdhui = nouvelle_date(24, 10, 2016)
>>> aujourdhui
b'\x80\x03K\x18K\nM\xe0\x07\x87q\x00.'

>>> no_jour(aujourdhui)
24
>>> no_mois(aujourdhui)
10
>>> no_annee(aujourdhui)
2016


>>> jamais = nouvelle_date(35, 36, 37)
>>> jamais
b'\x80\x03K#K$K%\x87q\x00.'

>>> no_jour(jamais)
35
>>> no_mois(jamais)
36
>>> no_annee(jamais)
37

Écrivez une procédure affiche_date qui permet d'afficher (avec la procédure print) une date donnée en argument.

Par exemple :

>>> affiche_date(aujourdhui)
24-10-2016

>>> affiche-date(jamais)
35-36-37

Remarque : une procédure est une fonction qui ne contient pas l'instruction return.

2. Décalage de n jours dans le temps

L'objectif de cette partie est d'écrire une fonction decale_date qui prend en paramètres :

et renvoie la date correspondant à une avancée de n jours dans le temps à partir de la date donnée. Par exemple :

Exemple :

>>> affiche_date(decale_date(aujourdhui, 3))
27-10-2016
>>> affiche_date(decale_date(aujourdhui, 10))
3-11-2016
>>> affiche_date(decale_date(nouvelle_date(31, 12, 2016), 1))
1-1-2017

Pour écrire cette fonction, nous allons commencer par écrire une fonction lendemain qui prend une date en paramètre et renvoie la date du lendemain.

Calcul de la date du lendemain - 1ère version

Nous allons écrire une première version de la fonction lendemain qui prend une date en paramètre et renvoie la date du lendemain. Dans le cas général, il suffit d'ajouter 1 au n° du jour pour obtenir la date du lendemain. Ainsi par exemple :

>>> affiche_date(lendemain(aujourdhui))
25-10-2016

Cette règle est correcte pour presque tous les jours de l'année sauf ceux qui correspondent à des fins de mois et fin d'année. Elle est de fait correcte pour 353 jours sur 365.

On aura ainsi :

>>> affiche_date(lendemain(nouvelle_date(31, 11, 2016)))
32-11-2016
>>> affiche_date(lendemain(nouvelle_date(31, 12, 2016)))
32-12-2016

Écrivez la fonction lendemain telle que définie ci-dessus, qui renverra un résultat correct pour tous les cas sauf les fins de mois et d'année.

Attention : votre fonction lendemain doit avoir un argument de type date et doit renvoyer un résultat de type date également. Testez votre fonction.

Décalage de dans le temps

Nous avions laissé de côté le test de la fonction decale_date car il fallait au préalable définir lendemain. C'est maintenant chose faite, même si la fonction lendemain est encore imparfaite. Il est de fait possible d'écrire une première version de decale_date.

Écrivez la fonction decale_date et testez la en prenant des exemples de dates pertinents.

Attention : votre fonction decale_date doit uniquement utiliser la fonction lendemain et ne doit pas utiliser les fonctions nouvelle_date, no_jour, no_mois ou no_annee.

Vous n'aurez normalement pas à modifier votre fonction decale_date dans la suite du TP.

Calcul de la date du lendemain - 2ème version : fin d'année

Dans sa première version, la fonction lendemain a été définie sans tenir compte des fins de mois et d'année. Nous allons maintenant en écrire une seconde version qui traitera le cas de la fin d'année. Pour cela, nous allons introduire, ici aussi, une fonction qui va nous "faciliter la vie".

Écrivez une fonction fin_annee qui prend une date en paramètre et teste si cette date correspond à la fin de l'année (c'est-à-dire au 31 décembre).

Modifiez la fonction lendemain en utilisant fin_annee et testez cette nouvelle version.

Testez à nouveau la fonction decalage_date, sans la modifier, pour constater les différences.

Calcul de la date du lendemain - 3ème version : fin de mois

Dans sa version actuelle, la fonction lendemain ne traite pas le cas des fins de mois qui ne sont pas fin d'année. Nous allons maintenant en écrire une troisième et dernière version, qui sera correcte pour toutes les dates. Pour cela, nous allons introduire, encore une fois, une fonction qui va nous "faciliter la vie". Il s'agit de la fonction fin_de_mois qui prend une date en paramètre et teste si cette date correspond à une fin de mois.

Exemple :

>>>fin_de_mois(aujourdhui)
False
>>> fin_de_mois(nouvelle_date(31, 12, 2016))
True
>>> fin_de_mois(nouvelle_date(31, 10, 2016))
True
>>> fin_de_mois(nouvelle_date(28, 2, 2012))
False
>>> fin_de_mois(nouvelle_date(29, 2, 2012))
True

Calcul du nombre de jours d'un mois donné

Pour écrire la fonction fin_de_mois, nous aurons besoin d'une fonction supplémentaire: nb_jours_ds_mois Cette fonction prend en paramètres :

et renvoie le nombre de jours dans un mois donné, étant donné une année donnée. Le paramètre annee est nécessaire du fait des années bissextiles : février n'a en effet pas le même nombre de jours selon que l'année est bissextile ou pas.

Exemple :

>>> nb_jours_ds_mois(5, 2016)
31
>>> nb_jours_ds_mois(2, 2016)
29
>>> nb_jours_ds_mois(2, 2016)
28

Écrivez la fonction est_bissextile qui teste si une année est bissextile. Pour rappel, une année A est bissextile si A est multiple de 4, mais pas de 100, ou multiple de 400.

Exemple :

>>> est_bissextile(2014)
False
>>> est_bissextile(2012)
True
>>> est_bissextile(2000)
True
>>> est_bissextile(1900)
False

Écrivez la fonction nb_jours_ds_mois en utilisant la fonction est_bissextile et testez la.

Modifiez la fonction lendemain en prenant les fins de mois en compte, et testez la sur des exemples de dates pertinents.

Testez la fonction decalage_date en prenant des exemples de dates pertinents.

3. Calendrier perpétuel

Un calendrier perpétuel (voir Wikipedia) est un calendrier qui donne le jour de la semaine (lundi, mardi, mercredi, ...) correspondant à une date donnée. Nous allons, dans cette partie, écrire une fonction calendrier_perpetuel prenant une date en paramètre et renvoyant une chaîne de caractères correspondant au jour de la semaine de cette date. Nous ne traiterons que les dates appartenant au calendrier grégorien, c'est-à-dire à partir du 20 décembre 1582 en France.

Exemple :

>>> calendrier_perpetuel(nouvelle_date(20, 10, 2014))
"lundi"
>>> calendrier_perpetuel(nouvelle_date(8, 10, 2003))
"mercredi"
>>> calendrier_perpetuel(nouvelle_date(1, 6, 2014))
"dimanche"
>>> calendrier_perpetuel(nouvelle_date(7, 1, 1994))
"vendredi"

C'est M. Moret qui a proposé ce calendrier. Il est composé, dans sa version initiale, de 3 tableaux dans lesquels il faut aller chercher des chiffres en fonction de la date en entrée et faire ensuite des calculs sur ces chiffres. Une version simplifiée a été proposée. Elle consiste à attribuer :

et à faire leur somme. Tous ces nombres sont définis modulo 7 (ex : 5 est équivalent à 12, 19, 26, ...). Le résultat de l'addition, modulo 7 lui aussi, est un nombre compris entre 0 et 6, qui donne le jour de la semaine selon la correspondance suivante :

Nous allons définir, dans ce qui suit, des fonctions permettant de calculer les nombres séculaire, annuel, mensuel et le quantième d'une date donnée. Nous les utiliserons pour définir la fonction calendrier_perpetuel.

Nombre séculaire

Le « nombre séculaire » est le même pour toutes les années commençant par les deux mêmes chiffres. Par exemple, toutes les années du 21ème siècle (années 2001 à 2099) auront un nombre séculaire égal à 0. C'est également le cas de l'année 2000.

Le tableau suivant donne les nombres séculaires pour chaque siècle :

Il est à noter que ce nombre diminue de deux unités chaque siècle (modulo 7) sauf lorsque les deux premiers chiffres sont un multiple de 4 (1600 à 1699, 2000 à 2099).

Écrivez une fonction nombre_seculaire qui prend une année en paramètre et renvoie le nombre séculaire associé à cette année.

Exemple :

>>> nombre_seculaire(2014)
0
>>> nombre_seculaire(1996)
1

Vous pourrez introduire des fonctions intermédiaires pour écrire cette fonction, par exemple la fonction siecle qui prend en paramètre une année A et renvoie le nombre correspondant au siècle de A.

Exemple :

>>> siecle(2014)
20
>>> siecle(1996)
19
>>> siecle(1900)
19

Nombre annuel

La ligne ci-dessous mentionne les années pour lesquelles le nombre annuel est égal à 0. À partir de ces années, le nombre annuel augmente d'une unité chaque année et de deux si l'année est bissextile.

Années dont le nombre annuel est 0 :

..04 ..10 ..21 ..27 ..32 ..38 ..49 ..55 ..60 ..66 ..77 ..83 ..88 ..94

Exemple :

On peut aussi remarquer que le résultat est donné par la formule suivante :

Exemple pour l'année 2010 :

Exemple pour l'année 2016 :

Écrivez une fonction nombre_annuel qui prend une année en paramètre et renvoie le nombre annuel associé à cette année.

Exemple :

>>> nombre_annuel(2010)
0
>>> nombre_annuel(2016)
1

Vous pourrez introduire des fonctions intermédiaires pour écrire cette fonction, par exemple la fonction an qui prend en paramètre une année A et renvoie le nombre correspondant aux 2 derniers chiffres de A.

Exemple :

>>> an(2014)
14
>>> an(1996)
96
>>> an(1900)
0

Nombre mensuel

Le tableau suivant donne le nombre mensuel pour chaque mois de l'année (cf. Nombre mensuel sur Wikipedia):

mois nombre mensuel
février (année non bissextile), mars, novembre 0
juin 1
septembre, décembre 2
janvier (année bissextile), avril, juillet 3
janvier (année non bissextile), octobre 4
mai 5
février (année bissextile), aout 6

Exemple : le mois de janvier a un nombre mensuel de 4 en 1995 et de 3 en 1996 (année bissextile).

Ecrire une fonction nombre_mensuel qui prend en paramètres :

et qui renvoie le nombre mensuel associé à ces données.

Exemple :

>>> nombre_mensuel(1, 1995)
4
>>> nombre_mensuel(1, 1996)
3

Quantième

Le quantième correspond au jour d'une date donnée. Ainsi par exemple :

Écrivez une fonction quantieme qui prend une date en paramètre et renvoie le quantième de la date donnée.

Ecriture de la fonction "calendrier_perpetuel"

Écrivez la fonction calendrier_perpetuel qui prend une date en paramètre et renvoie une chaîne de caractères correspondant au jour de la semaine de la date donnée. Vous utiliserez pour cela les fonctions nombre_seculaire, nombre_annuel, nombre_mensuel et quantieme que vous venez d'écrire.

Il vous faut revenir au début de cette partie pour relire les explications sur le calendrier perpétuel et la manière de calculer le jour de la semaine en utilisant les 4 fonctions définies ci-dessus, ainsi que le tableau de correspondance entre un numéro et un jour de la semaine.

Vendredi 13...

Les vendredis 13 ont toujours hanté l'imaginaire collectif. Pour certains, c'est une date maudite et il convient de rester confiné à la maison ce jour-là si l'on ne veut pas prendre le ciel sur la tête. Pour d'autres, c'est une date porte-bonheur.

Écrivez la fonction vendredi_13 qui prend en paramètres :

et calcule le nombre de vendredis 13 qu'il y a entre le 1er janvier de l'année initiale et le 1er janvier de l'année finale. Vous introduirez au préalable une fonction nb_jours permettant de compter le nombre de jours entre ces 2 dates (vous en aurez besoin pour écrire vendredi_13 Pour rappel, une année comporte 365 jours si elle n'est pas bissextile, 366 sinon.

Exemple :

>>> vendredi_13(2014, 2015)
1
>>> vendredi_13(2010, 2014)
7
>>> vendredi_13(2009, 2010)
3

Utilisez cette fonction pour compter le nombre de vendredis 13 entre 1600 et 2000. Que remarquez-vous ?