Optimiser un hôpital, grâce aux types abstraits
Ce concept est purement fictif et il ne s’agit bien entendu que d’un exercice d’application de l’optimisation possible des types abtraits en Algorithmique
Contexte
Imaginons que nous sommes engagés par un hôpital pour remplacer le système manuel de tri des patients arrivant aux urgences par un système informatique. Les patients arrivant aux urgences sont immédiatement évalués en fonction de la gravité de leur pathologie et de l’urgence de leur prise en charge. Ils sont notés : cette évaluation prend la forme d’une note allant entre 0 (les cas les moins urgents et les plus bénins) à N − 1 (les cas extrêmes à prendre en charge en premier).
Ensuite, ils vont dans une salle d’attente en attendant qu’un médecin les prenne en charge.
La méthode pour choisir le prochain patient en salle d’attente est la suivante :
- On prend d’abord le patient ayant la note la plus élevée.
- S’il y a plusieurs patients ayant la même note, on prend d’abord en charge le patient étant arrivé le plus tôt.
Mise en application
Nous allons implémenter plusieurs types pour représenter une salle d’attente ainsi qu’un patient, avec les avantages et les inconvénients pour chacun d’entre eux.
1ère version du type abstrait
On choisit dans un premier temps de représenter un patient par un couple d’entier :
- Le premier élément représente son score de gravité.
- Le second élément est un identifiant unique permettant de différencier deux personnes ayant le même score de gravité par exemple.
De plus, on choisit de représenter une salle d’attente par une simple liste de couples patients.
type patient = (int * int)
type room = patient list
Maintenant que ces deux types sont définis, nous pouvons à présent définir l’interface en version fonctionnelle :
Type d’opérations | Description | Implémenté en OCaml |
---|---|---|
Constructeur | Crée une salle d’attente vide | make_room: unit → (int * int) list |
Constructeur | Crée un couple ( score / id du patient) | make_patient: int → int → (int * int) |
Constructeur | Ajoute un couple patient à une salle d’attente | add_pat_room: (int * int) → (int * int) list → (int * int) list |
Constructeur | Faire sortir le patient prioritaire de la salle d’attente | call_patient: (int * int) list → (int * int) list |
Accesseur | Renvoie le patient à traiter en priorité dans une salle d’attente | get_prio: (int * int) list → (int * int) |
Parallèlement, nous pouvons définir l’interface en version impérative :
Type d’opérations | Description | Implémenté en OCaml |
---|---|---|
Constructeur | Crée une salle d’attente vide | make_room: unit → patient list |
Constructeur | Crée un couple ( score / id du patient) | make_patient: int → int → patient |
Transformateur | Ajoute un couple patient à une salle d’attente | add_pat_room: patient → room → unit |
Transformateur | Faire sortir le patient prioritaire de la salle d’attente | call_patient: room → patient |
Accesseur | Renvoie le patient à traiter en priorité dans une salle d’attente | get_prio: room → patient |
let make_room = []
let make_patient (score : int) (id : int) = (score, id)
(* Fonction permettant d'ajouter un patient à une salle d'attente *)
let add_pat_room patient_inp room_inp = patient_inp::room_inp
(* Fonction permettant d'obtenir le patient prioritaire *)
let get_prio room_inp = match room_inp with
| [] -> failwith "Salle d'attente complètement vide !"
| t::q -> let first_val = t in
let rec prio_auxi best lst_inp = match lst_inp with
| [] -> best
| t::q -> if fst t > fst best || fst t = fst best then prio_auxi t q
else prio_auxi best q
in prio_auxi first_val room_inp
(* Fonction permettant d'appeler le patient prioritaire et en pratique le retirer de la salle d'attente *)
let call_patient room_inp =
let patient_a_appeler = get_prio room_inp in
let rec search_for_patient room_inp_aux pat = match room_inp_aux with
| [] -> failwith "Erreur"
| t::q -> if fst t = fst pat then q
else t :: search_for_patient q pat
in search_for_patient room_inp patient_a_appeler
Complexités observées :
Type d’opérations | Complexité temporelle |
---|---|
make_room |
O(1) |
make_patient |
O(1) |
add_pat_room |
O(1) |
get_prio |
O(n) |
call_patient |
O(n²) |
On constate que chercher le patient dans la salle d’attente requiert un nombre important d’opérations, en effet, le patient prioritaire peut très bien se situer au fond de la salle d’attente (conceptuellement), ce qui fait donc perdre un temps précieux !
Pour contrer ce soucis, on décide de trier la salle d’attente afin que le patient prioritaire se situe en tête de la salle d’attente, puis le second juste après, etc… jusqu’au patient le moins prioritaire. On rappelle que les patients avec des blessures de même gravité sont triés de telle sorte que le patient arrivé avant est traité d’abord pour éviter les séquelles.
2ème version du type abstrait
Les représentations des patients et des salles d’attentes restent inchangées.
On choisit de trier la salle d’attente en utilisant le tri par insertion :
let rec insere lst element = match lst with
| [] -> [element]
| h::t -> if fst element > fst h then element::h::t else h::(insere t element)
let rec tri_insertion lst = match lst with
| [] -> []
| h::t -> insere (tri_insertion t) h
On propose par conséquent trois nouvelles versions des fonctions add_pat_room
, get_prio
et call_patient
:
let add_pat_room_v2 patient_inp room_inp = tri_insertion (patient_inp::room_inp)
let get_prio_v2 room_inp = match room_inp with
| [] -> failwith "Salle d'attente complètement vide !"
| t::q -> t
let call_patient_v2 room_inp = match room_inp with
| [] -> failwith "Salle d'attente complètement vide !"
| t::q -> q
On peut clairement voir ici que trouver et appeler la patient prioritaire est simplissime car il se trouve en tête de la liste (la salle d’attente en l’occurrence).
Complexités observées :
Type d’opérations | Complexité temporelle |
---|---|
make_room |
O(1) |
make_patient |
O(1) |
add_pat_room_v2 |
O(n²) |
get_prio_v2 |
O(1) |
call_patient_v2 |
O(1) |
On constate que la complexité en O(n²)
a été reléguée à l’ajout du patient dans la salle d’attente en raison du tri par insertion, si un tri avec une complexité temporelle moindre est souhaité alors on pourra utiliser un autre algorithme. Sinon toutes les autres opérations du type abstrait sont effectuées en O(1)
, soit une complexité en temps constante.
Auteur : Romain MELLAZA
Date de publication : 8 Janvier 2024