Vous êtes sur la page 1sur 8

Année universitaire 22-23

Examen N°1
Matière Nombre de pages durée Classes
Informatique 8 2h 2ième Cycle préparatoire

NB : Si un candidat est amené à repérer ce qui peut lui sembler être une erreur d’énoncé, il le signalera sur sa
copie et devra poursuivre sa composition en expliquant les raisons des initiatives qu’il a été amené à prendre.

Problème : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 points
Partie I : Modélisation Objet d’un système de transport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 points
On souhaite modéliser en PYTHON le calcul de coûts de transport de marchandises. Les marchandises transpor-
tées seront des instances de la classe Marchandise dont la description est la suivante :

Nom Type Rôle


Attributs d’instance ref str représente la référence de la marchandise
d’une Marchandise poids float représente le poids de la marchandise (en kg)
volume float représente le volume de la marchandise (en litre)
Nom de la méthode Rôle
Méthodes relatives à
__init__ Le constructeur permettant d’initier l’objet Marchandise à
une Marchandise
partir de 3 paramètres ref, poids et volume.
__repr__ Retourne un str représentant une Marchandise sous la forme
'Marchandise ("ref", poids, volume)'

Les marchandises sont transportées sous la forme de cargaisons. Une cargaison est caractérisée par la distance sur
laquelle elle est transportée.

Nom Type Rôle


Attributs d’instance id int représente l’identifiant unique de la cargaison encours de-
d’une cargaison vant être auto-incrément.
data list regroupe les marchandises transportées par la cargaison en
question.
distance int strictement positif, caractérise le nombre de kilomètres sur
laquelle elle est transportée la cargaison en question.
Attribut de classe pour ids int initialisé par zéro et incrémenté après chaque instanciation
une cargaison d’une cargaison. Cet attribut est utilisé pour générer les
valeurs des ids des cargaisons instanciées.
Nom de la méthode Rôle
__init__ Le constructeur permettant d’initier l’objet Cargaison ne
contient aucune marchandise et dont la distance est don-
née en paramètre. L’attribut de classe ids doit être géré
Méthodes relatives à
convenablement afin d’assurer l’auto-incrémentassions des
une Cargaison
identifiants.
__str__ Retourne un str représentant une personne sous la forme
"Cargaison [représentation des marchandises ]"
__len__ Retourne le nombre de marchandises transportées par la
cargaison en question.
volumeTotal Retourne le volume total des marchandises mises dans la
cargaison en question
poidsTotal Retourne le poids total des marchandises mises dans la car-
gaison en question
verifierEncombrement Prend une marchandise m en entrée, retourne un booléen
indiquant si l’ajout de la marchandise dans la cargaison
respecte les contraintes d’encombrement.
ajouter Permet d’ajouter une marchandise m dans cette cargaison
si les contraintes d’encombrement sont toujours respectées.
cout Retourne le coût total du transport de cette cargaison.
Une cargaison ne peut réunir qu’un nombre limité de marchandises qui dépend d’un encombrement total de ces
marchandises à ne pas dépasser. Cet encombrement est soit le poids total, soit le volume total des marchandises,
selon le type de transport utilisé. Ce dernier influe aussi sur le calcul du coût de transport de la cargaison qui
dépend aussi de l’encombrement des marchandises de la cargaison. On distingue donc plusieurs types de cargaisons
selon le moyen de transport utilisé.
Les différents types de cargaison et leurs caractéristiques sont donnés par le tableau suivant :
Type Encombrement Coût Limite
Fluviale Poids distance × encombrement encombrement ≤ 300000
Routière Poids 4 × distance × encombrement encombrement ≤ 38000
Aérienne Volume 10 × distance × encombrement encombrement ≤ 80000
Aérienne Urgente Volume 2 × coût cargaison Aérienne encombrement ≤ 80000

TRAVAIL A FAIRE :
I.1. Définir la classe Marchandise :
I.1.a. Implémenter la méthode __init__. 1 point
I.1.b. Implémenter la méthode __repr__. 1 point

Solution:

Classe Marchandise
class Marchandise:

def __init__(self, ref, poids, volume):


self.ref = ref
self.poids = poids
self.volume = volume

def __repr__(self):
return "Marchandise({!r}, {}, {})".format(self.ref, self.poids, self.volume)

I.2. Définir la classe Cargaison :


