Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Toujours à la recherche de nouveaux exploits à accomplir, vous décidez de par

Commencez par trouver ce qui vous semble être le plus court chemin sans technique particulière. Notez votre réponse.

Bordeaux -> Orléans -> Paris -> Strasbourg
57

### 1.b Algorithme de Dijkstra

Sans l'implémenter pour le moment, trouvez le plus court chemin en utilisant manuellement l'algorithme de Dijkstra.
Expand Down Expand Up @@ -232,6 +235,9 @@ Mais il y a aussi des valeurs en vert : des gens très seuls et très riches, qu

Saurez-vous trouver le meilleur trajet de tête ? Probablement.

Bordeaux -> Nantes -> Rennes -> Rouen -> Paris -> Orleans -> Strasbourg
15

### 3.b Est-ce que Dijkstra fonctionne ?

Utilisez l'algorithme de Dijkstra pour résoudre le problème. Qu'observez-vous ?
Expand Down
18 changes: 18 additions & 0 deletions __main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from pathfinder.pathfinder import Pathfinder
from pathfinder.graphs import graph, spfa_graph
from pathfinder.city import City
from pathfinder.astar import AStar
from pathfinder.heuristics import heuristics
from pathfinder.spfa import SPFA

pathfinder = Pathfinder(graph)
print("Pathfinder : ",
pathfinder.get_shortest_path(City.BORDEAUX, City.STRASBOURG))

astar = AStar(graph, heuristics)
print("AStar : ",
astar.get_shortest_path(City.BORDEAUX, City.STRASBOURG))

spfa = SPFA(spfa_graph)
print("SPFA : ",
spfa.get_shortest_path(City.BORDEAUX, City.STRASBOURG))
40 changes: 40 additions & 0 deletions pathfinder/astar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from pathfinder.graphs import graph
from pathfinder.pathfinder import Pathfinder
from pathfinder.heuristics import heuristics
from pathfinder.city import City
from pathfinder.types import Path


class AStar(Pathfinder):
def __init__(self, graph, heuristics):
super().__init__(graph)
self.heuristics = heuristics

def get_shortest_path(self, start: City, end: City) -> Path:
"""
Returns the shortest path between two cities and the total cost of the
path.
"""
shortest_paths = {start: (None, 0)}
current_city = start
visited = set()

while current_city != end:
visited.add(current_city)
destinations = self.graph[current_city]
cost_to_current_city = shortest_paths[current_city][1]

for next_city in destinations:
self._update_shortest_paths(next_city, current_city,
cost_to_current_city,
shortest_paths)

next_destinations = {
city: (shortest_paths[city][1] + self.heuristics[city], city)
for city in shortest_paths if city not in visited}

current_city = min(next_destinations,
key=lambda city: next_destinations[city][0])

path, cost = self._build_path_and_cost(end, shortest_paths)
return Path(total=cost, steps=path)
16 changes: 16 additions & 0 deletions pathfinder/city.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from enum import Enum


class City(Enum):
BORDEAUX = "Bordeaux"
DIJON = "Dijon"
LILLE = "Lille"
LYON = "Lyon"
MARSEILLE = "Marseille"
NANTES = "Nantes"
PARIS = "Paris"
RENNES = "Rennes"
STRASBOURG = "Strasbourg"
TOULOUSE = "Toulouse"
ROUEN = "Rouen"
ORLEANS = "Orleans"
121 changes: 121 additions & 0 deletions pathfinder/graphs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from pathfinder.city import City

Graph = dict[City, dict[City, float]]

graph: Graph = {
City.BORDEAUX: {
City.NANTES: 19,
City.ORLEANS: 24,
City.LYON: 31,
City.TOULOUSE: 14
},
City.DIJON: {
City.STRASBOURG: 20,
City.PARIS: 16,
City.ORLEANS: 15,
City.LYON: 11
},
City.LILLE: {
City.ROUEN: 12,
City.PARIS: 13,
City.STRASBOURG: 29
},
City.LYON: {
City.BORDEAUX: 31,
City.DIJON: 11,
City.MARSEILLE: 18,
City.ORLEANS: 20,
City.TOULOUSE: 28,
},
City.MARSEILLE: {
City.LYON: 18,
City.TOULOUSE: 22
},
City.TOULOUSE: {
City.BORDEAUX: 14,
City.LYON: 28,
City.MARSEILLE: 22
},
City.NANTES: {
City.BORDEAUX: 19,
City.RENNES: 6,
City.ROUEN: 17,
City.ORLEANS: 16
},
City.ORLEANS: {
City.BORDEAUX: 24,
City.DIJON: 15,
City.LYON: 20,
City.NANTES: 16,
City.PARIS: 7,
City.ROUEN: 11
},
City.PARIS: {
City.DIJON: 16,
City.LILLE: 13,
City.ORLEANS: 7,
City.ROUEN: 7,
City.STRASBOURG: 26
},
City.RENNES: {
City.NANTES: 6,
City.ROUEN: 17
},
City.ROUEN: {
City.LILLE: 12,
City.NANTES: 19,
City.ORLEANS: 11,
City.PARIS: 7,
City.RENNES: 17
},
City.STRASBOURG: {
City.DIJON: 20,
City.LILLE: 29,
City.PARIS: 26
}
}

