diff --git a/README.md b/README.md index 8e9f264..13a5bb1 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ def function1( -[Source](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/src/ex1_.py) +[Source](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/src/Ex1.py) / [Tests](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/tests/test_ex1.py) @@ -46,7 +46,7 @@ def function1( - + @@ -70,7 +70,7 @@ def function2(
But...
Input
-[Source](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/src/ex2_.py) +[Source](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/src/Ex2.py) / [Tests](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/tests/test_ex2.py) @@ -104,7 +104,7 @@ def function3( -[Source](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/src/ex3_.py) +[Source](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/src/Ex3.py) / [Tests](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/tests/test_ex3.py) @@ -138,7 +138,7 @@ def function4( -[Source](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/src/ex4_.py) +[Source](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/src/Ex4.py) / [Tests](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/tests/test_ex4.py) @@ -172,6 +172,6 @@ def function5( -[Source](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/src/ex5_.py) +[Source](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/src/Ex5.py) / [Tests](https://git.kb28.ch/HEL/AlgoDS-Examen2025/src/branch/main/tests/test_ex5.py) diff --git a/src/Ex2.py b/src/Ex2.py new file mode 100644 index 0000000..175c9ff --- /dev/null +++ b/src/Ex2.py @@ -0,0 +1,95 @@ + +""" +Nom/Prénom: Heredero/Louis +Explications: +Tout d'abord, le réseau de routes et d'intersections peut être représenté par un graphe, +dans lequel les interséctions sont les nœuds et les routes les arêtes. + +Ainsi, nous cherchons à trouver le chemin le plus court du point de départ au +point d'arrivée, tel que les routes marquées 1 ne soient parcourues que de jour +(c'est-à-dire si leurs index dans le chemin parcouru est pair), et celles marquées -1 +seulement de nuit (index impair). En plus des routes données, nous pouvons aussi +rester sur un même nœud pour une nuit. +Afin de bien gérer les différences d'états entre jour et nuit, nous pouvons intégrer le demi-jour +associé à chaque visite de nœud. Ainsi, une même intersection peut avoir deux nœud: +un pour une visite de jour, et un de nuit. + +Pour résoudre ce problème, comme nous ne pouvons pas établir d'heuristique mesurant +notre distance au point d'arrivée, nous pouvons utiliser un algorithme BFS assez simple: +- Initialiser la liste des nœuds à traiter avec le nœud de départ et le demi-jour de départ (`DAY`) +- Tant que nous n'avons pas trouvé le nœud d'arrivée: + - Pour chaque nœud à traiter : + - Visiter les nœuds voisins non visités (liés par une route praticable) + - Indiquer pour chaque voisin son parent + - L'ajouter à la nouvelle liste des nœuds à traiter + - Alterner le demi-jour courant + - Recommencer avec la nouvelle liste de nœuds à traiter +""" +from typing import Optional + +ALWAYS = 0 +DAY = 1 +NIGHT = -1 + + +def get_path(visited: dict[tuple[int, int], Optional[int]], end: int, end_time: int) -> list[int]: + path: list[int] = [] + parent: Optional[int] = end + + time: int = end_time + while parent is not None: + path.append(parent) + parent = visited[(parent, time)] + time = -time + + return list(reversed(path)) + +def findSafestPath(start: int, end: int, intersections: list[tuple[int, int, int]]) -> list[int]: + edges: dict[int, dict[int, set[int]]] = { + ALWAYS: {}, + DAY: {}, + NIGHT: {} + } + + for i1, i2, mode in intersections: + edge_dict: dict[int, set[int]] = edges[mode] + if i1 not in edge_dict: + edge_dict[i1] = set() + if i2 not in edge_dict: + edge_dict[i2] = set() + edge_dict[i1].add(i2) + edge_dict[i2].add(i1) + + visited: dict[tuple[int, int], Optional[int]] = { + (start, DAY): None + } + to_process: list[int] = [start] + time = DAY + while len(to_process) != 0: + to_process2 = [] + time2 = -time + for idx in to_process: + always: set[int] = edges[ALWAYS].get(idx, set()) + matching_time: set[int] = edges[time].get(idx, set()) + neighbors: set[int] = always | matching_time + neighbors.add(idx) + + for neighbor in neighbors: + key = (neighbor, time2) + # Skip if already visited + if key in visited: + continue + visited[key] = idx + if neighbor == end: + return get_path(visited, end, time2) + to_process2.append(neighbor) + + to_process = to_process2 + time = time2 + + return [] + + +if __name__ == '__main__': + print(findSafestPath(0,2,[(0, 1, -1), (1, 2, 0)]) == [0, 0, 1, 2]) + print(findSafestPath(0, 5, [(0,1,0), (0,2,1), (2,1,-1), (1,3,-1), (2,4,-1), (3,5,1), (3,4,0), (4,5,-1)]) == [0, 1, 3, 5]) diff --git a/tests/test_ex2.py b/tests/test_ex2.py index b24a888..54e6601 100644 --- a/tests/test_ex2.py +++ b/tests/test_ex2.py @@ -1,8 +1,20 @@ import unittest +from Ex2 import findSafestPath + + class MyTestCase(unittest.TestCase): def test_simple1(self): - pass + self.assertEqual( + findSafestPath(0, 2, [(0, 1, -1), (1, 2, 0)]), + [0, 0, 1, 2] + ) + + def test_simple2(self): + self.assertEqual( + findSafestPath(0, 5, [(0,1,0), (0,2,1), (2,1,-1), (1,3,-1), (2,4,-1), (3,5,1), (3,4,0), (4,5,-1)]), + [0, 1, 3, 5] + ) if __name__ == '__main__': unittest.main()