I.2.a. Implémenter la méthode __init__. 1 point
I.2.b. Implémenter la méthode __str__ . 1 point
I.2.c. Implémenter la méthode __len__. 1 point
I.2.d. Implémenter la méthode volumeTotal. 1 point
I.2.e. Implémenter la méthode poidsTotal. 1 point
I.2.f. En supposant que la méthode verifierEncombrement a été définie correctement, implémenter la méthode 1 point
ajouter. Cette dernière doit déclencher une erreur si les contraintes d’encombrement ne sont pas remplies,
sinon la méthode doit effectuer les traitements nécessaires pour ajouter la marchandise à la cargaison
courante.

Solution:

Page 2
Classe Cargaison
class Cargaison:

ids = 0
def __init__(self, distance):
self.id = Cargaison.ids
Cargaison.ids += 1
self.data = list()
self.distance = distance

def __str__(self):
return "Cargaison {}".format(self.data)

def __len__(self):
return len(self.data)

def volumeTotal(self):
return sum(m.volume for m in self.data)

def poidsTotal(self):
return sum(m.poids for m in self.data)

def ajouter(self, m):


if self.verifier_encombrement(m):
self.data.append(m)
else:
raise Exception("Limite d'encombrement débordée !!!")

I.3. Définir les classes Fluviale, Routière, Aérienne , dérivées de la classe mère Cargaison. En redéfinissant 3 points
uniquement les méthodes nécessaires pour vérifier l’encombrement et mesurer le cout du transport.

Solution:

Page 3
Classes Fluviale, Routière et Aérienne
class Fulviale(Cargaison):

def verifier_encombrement(self, m):


limite = 300000
return self.poidsTotal() + m.poids <= limite

def cout(self):
return self.distance * self.poidsTotal()

class Routière(Cargaison):

def verifier_encombrement(self, m):


limite = 38000
return self.poidsTotal() + m.poids <= limite

def cout(self):
return 4 * self.distance * self.poidsTotal()

class Aérienne(Cargaison):

def verifier_encombrement(self, m):


limite = 80000
return self.volumeTotal() + m.volume <= limite

def cout(self):
return 10 * self.distance * self.volumeTotal()

I.4. Définir la classe AérienneUrgente dérivée de la classe mère Aérienne. Redéfinir la ou les méthode(s) néces- 1 point
saires.

Solution:

Classe AérienneUrgente
class AérienneUrgente(Aérienne):

def cout(self):
return 2 * super().cout()