spfa_graph: Graph = {
City.BORDEAUX: {
City.NANTES: 50,
City.TOULOUSE: 50
},
City.DIJON: {
City.STRASBOURG: 30,
},
City.LILLE: {
},
City.LYON: {
City.DIJON: 20
},
City.MARSEILLE: {
City.LYON: 30
},
City.TOULOUSE: {
City.LYON: -75,
City.MARSEILLE: 40
},
City.NANTES: {
City.ORLEANS: 10,
City.RENNES: 20
},
City.ORLEANS: {
City.STRASBOURG: 15,
City.PARIS: 40
},
City.PARIS: {
City.LILLE: 50,
City.STRASBOURG: -10,
City.ORLEANS: -30
},
City.RENNES: {
City.PARIS: 20,
City.ROUEN: 10
},
City.ROUEN: {
City.PARIS: -50
},
City.STRASBOURG: {
City.LILLE: 50
}
}
16 changes: 16 additions & 0 deletions pathfinder/heuristics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from pathfinder.city import City

heuristics: dict[City, float] = {
City.BORDEAUX: 47,
City.DIJON: 15,
City.LILLE: 25,
City.LYON: 24,
City.MARSEILLE: 39,
City.TOULOUSE: 46,
City.NANTES: 44,
City.ORLEANS: 27,
City.PARIS: 25,
City.RENNES: 43,
City.ROUEN: 31,
City.STRASBOURG: float("inf")
}
73 changes: 73 additions & 0 deletions pathfinder/pathfinder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from pathfinder.graphs import Graph
from pathfinder.city import City
from pathfinder.types import Path


class Pathfinder:
def __init__(self, graph: Graph):
self.graph = graph

def get_shortest_path(self, start: City, end: City) -> Path:
"""
Returns the shortest path between two cities and the total cost of the
path.
"""
shortest_paths = {start: (None, 0)}
current_city = start
visited = set()

while current_city != end:
visited.add(current_city)
destinations = self.graph[current_city]
cost_to_current_city = shortest_paths[current_city][1]

for next_city in destinations:
self._update_shortest_paths(next_city, current_city,
cost_to_current_city,
shortest_paths)

next_destinations = {
city: shortest_paths[city]
for city in shortest_paths if city not in visited}
# if not next_destinations:
# return "Route Not Possible"
current_city = self._get_next_city(next_destinations)

path, cost = self._build_path_and_cost(end, shortest_paths)
return Path(total=cost, steps=path)

def _update_shortest_paths(self, next_city: City, current_city: City,
cost_to_current_city: float,
shortest_paths: dict) -> None:
"""
Updates the shortest path if a shorter path is found.
"""
cost = self.graph[current_city][next_city] + cost_to_current_city
if next_city not in shortest_paths:
shortest_paths[next_city] = (current_city, cost)
else:
current_lowest_cost = shortest_paths[next_city][1]
if current_lowest_cost > cost:
shortest_paths[next_city] = (current_city, cost)

def _get_next_city(self, next_destinations: dict):
"""
Returns the next city to visit.
"""
return min(next_destinations, key=lambda k: next_destinations[k][1])

def _build_path_and_cost(self, end: City, shortest_paths: dict) -> tuple[
list[City], float]:
"""
Returns the path and total cost of the path.
"""
path = []
total_cost = 0
current_city = end
while current_city is not None:
path.append(current_city)
next_city, cost = shortest_paths[current_city]
if next_city is not None: # Prevent adding cost for the start city
total_cost += self.graph[next_city][current_city]
current_city = next_city
return path[::-1], total_cost # Return reversed path and total cost
46 changes: 46 additions & 0 deletions pathfinder/spfa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from pathfinder.pathfinder import Pathfinder
from pathfinder.types import Path


class SPFA(Pathfinder):
def __init__(self, graph):
"""
Create a new SPFA pathfinder with the given graph.
"""
super().__init__(graph)

def _update_shortest_paths(self, next_city, current_city,
cost_to_current_city, shortest_paths) -> bool:
"""
Update the shortest paths if a shorter path is found.
"""
cost = self.graph[current_city][next_city] + cost_to_current_city
if next_city not in shortest_paths or cost < shortest_paths[next_city][
1]:
shortest_paths[next_city] = (current_city, cost)
return True
return False

def _get_next_city(self, queue):
return queue.pop(0)

def get_shortest_path(self, start, end) -> Path:
"""
Get the shortest path from start to end using the SPFA algorithm.
"""
shortest_paths = {start: (None, 0)}
queue = [start]

while queue:
current_city = self._get_next_city(queue)
destinations = self.graph[current_city]
cost_to_current_city = shortest_paths[current_city][1]

for next_city in destinations:
if self._update_shortest_paths(next_city, current_city,
cost_to_current_city,
shortest_paths):
queue.append(next_city)

path, cost = self._build_path_and_cost(end, shortest_paths)
return Path(total=cost, steps=path)
7 changes: 7 additions & 0 deletions pathfinder/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from typing import TypedDict, List
from pathfinder.city import City


class Path(TypedDict):
total: float
steps: List[City]