diff --git a/main.py b/main.py index 1862330..940239d 100755 --- a/main.py +++ b/main.py @@ -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'): diff --git a/sage/engine.py b/sage/engine.py index ef08a57..49b7778 100644 --- a/sage/engine.py +++ b/sage/engine.py @@ -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 diff --git a/sage/interface.py b/sage/interface.py index 56df391..9e82e43 100644 --- a/sage/interface.py +++ b/sage/interface.py @@ -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 # diff --git a/sage/seatingplan.py b/sage/seatingplan.py index 86358bc..42554c4 100644 --- a/sage/seatingplan.py +++ b/sage/seatingplan.py @@ -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 \ No newline at end of file diff --git a/sage/student.py b/sage/student.py index 38ed898..262cfee 100644 --- a/sage/student.py +++ b/sage/student.py @@ -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) \ No newline at end of file diff --git a/tests/user_table_6.csv b/tests/user_table_6.csv new file mode 100644 index 0000000..34b6fba --- /dev/null +++ b/tests/user_table_6.csv @@ -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 \ No newline at end of file