Exercice 1 : Élection présidentielle, Json (partie 1).
Une date représente simultanément 3 informations, on utilise donc un type produit.
type date =
{ year : int;
month : int;
day : int
}
Bien sûr, pour l'année il faut 4 chiffres, on adapte donc le motif.
let string_of_date { day; month; year } =
Printf.sprintf "%02d/%02d/%04d" day month year
On donne donc une fonction en dernier argument, utilisant les valeurs lues. On pourrait la donner parenthésée, ou bien la nommer avant, ou comme ici utiliser le @@ avec une fonction anonyme.
let date_of_string text =
Scanf.sscanf text "%d/%d/%d" @@ fun day month year ->
{ day; month; year }
On récupère les différents champs avec member et on n'oublie pas des les traduire dans le type adéquat.
let sponsor_of_json json =
let open Yojson.Basic.Util in
{ circonscription = json |> member "Circonscription" |> to_string;
civility = json |> member "Civilité" |> to_string;
publication =
json |> member "Date de publication" |> to_string |> date_of_string;
department = json |> member "Département" |> to_string;
mandate = json |> member "Mandat" |> to_string;
last_name = json |> member "Nom" |> to_string;
first_name = json |> member "Prénom" |> to_string
}
Attention à bien utiliser List.map pour convertir les éléments de la liste des parrains.
let sponsorship_of_json json =
let open Yojson.Basic.Util in
{ candidate =
json |> member "Candidat-e parrainé-e" |> to_string;
sponsors =
json |> member "Parrainages" |> to_list |> List.map sponsor_of_json;
}
Yojson propose une fonction pour lire un fichier contenant du json. On utilise ensuite les fonctions que nous venons d'écrire.
let load_sponsorships file_name =
Yojson.Basic.from_file file_name
|> to_list
|> List.map sponsorship_of_json
Exercice 2 : Élection présidentielle, validation (partie 2).
On construit des dictionnaires en appliquant le foncteur Map.Make à un type ordonnés des clés.
module StringMap = Map.Make(String)
On récupère la liste des sponsors déjà enregistrés, par défaut la liste vide. Puis on remet cette liste avec le nouveau sponsor en plus.
let add_sponsor departments sponsor =
let previous_sponsors =
StringMap.find_opt sponsor.department sponsor
|> Option.value ~default:[]
in
StringMap.add
sponsor.department
(sponsor :: previous_sponsors)
departments
Il faut ajouter tous les sponsors de la liste dans un même dictionnaire initialement vide. On doit donc consommer tous les éléments d'une liste, donc utiliser List.fold_left.
let convert_sponsors_into_map sponsors =
List.fold_left add_sponsor StringMap.empty sponsors
Pas de problème, on a une fonction StringMap.cardinal.
let has_30_departments departments =
StringMap.cardinal departments >= 30
On veut parcourir tous les éléments de la structure de dictionnaire pour obtenir une valeur agrégée, donc on utilise fold. Pour rester lisible, on utilise ici une fonction nommée.
let find_number_of_sponsors departments =
let add_department_count department sponsors count =
count + min 50 (List.length sponsors)
in
StringMap.fold add_department_count 0 departments
On crée un prédicat qu'on utilise avec List.filter. Le prédicat transforme la liste en dictionnaire et utilise les fonctions des questions précédentes.
let is_valid_sponsorship sponsorship =
let map = convert_sponsors_into_map sponsorship in
has_30_departments map
&& find_number_of_sponsors map >= 500
let extract_valid_sponsorships sponsorships =
sponsorships
|> List.filter is_valid_sponsorship
On compte le nombre de fois où chaque signataire a signé. Pour cela, on crée un dictionnaire dont les clés sont les signataires.
Ensuite on compte tous les parrains de tous les candidats et on garde ceux ayant parrainés deux fois.
module OrderedSponsor =
struct
type t = sponsor
let compare sponsor1 sponsor2 =
let delta = String.compare sponsor1.last_name sponsor2.last_name in
if delta = 0 then
String.compare sponsor1.first_name sponsor2.first_name
else
delta
end
module SponsorMap = Map.Make(Sponsor)
let count_sponsor map sponsor =
let count =
SponsorMap.find_opt sponsor map
|> Option.value ~default:0
in
SponsorMap.add sponsor (count+1) map
let invalid_sponsors sponsorships =
sponsorships
|> List.concat
|> List.fold_left count_sponsor SponsorMap.empty
|> SponsorMap.filter (fun count -> count > 1)
let has_no_duplicate_sponsor sponsorships =
SponsorMap.is_empty (invalid_sponsors sponsorships)
Exercice 3 : Lentilles.
1 . Un coup d'état
let coup state =
{ state with
head_of_state =
{ state.head_of_state with
id =
{ state.head_of_state.id with
genre = Robot
}
}
}
2 . Des lentilles pour simplifier les manipulations.
let id_of_head_lens =
Lens.create
~setter:(fun head id -> { head with id })
~getter:(fun head -> head.id)
let name_of_id_lens =
Lens.create
~setter:(fun id name -> { id with name })
~getter:(fun id -> id.name)
let genre_of_id_lens =
Lens.create
~setter:(fun id genre -> { id with genre })
~getter:(fun id -> id.genre)
3 . Composer des lentilles.
let genre_of_state_lens =
let open Lens in
head_of_state_lens >> id_of_head_lens >> genre_of_id_lens
4 . Utiliser des lentilles.
let change_genre =
Lens.over genre_of_state_lens
~f:(function
| Male -> Robot
| Robot -> Female
| Female -> Male
)
5 . Élection présidentielle.
let elect =
let open Lens in
set
(head_of_state_lens >> id_of_head_lens >> name_of_id_lens)
france
"Xavier Leroy"
6 . Type d'une lentille.
Une façon simple de faire et de stocker la fonction d'accès et la fonction de modification.
type ('item,'content) lens =
{ setter : 'content -> 'item -> 'content;
getter : 'content -> 'item
}
La composition s'écrit alors :
let compose b_in_a c_in_b =
{ setter =
fun a new_c ->
let b = b_in_a.getter a in
let new_b = c_in_b.setter b new_c in
b_in_a.setter a new_b;
getter =
fun a ->
a
|> b_in_a.getter
|> c_in_b.getter
}
Il existe cependant des façons plus générales de faire ceci.