Update and some portion of code

This commit is contained in:
Jeff Lance 2022-03-05 18:09:26 +01:00
parent f4f272c625
commit f411dab924
6 changed files with 234 additions and 165 deletions

38
main.py
View File

@ -110,7 +110,7 @@ def mainmenu_option4(students_list):
# On récupère l'élève associé à l'ID
student = students_list[student_id]
# On demande le siège où l'asseoir
student_seat = interface.console_ask_student_seat()
student_seat = interface.console_ask_student_seat(seatingplan)
# Si on a pu récupérer une valeur
if seatingplan.is_a_seat(student_seat):
# On positionne l'élève
@ -187,8 +187,8 @@ def mainmenu_option7(students_list):
Plan de classe possible.
:rtype: iterator
"""
number_of_proposals = input('Nombre maximal de propositions à faire : ')
number_of_proposals = int(number_of_proposals)
number_of_proposals = input('Nombre maximal de propositions à faire (5 par défaut) : ')
number_of_proposals = int(number_of_proposals or "5")
print('Presser Ctrl-C pour interrompre...')
try:
return iter(list(islice(engine.solve(seatingplan, students_list),
@ -208,7 +208,8 @@ def mainmenu_option8():
:return: None
"""
print('Effacement du plan de classe...')
engine.flush_seatingplan(seatingplan)
# engine.flush_seatingplan(seatingplan)
seatingplan.flush()
def mainmenu_option_next(solution):
@ -225,14 +226,27 @@ def mainmenu_option_next(solution):
"""
if solution:
try:
engine.flush_seatingplan(seatingplan)
engine.write_solution_to_seatingplan(next(solution), seatingplan)
# engine.flush_seatingplan(seatingplan)
seatingplan.flush()
# engine.write_solution_to_seatingplan(next(solution), seatingplan)
seatingplan.write_solution(next(solution))
except StopIteration:
print("Il n'y a plus de propositions de placement")
else:
print("Vous devez le calcul de solutions d'abord")
def mainmenu_option9():
"""Option 9 du menu principal.
:return: None
"""
print('Teste plan de classe...')
print(engine.verify_solution(seatingplan))
def settingsmenu():
""" Menu paramètres.
@ -286,10 +300,15 @@ def mainmenu():
elif (command == '7'):
proposals = mainmenu_option7(students_list)
# On affiche la première solution
mainmenu_option_next(proposals)
elif (command == '8'):
mainmenu_option8()
elif (command == '9'):
mainmenu_option9()
elif (command == 'n'):
mainmenu_option_next(proposals)
@ -300,8 +319,11 @@ def mainmenu():
return s
# DEBUG: print solutions
for s in engine.solve(seatingplan, students_list):
print({p: f(s) for p, s in s.items()})
try:
for s in engine.solve(seatingplan, students_list):
print({p: f(s) for p, s in s.items()})
except KeyboardInterrupt:
pass
# DEBUG
elif (command == 's'):

View File