Page 4
Partie II : Optimisation de transport des cargaisons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 points
La compagnie de transport envisage d’utiliser une stratégie pour optimiser le chargement des cargaisons dans les
différents véhicules de transport. Pour cette finalité, les cargaisons sont regroupées en fonction de leurs destinations
et suivant les différentes escales des véhicules. Étant donné un ensemble de cargaisons de même classe (Fluviale,
Routière, Aérienne ou AérienneUrgente) ayant des destinations compatibles et pouvant ainsi être chargée dans le
même véhicule. L’objectif de cette partie est de trouver un sous-ensemble de cargaison permettant de maximiser
le profit du voyage (total des coûts de transport des cargaisons) tout en respectant les contraintes de capacités
relatives au volume et au poids total pouvant être pris en charge par ce véhicule. Formellement, étant donné un
ensemble de cargaisons :
ec = {c1 , c2 . . . , cn }
L’objectif est de trouver les variables de décision xi ∈ {0, 1} ∀ i = 1 . . . n.
(
xi = 1 ⇔ La cargaison ci doit être chargée dans le voyage courant
Avec :
xi = 0 ⇔ La cargaison ci n’est pas considérée dans le voyage courant
Les valeurs xi doivent maximiser le profit :
n
X
xi ci .cout()
i=1
Tout en respectant les contraintes de capacités limites du véhicule :
 n
X
xi ci .poidsTotal() ≤ W : contrainte de capacité limite en poids.





 i=0


 n
X
xi ci .volumeTotal() ≤ V : contrainte de capacité limite en volume.





i=0

Où W et V sont des quantités réelles positives désignant respectivement le poids limite et le volume limite que le
véhicule concerné peut prendre en charge.

Un état dans ce problème d’optimisation est représenté par un triplet t = (i, w, v) où :


— i indique les cargaisons restantes à considérer : {c1 , . . . , ci }.
— w est le poids limite encore disponible : w = W − poids des cargaisons déjà prises en charge.
P

— v est le volume limite encore disponible : v = V − volume des cargaisons déjà prises en charge. .
P

La cargaison ci respecte les contraintes de capacité limite en poids et en volume relativement à un


état courant (i, w, v) si et seulement si : ci .poidsTotal() ≤ w et ci .volumeTotal() ≤ v. Sinon, la cargaison
transgresse les contraintes de capacité limite.

L’état initial de l’optimisation est décrit par le triplet t = (n, W, V) où :


— n est le nombre total de cargaisons (initialement toutes les cargaisons de ec sont considérées).
— W est la capacité limite en poids du véhicule de transport.
— V est la capacité limite en volume du véhicule de transport.
La formule permettant de calculer le profit maximal tout en respectant les contraintes peut être formulée récursi-
vement comme suit : Si on dénote par P(i, w, v) le profit maximal obtenu en considérant un état t = (i,w,v)
Alors :


 Cas d’arrêt : P (0, w, v) = 0
P (i − 1, w, v) si la cargaison courante ci transgresse les contraintes de capacité.

 

 

 


 


 

max (α, ᾱ) si la cargaison courante ci respecte les contraintes de capacité.

 


P (i, w, v) =

 Cas récursifs :
 
α = P (i − 1, w − ci .poidsTotal(), v − ci .volumeTotal()) + ci .cout() et

 


 
 



 


 


  
ᾱ = P (i − 1, w, v)

.
 

L’objectif de cette partie est d’implémenter une classe CargoOptimizer qui combine la démarche récursive précé-
dente avec la technique de mémoïsation pour trouver le sous-ensemble de cargaisons optimal. La description de
cette classe est comme suit :

Page 5
Nom Type Rôle
Attributs items list Une liste de cargaisons de même classe, représentant les éléments de l’ensemble des car-
d’instance gaisons de décision ec.
cache dict Un dictionnaire, où :
— Chaque clé est un tuple (i, w, v) représentant un état dans l’espace d’optimisation.
— Chaque valeur est un réel P(i,w,v) calculé suivant la démarche récursive ci-haut.
x list Une liste de booléens ayant la même taille que items correspondant aux variables de
décisions. xi = 1 ⇔ (self.x[i-1] = True) et réciproquement.
Nom de la méthode Rôle
__init__ Le constructeur permettant d’initier l’instance courante à partir d’une liste contenant les
cargaisons de décision {c1 . . . cn }. L’attribut items doit contenir une copie de cette liste,
cache est initialisé par un dictionnaire vide et x à une liste de booléens qui valent False.
Méthodes __getitem__ Prend un int i et retourne la cargaison ci d’indice i-1 de items. self[i] ⇔
self.items[i-1].
reset Permet de réinitialiser le contenu de dictionnaire cache à un dictionnaire vide.
expand_state Méthode récursive qui prend 3 paramètres i, w, v. Elle effectue l’appel du schéma
récursif expliqué ci-haut uniquement si le tuple t = (i,w,v) n’est pas présent
dans cache. Le résultat de cet appel est associé à la clé t puis inséré dans le
dictionnaire cache. La valeur cache[t] est finalement retournée par la fonction.
N. B. Pour générer tous les états possibles, appeler cette fonction avec l’état initial
(n, W, V).
fit Prend en entrée deux paramètres W et V correspondant aux poids limite et au volume
limite du véhicule. Elle remplit le dictionnaire cache par les différentes configurations
possibles d’états (i, w, v) et leurs profits associés, après l’avoir réinitialisé.
deduce C’est une méthode récursive qui représente la phase de déduction (calcul des xi ) elle
prend en entrée 3 paramètres i, w et v décrivant un état d’optimisation et permet de
remplir la liste x comme suit :
Condition d’arrêt : i = 0
Cas récursifs : soit α = cache[i, w, v] et ᾱ = cache[i-1, w, v]
si α ≥ ᾱ alors xi ← True, puis poursuivre la déduction récursivement à partir de
l’état (i-1, w- ci .poidsTotal(), v- ci .volumeTotal())
sinon xi ← False, puis poursuivre la déduction à partir de l’état (i-1, w, v).
N. B. Pour calculer toutes les xi , appeler cette fonction avec l’état initial (n, W, V).
get_idx Prend en entrée deux paramètres W et V représentant les capacités limites en poids et
en volume du véhicule. Cette fonction applique la méthode de déduction pour remplir
la liste x des variables de décision puis retourne un ensemble contenant les indices des
cargaisons qui seront chargées dans le véhicule.
__call__ Prend en entrée deux paramètres W et V représentant respectivement les capacités limites
en poids et en volume du véhicule. Cette fonction applique les étapes suivantes :
(i) Remplit le dictionnaire cache par tous les états possibles d’optimisation avec leurs
profits associés.
(ii) Retourne un tuple contenant deux éléments : la valeur optimale du profit et une liste
des cargaisons qui seront transportées par le véhicule.

TRAVAIL A FAIRE :
II.1. Définir la classe CargoOptimizer :
II.1.a. Définir la méthode __init__. 1 point
II.1.b. Définir la méthode __getitem__. 1 point
II.1.c. Définir la méthode reset. 1 point
II.1.d. Définir la méthode expand_state. 1 point
II.1.e. Définir la méthode fit. 1 point
II.1.f. Définir la méthode deduce. 1 point
II.1.g. Définir la méthode get_idx. 1 point
II.1.h. Définir la méthode __call__. 1 point

Page 6
Solution:

Classe CargoOptimizer
class CargoOptimizer:
def __init__(self, lst):
self.items = lst.copy()
self.cache = {}
self.x = [False] * len(lst)

def __getitem__(self, idx):


return self.items[idx-1]

def reset(self):
self.cache = {}

def expand_state(self, i, w, v):


if (i, w, v) not in self.cache:
if i == 0:
r = 0
elif self[i].poidsTotal() > w or self[i].volumeTotal() > v:
r = self.expand_state(i-1, w, v)
else:
alpha = self.expand_state(i-1, w-self[i].poidsTotal(),
,→ v-self[i].volumeTotal()) + self[i].cout()
alpha_bar = self.expand_state(i-1, w, v)
r = max(alpha, alpha_bar)
self.cache[i, w, v] = r
return self.cache[i, w, v]

def fit(self, W, V):


self.reset()
return self.expand_state(len(self.x), W, V)

def deduce(self, i, w, v):


if i == 0:
return
if self.cache[i, w, v] > self.cache[i-1, w, v]:
self.x[i-1] = True
self.deduce(i-1, w - self[i].poidsTotal(), v - self[i].volumeTotal())
else:
self.x[i-1] = False
self.deduce(i-1, w, v)

def get_idx(self, W, V):


self.deduce(len(self.x), W, V)
return {idx for idx, x in enumerate(self.x, start = 1) if x}

def __call__(self, W, V):


opt = self.fit(W,V)
idx = self.get_idx(W, V)
return opt, [self[i] for i in idx]

Page 7
Annexe
Le tableau suivant contient quelques méthodes utiles que l’étudiant peut éventuellement utiliser pour la composition
de ses réponses.
méthode/fonction rôle exemple
la classe str
str.format retourne un str résultant de la sub- " {} + {} = {}".format(1,1,2)
stitution des motifs par ses para- donne "1 + 1 = 2"
mètres
str.replace Retourne un nouvel str contenant str(list(range(5))).replace(",", " ")
où chaque occurrence du motif old donne '[0 1 2 3 4]'
est substitué par le motif new
la classe list
list.append permet d’ajouter un objet en queue l = [1,2]
de list l.append(20)
l donne [1,2,20,20]
list.copy retourne une copie superficielle de l = [1,2,20]; l1 = l.copy(); print(l1)
la liste. donne [1,2,20]
la classe dict
dict.keys Retourne un itérable formé par les list({1:3,"med":5}.keys())
clés du dict. donne [1,"med"]
dict.values Retourne un itérable formé par les list({1:3,"med":5}.values())
valeurs du dict. donne [3,5]
dict.pop Supprime puis retourne la valeur d = {1:3,"med":5}
associée à la clé passée en para- d.pop(1) donne 3
mètre. d donne {"med":5}
dict.update Mettre à jour le dict en utilisant d = {1:3,"med":5}
les éléments du dict passé en pa- d.update({1:5, "ali":6})
ramètre. d donne {1:5, "med":5, "ali":6}
d[clé] = valeur Insérer /mettre à jour la paire (clé,
valeur)
la classe set
set.add ajoute un élément à l’ensemble s = set()
s.add("med ali")
s donne {"med ali"}
set.discard élimine un élément de l’ensemble s = {"med", "ali"}
s.discard("ali")
s donne {"med"}
Fonction utile sur les itérables
max(it) Retourne la valeur maximale d’un max({5:2, 10:2, -10:22})
itérable it donne 10
Méthodes magiques

Opérations sur les itérables


Opération Méthode associée
len(obj) obj.__len__()
obj[index] obj.__getitem__(index)
Représentation textuelle
Opération Méthode associée
str(obj) obj.__str__()
repr(obj) obj.__repr__()
Construction d’une instance
Opération Méthode associée
...
inst = Classe(params)
Classe.__init__(inst, params)

Vous aimerez peut-être aussi