Les deux premiers exercices portent sur les règles de validation des listes de signatures pour les candidats à la présidence de la République Française.
Exercice 1 : Élection présidentielle, Json (partie 1).
On dispose d'un fichier sponsors.json au format json dans le répertoire courant. Ce fichier contient la liste des signatures déposées au conseil constitutionnel, classées par candidat supporté. On l'a récupéré sur le site du conseil constitutionnel :
https://presidentielle2017.conseil-constitutionnel.fr/les-parrainages/tous-les-parrainages/
Chaque signataire est décrit dans une table d'association json par ses nom, prénom, circonscription électorale (ville, département,...), mandat ( "Maire" , "Député-e" ,...), civilité ( "M" , "Mme" , ...), département, et la date de publication de sa signature par le conseil constitutionnel, au format "jj/mm/aaaa" . Tous ces champs sont des chaînes de caractères et sont toujours définis. Le premier signataire du fichier est par exemple représenté par le fragment json :
{ "Circonscription":"",
"Civilité":"Mme",
"Date de publication":"01/03/2017",
"Département":"Manche",
"Mandat":"Conseiller/ère départemental-e",
"Nom":"LARSONNEUR-MOREL",
"Prénom":"Dominique"
}
Le fichier json complet est une liste json de candidats, chaque candidat est une table d'association avec deux champs :
"Candidat-e parrainé-e" contient une chaîne de caractère, le nom du candidat.
"Parrainages" contient une liste json de signataires (au format vu ci-dessus).
On se propose de lire le fichier et de l'organiser sous une forme exploitable en OCaml. On ne se préoccupera pas de gérer les erreurs si le fichier est mal-formé.
Proposer un type pour les dates.
On veut écrire une fonction de conversion d'une chaîne de caractères en date. Dans un premier temps, donner une fonction convertissant une date en chaîne de caractères en utilisant Print.sprintf. Pour écrire un entier en 2 chiffres, on utilise le motif "%02d" .
Écrire la fonction date_of_string. Pour écrire avec printf il faut donner le format et les valeurs à attribuer à chaque motif du format. Pour lire avec Scanf.sscanf, il faut donner :
Utiliser le module Json = Yojson.Basic.Util pour convertir un fragment json correspondant à un signataire en sponsor:
type sponsor =
{ circonscription : string;
civility : string;
publication_date : date;
department : string;
mandate : string;
last_name : string;
first_name : string
}
On représente chaque liste de parrainages avec le type :
type sponsorship =
{ candidate : string;
sponsors : sponsor list
}
Écrire une fonction convertissant un fragment json correspondant à un candidat en une valeur du type sponsorship.
Enfin, écrire une fonction chargeant un fichier dont le nom est donné en argument :
val load_sponsorships : string -> sponsorship list
Exercice 2 : Élection présidentielle, validation (partie 2).
Cet exercice réutilise les types sponsor et sponsorship de l'exercice 1 . Il n'est cependant pas nécessaire d'avoir répondu aux questions de l'exercice 1 pour répondre à celui-ci.
Pour être validée par le conseil constitutionnel, une candidature doit être supportée par au moins 500 signatures, telles que :
pas plus de 10 % viennent du même département (ou de la même collectivité d'Outre-Mer),
au moins 30 départements (ou collectivités d'Outre-Mer) sont représentées.
Le but de l'exercice est de trouver les candidatures valides parmi la liste de toutes les candidatures :
val extract_valid_sponsorship : sponsorship list -> sponsorship list
Comment définir un module StringMap proposant des dictionnaires dont les clés sont des chaînes de caractères ?
Il nous faut, pour chaque candidature, classer les signatures par département. Pour cela, nous allons transformer la liste des signatures en un dictionnaire StringMap.t, associant à chaque département les signataires pour ce département. Commencer par écrire une fonction permettant d'ajouter une signature dans le dictionnaire :
val add_sponsor : sponsor list StringMap.t -> sponsor -> sponsor list StringMap.t
Ajouter une fonction transformant une liste de signature en dictionnaire :
val convert_sponsors_into_map : sponsor list -> sponsor list map
Écrire un prédicat pour tester si les signatures proviennent d'au moins 30 départements différents.
val has_30_departments : sponsor list StringMap.t -> bool
Écrire une fonction pour compter le nombre de signatures d'un candidat, en n'en comptant pas plus de 50 par département (pour respecter la contrainte d'au plus 10 % par département) :
val find_number_of_sponsors : sponsor list StringMap.t -> int
Écrire la fonction extract_valid_sponsorship.
(bonus) Écrire une fonction vérifiant qu'aucun signataire n'a signé deux fois.
Exercice 3 : Lentilles.
On représente les États et leurs chefs d'État à l'aide des types suivants :
type genre = Male | Female | Robot
type id =
{ name : string;
genre : genre
}
type residence = string
type head_of_state =
{ id : id;
residence : residence;
}
type country_name = string
type state =
{ country : country_name;
head_of_state : head_of_state;
}
Par exemple :
let france =
let f_hollande = { name = "François Hollande"; genre = Male } in
let french_president = { id = f_hollande; residence = "Palais de l'Élysée" } in
{ country = "France";
head_of_state = french_president
}
let usa =
let d_trump = { name = "Donald John Trump"; genre = Male } in
let us_president = { id = d_trump; residence = "The White House" } in
{ country = "United States of America";
head_of_state = us_president
}
1 . Un coup d'état
Le complexe militaro-industriel américain décide de remplacer les chefs d'État par des robots indistingables des originaux. Écrire une fonction qui à un État associe un autre État similaire mais tel que le genre de son chef d'État soit Robot.
val coup : state -> state = <fun>
# let usa_after_coup = coup usa;;
val usa_after_coup : state =
{ country = "United States of America";
head_of_state =
{ id = {name = "Donald John Trump"; genre = Robot};
residence = "The White House"
}
}
2 . Des lentilles pour simplifier les manipulations.
La fonction coup est très verbeuse. Les lentilles sont une technique pour manipuler et modifier des champs profondément imbriqués dans une structure de données. Voici un petit module proposant des lentilles :
module Lens :
sig
type ('item, 'content) lens
val create :
setter:('item -> 'content -> 'item) ->
getter:('item -> 'content) ->
('item, 'content) lens
val compose :
('subitem, 'content) lens ->
('item, 'subitem) lens -> ('item, 'content) lens
val ( >> ) :
('item, 'subitem) lens ->
('subitem, 'content) lens -> ('item, 'content) lens
val view : ('item, 'content) lens -> 'item -> 'content
val set : ('item, 'content) lens -> 'item -> 'content -> 'item
val over :
('item, 'content) lens -> 'item -> f:('content -> 'content) -> 'item
end
Par exemple, voici une lentille pour pour accéder au chef d'État dans un État :
let head_of_state_lens =
Lens.create
~setter:(fun state new_head -> { state with head_of_state = new_head })
~getter:(fun state -> state.head_of_state)
Écrire de la même façon :
une lentille pour accéder à l'identité à partir d'un chef d'État,
une autre pour accéder au nom à partir d'une identité,
une autre pour accéder au genre à partir d'une identité.
3 . Composer des lentilles.
Utiliser une fonction de composition de Lens pour créer une lentille permettant d'accéder au genre d'un chef d'État à partir de l'État.
val genre_from_state_lens : (state,genre) lens
4 . Utiliser des lentilles.
On peut convertir une lentille en accesseur ou mutateur avec les fonctions view, set et over de Lens.
Écrire une fonction permettant, étant donné un État, de modifier le genre de son chef, transformant les hommes en robots, les robots en femme et les femmes en hommes.
5 . Élection présidentielle.
Proposer une expression utilisant les lentilles pour remplacer le chef d'État de la France par la personnalité de votre choix.
6 . Type d'une lentille.
À votre avis, comment est représentée une lentille ? Quelle peut être la définition du type lens ? Comment écrire la fonction compose ?
Éléments de langage
Fonctions utiles du module List :
val length : 'elt list -> int
val rev : 'elt list -> 'elt list
val concat : 'elt list list -> 'elt list
val map : ('elt -> 'im) -> 'elt list -> 'im list
val fold_left : ('state -> 'elt -> 'state) -> 'state -> 'elt list -> 'state
val for_all : ('elt -> bool) -> 'elt list -> bool
val exists : ('elt -> bool) -> 'elt list -> bool
val mem : 'elt -> 'elt list -> bool
val filter : ('elt -> bool) -> 'elt list -> 'elt list
val fast_sort : ('elt -> 'elt -> int) -> 'elt list -> 'elt list
Fonctions utiles de Map.S :
type key
type 'assoc t
val empty : 'assoc t
val add : key -> 'assoc -> 'assoc t -> 'assoc t
val mem : key -> 'assoc t -> bool
val find : key -> 'assoc t -> 'assoc
val map : ('assoc -> 'im) -> 'assoc t -> 'im t
val fold : ('key -> 'assoc -> 'state -> 'state) -> 'assoc t -> 'state -> state
val cardinal : 'assoc t -> int
Fonctions utiles de Json = Yojson.Basic :
val from_file : string -> json
module Util : sig
val member : string -> json -> json
val to_list : json -> json list
val to_string : json -> string
end
Exemples de syntaxe :
type ratio = int * int
let rec euclid a b =
if b = 0 then a
else euclid b (a mod b)
let pgcd a b = euclid (abs a) (abs b)
let ratio p q =
let d = pgcd p q in
if q < 0 then (-p / d, q / d)
else (p/d, q/d)
let (+/) (a,b) (c,d) = ratio (a*d + b*c) (b*d)
let (++) (a,b) (c,d) = ratio (a+c) (b+d)
module Queue =
struct
type 'elt t =
Queue of ('elt list) * ('elt list)
exception Empty
let empty = Queue ([],[])
let is_empty = function
| Queue ([],[]) -> true
| _ -> false
let queue = function
| Queue ([],l) -> Queue(List.rev l,[])
| x -> x
let snoc (Queue (l1,l2)) ele =
queue (Queue (l1,ele::l2))
let head = function
| Queue ([],_) -> raise Empty
| Queue (l,_) -> List.hd l
let tail = function
| Queue ([],_) -> raise Empty
| Queue (l1,l2) -> queue (Queue (List.tl l1,l2))
end