@ -46,72 +46,6 @@ class Engine:
self.DELTA_FOR_MAX_CHAT_LVL_4 = 2
self.DELTA_FOR_MAX_CHAT_LVL_5 = 2
def get_neighbourhood(self, seatingplan, seat, radius):
"""Dans un plan de classe, retourne le voisinage dans un rayon donné
d'une place.
:param seatingplan:
Plan de classe.
:type seatingplan: SeatingPlan
:param seat:
Coordonnées de la place dans le plan de classe.
:type seat: tuple
:param radius:
Rayon du voisinage.
:type radius: int
:return:
Liste des places voisines.
:rtype: list
"""
# La liste des voisins
neighbourhood = []
# 'position' est un tuple de coordonnées (i, j) où:
# - i compris entre 0 et seatingplan.row-1
# - j compris entre 0 et seatingplan.col-1
#
# Les voisins sont les élèves aux places de coordonnées situées
# dans le carré passant par les points suivants :
# (i, j-radius), (i, j+radius), (i-radius, j), (i+radius, j),
# (i-radius, j-radius), (i-radius, j+radius),
# (i+radius, j-radius) et (i+radius, j+radius)
# On analyse ce voisinage et on n'ajoute un voisin que s'il y en a un
for i in range(-radius, radius+1, 1):
for j in range(-radius, radius+1, 1):
# Traitons la place voisine
neighbour_seat = (seat[0]+i, seat[1]+j)
# print("Place: ", neighbour_seat) # DEBUG #
# Cette place est-elle ?
# - différente de la place autour de laquelle on regarde
# - bien comprise dans le plan de classe (coordonnées de
# dépassant pas celles du plan de classe)
if (neighbour_seat != seat and
0 <= neighbour_seat[0] <= seatingplan.row-1 and
0 <= neighbour_seat[1] <= seatingplan.col-1):
neighbourhood.append(neighbour_seat)
# On retourne notre liste de places voisines
return neighbourhood
def are_safe_neighbours(self, student, neighbour, delta_chat_lvl):
"""Indique si deux élèves constituent de bons voisins.
C'est-à-dire si la différence de leur coefficient de bavardage
est d'une certaine valeur.
:param student:
Élève.
:type student: Student
:param neighbour:
Élève.
:type neighbour: Student
:param delta_chat_lvl:
Différence entre les coefficients de bavardage.
:type delta_chat_lvl: int
:rtype: bool
"""
return bool(abs(student.chat_lvl - neighbour.chat_lvl)
>= delta_chat_lvl)
def respect_constraints(self, seat, student, seatingplan, solution):
"""Indique si une place associée à une élève satisfait aux
contraintes, à savoir si :
@ -155,9 +89,8 @@ class Engine:
else:
# Si non, veŕifions le voisinage de la place
# Récupérons le voisinage de la place en cours
neighbourhood = self.get_neighbourhood(seatingplan,
seat,
self.NEIGHBOURHOOD_RADIUS)
neighbourhood = seatingplan.get_seat_neighbourhood(seat,
self.NEIGHBOURHOOD_RADIUS)
for neighbour_seat in neighbourhood:
# On ne teste les contraintes que sur les sièges déjà occupés
@ -174,34 +107,83 @@ class Engine:
# Les contraintes portent sur la différence de
# coefficient de bavardage entre deux voisins:
if max_chat_lvl == 5:
result = result and self.are_safe_neighbours(
student,
result = result and student.has_safe_neighbour(
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_5)
elif max_chat_lvl == 4:
result = result and self.are_safe_neighbours(
student,
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_4)
result = result and student.has_safe_neighbour(
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_4)
elif max_chat_lvl == 3:
result = result and self.are_safe_neighbours(
student,
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_3)
result = result and student.has_safe_neighbour(
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_3)
elif max_chat_lvl == 2:
result = result and self.are_safe_neighbours(
student,
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_2)
result = result and student.has_safe_neighbour(
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_2)
elif max_chat_lvl == 1:
result = result and self.are_safe_neighbours(
student,
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_1)
result = result and student.has_safe_neighbour(
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_1)
else:
result = result and True
# Le siège voisin n'est pas occupé, il est donc valide
result = result and True
# retournons la validité de l'association
return result
def verify_solution(self, seatingplan):
solution = seatingplan.mapping
result = True
for seat in list(solution.keys()):
student = seatingplan.get_student(seat)
neighbourhood = seatingplan.get_seat_neighbourhood(seat,
self.NEIGHBOURHOOD_RADIUS)
for neighbour_seat in neighbourhood:
# On ne teste les contraintes que sur les sièges déjà occupés
# donc sur ceux faisant déjà partie des solutions
if neighbour_seat in list(solution.keys()):
# On récupère l'élève du siège voisin
neighbour = solution[neighbour_seat]
# Lequel de l'élève en cours ou du voisin
# est le plus bavard ?
max_chat_lvl = max(student.chat_lvl,
neighbour.chat_lvl)
# Les contraintes portent sur la différence de
# coefficient de bavardage entre deux voisins:
if max_chat_lvl == 5:
result = result and student.has_safe_neighbour(
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_5)
elif max_chat_lvl == 4:
result = result and student.has_safe_neighbour(
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_4)
elif max_chat_lvl == 3:
result = result and student.has_safe_neighbour(
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_3)
elif max_chat_lvl == 2:
result = result and student.has_safe_neighbour(
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_2)
elif max_chat_lvl == 1:
result = result and student.has_safe_neighbour(
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_1)
else:
result = result and True
@ -320,64 +302,3 @@ class Engine:
elif idx_student == 0:
end = True
def write_solution_to_seatingplan(self, solution, seatingplan):
"""Place les élèves de la solution dans un plan de classe.
:param solution:
Solution de positionnement des élèves dans le plan de classe.
:type solution: dict
:param seatingplan:
Plan de classe.
:type seatingplan: SeatingPlan
:return: None
"""
for place, student in solution.items():
if student is not None:
seatingplan.place_student(student, place)
else:
seatingplan.mapping[place] = None
def flush_seatingplan(self, seatingplan):
"""Vide le plan de classe. Supprime tous les élèves placés.
:param seatingplan:
Plan de classe.
:type seatingplan: SeatingPlan
:return: None
"""
for seat in list(seatingplan.mapping.keys()):
student = seatingplan.get_student(seat)
if student is not None:
seatingplan.remove_student(student)
def get_corners(self, seatingplan):
""" Renvoie, pour un plan de classe, la liste des places aux coins
ou extrémités.
C'est-à-dire une place située en : (0,0), (0,m-1),
(n-1,0) ou (n-1,m-1) pour un plan de classe de dimensions n x m.
:param seatingplan:
Plan de classe.
:type seatingplan: SeatingPlan
:return:
Liste des places aux coins/extrémités.
:rtype: list
"""
# Dimensions du plan de classe
n = seatingplan.raw
m = seatingplan.col
# Le plan de classe est un tableau de dimensions n x m :
# - si n>1 et m>1, il possède 4 coins
# - si n=1 et m>1, il en possède 2
# - si n>1 et m=1, il en possède 2
# - si n=1 et m=1, il en possède 1
#
# On utilise donc un set qui va permettre d'éliminer les doublons de
# coordonnées
# Ce set est ensuite "traduit" en liste
corners = list({(0, 0), (n-1, 0), (0, m-1), (n-1, m-1)})
return corners

View File

@ -384,7 +384,7 @@ def console_ask_student_id():
#
# Option 4
#
def console_ask_student_seat():
def console_ask_student_seat(seatingplan):
"""Demande à l'utilisateur les coordonnées d'une place
dans le plan de classe.
@ -401,10 +401,9 @@ def console_ask_student_seat():
# sinon, on intercepte une exception
student_row = int(student_row)
# Si la valeur est entière mais négative
if student_row <= 0:
if student_row < 1 or student_row > seatingplan.row:
# On lève une exception
raise ValueError('Le numéro de rangée doit être strictement '
'positif')
raise ValueError('Mauvais range')
# Tout va bien, on passe au numéro de colonne
student_col = input('\nVeuillez entrer le numéro de colonne où vous'
@ -415,10 +414,10 @@ def console_ask_student_seat():
# sinon, on intercepte une exception
student_col = int(student_col)
# Si la valeur est entière mais négative
if student_col <= 0:
if student_row < 1 or student_col > seatingplan.col:
# On lève une exception
raise ValueError('Le numéro de colonne doit être strictement '
'positif')
raise ValueError('Mauvais range')
except ValueError:
print('La valeur saisie pour le numéro de colonne est invalide')
student_col = None
@ -429,7 +428,7 @@ def console_ask_student_seat():
# On renvoit les valeurs entrées mais ajustées à l'indexation
# du plan de classe
return student_row, student_col
return student_row-1, student_col-1
#

View File

@ -57,6 +57,36 @@ class SeatingPlan:
self.mapping = {(i, j): None for i in range(row)
for j in range(col)}
def get_corners(self):
""" Renvoie, pour un plan de classe, la liste des places aux coins
ou extrémités.
C'est-à-dire une place située en : (0,0), (0,m-1),
(n-1,0) ou (n-1,m-1) pour un plan de classe de dimensions n x m.
:param seatingplan:
Plan de classe.
:type seatingplan: SeatingPlan
:return:
Liste des places aux coins/extrémités.
:rtype: list
"""
# Dimensions du plan de classe
n = self.raw
m = self.col
# Le plan de classe est un tableau de dimensions n x m :
# - si n>1 et m>1, il possède 4 coins
# - si n=1 et m>1, il en possède 2
# - si n>1 et m=1, il en possède 2
# - si n=1 et m=1, il en possède 1
#
# On utilise donc un set qui va permettre d'éliminer les doublons de
# coordonnées
# Ce set est ensuite "traduit" en liste
corners = list({(0, 0), (n-1, 0), (0, m-1), (n-1, m-1)})
return corners
def is_a_seat(self, seat):
"""Renvoie si une place est valide ou non.
@ -115,11 +145,59 @@ class SeatingPlan:
:rtype: tuple or False
"""
try:
return list(self.mapping.keys())[list(self.mapping.values())
return self.get_seats()[list(self.mapping.values())
.index(student)]
# return list(self.mapping.keys())[list(self.mapping.values())
# .index(student)]
except ValueError:
return False
def get_seat_neighbourhood(self, seat, radius):
"""Dans un plan de classe, retourne le voisinage dans un rayon donné
d'une place.
:param seat:
Coordonnées de la place dans le plan de classe.
:type seat: tuple
:param radius:
Rayon du voisinage.
:type radius: int
:return:
Liste des places voisines.
:rtype: list
"""
# La liste des voisins
neighbourhood = []
# 'position' est un tuple de coordonnées (i, j) où:
# - i compris entre 0 et seatingplan.row-1
# - j compris entre 0 et seatingplan.col-1
#
# Les voisins sont les élèves aux places de coordonnées situées
# dans le carré passant par les points suivants :
# (i, j-radius), (i, j+radius), (i-radius, j), (i+radius, j),
# (i-radius, j-radius), (i-radius, j+radius),
# (i+radius, j-radius) et (i+radius, j+radius)
# On analyse ce voisinage et on n'ajoute un voisin que s'il y en a un
for i in range(-radius, radius+1, 1):
for j in range(-radius, radius+1, 1):
# Traitons la place voisine
neighbour_seat = (seat[0]+i, seat[1]+j)
# print("Place: ", neighbour_seat) # DEBUG #
# Cette place est-elle ?
# - différente de la place autour de laquelle on regarde
# - bien comprise dans le plan de classe (coordonnées de
# dépassant pas celles du plan de classe)
if (neighbour_seat != seat and
0 <= neighbour_seat[0] <= self.row-1 and
0 <= neighbour_seat[1] <= self.col-1):
neighbourhood.append(neighbour_seat)
# On retourne notre liste de places voisines
return neighbourhood
def get_seats(self):
return list(self.mapping.keys())
def is_empty_seat(self, seat):
"""Indique si une place est libre.
@ -203,3 +281,28 @@ class SeatingPlan:
and self.place_student(student_two, seat_one)):
return True
return False
def flush(self):
"""Vide le plan de classe. Supprime tous les élèves placés.
:return: None
"""
for seat in self.get_seats():
student = self.get_student(seat)
if student is not None:
self.remove_student(student)
def write_solution(self, solution):
"""Place les élèves de la solution dans un plan de classe.
:param solution:
Solution de positionnement des élèves dans le plan de classe.
:type solution: dict
:return: None
"""
for place, student in solution.items():
if student is not None:
self.place_student(student, place)
else:
self.mapping[place] = None

View File

@ -32,3 +32,20 @@ class Student:
self.name = name
self.chat_lvl = chat_lvl
self.friends = friends
def has_safe_neighbour(self, neighbour, delta_chat_lvl):
"""Indique si deux élèves constituent de bons voisins.
C'est-à-dire si la différence de leur coefficient de bavardage
est d'une certaine valeur.
:param neighbour:
Élève.
:type neighbour: Student
:param delta_chat_lvl:
Différence entre les coefficients de bavardage.
:type delta_chat_lvl: int
:rtype: bool
"""
return bool(abs(self.chat_lvl - neighbour.chat_lvl)
>= delta_chat_lvl)

7
tests/user_table_6.csv Normal file
View File

@ -0,0 +1,7 @@
"ID";"NAME";"CHAT LEVEL"
0;"Jean NEYMAR";1
1;"Sam DÉPASSE";0
2;"Elsa DORSA";2
3;"Sophie STICKÉ";3
4;"Terry GOLO";4
5;"Robin DIDON";5
1 ID NAME CHAT LEVEL
2 0 Jean NEYMAR 1
3 1 Sam DÉPASSE 0
4 2 Elsa DORSA 2
5 3 Sophie STICKÉ 3
6 4 Terry GOLO 4
7 5 Robin DIDON 5