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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/venv/
/.idea/
4 changes: 4 additions & 0 deletions __main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from pathfinder.graphs import graph
from pathfinder.pathfinder import Pathfinder

pathfinder = Pathfinder(graph)
27 changes: 27 additions & 0 deletions pathfinder/astar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pathfinder.city import City
from pathfinder.graphs import Graph
from pathfinder.pathfinder import Pathfinder
from pathfinder.type import Path


class AStar(Pathfinder):
__heuristics: dict[City, float]

def __init__(self, graph: Graph, heuristics: dict[City, float]) -> None:
super().__init__(graph)
self.__heuristics = heuristics

def find_next_city(self, current_city: City, paths: dict[City, Path],
visited_cities: list[City]) -> City:
# Find the next city to visit
min_distance: float = float('inf')
for city, path in paths.items():
if (city not in visited_cities and
min_distance > path["total"] + self.__heuristics[city]):
min_distance = path["total"] + self.__heuristics[city]
current_city = city
return current_city

def is_loop_ended(self, current_city: City, end: City,
paths: dict[City, Path]) -> bool:
return paths.__contains__(end)
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.MARSEILLE: 18,
City.BORDEAUX: 31,
City.ORLEANS: 20,
City.DIJON: 11,
City.TOULOUSE: 28
},
City.MARSEILLE: {
City.LYON: 18,
City.TOULOUSE: 22
},
City.NANTES: {
City.BORDEAUX: 19,
City.ORLEANS: 16,
City.ROUEN: 19,
City.RENNES: 6
},
City.PARIS: {
City.LILLE: 13,
City.STRASBOURG: 26,
City.DIJON: 16,
City.ORLEANS: 7,
City.ROUEN: 7
},
City.RENNES: {
City.ROUEN: 17,
City.NANTES: 6
},
City.ROUEN: {
City.LILLE: 12,
City.PARIS: 7,
City.ORLEANS: 11,
City.NANTES: 19,
City.RENNES: 17
},
City.STRASBOURG: {
City.LILLE: 29,
City.PARIS: 26,
City.DIJON: 20
},
City.TOULOUSE: {
City.BORDEAUX: 14,
City.LYON: 28,
City.MARSEILLE: 22
},
City.ORLEANS: {
City.BORDEAUX: 24,
City.DIJON: 15,
City.LYON: 20,
City.PARIS: 7,
City.NANTES: 16,
City.ROUEN: 11
}
}

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.NANTES: {
City.ORLEANS: 10,
City.RENNES: 20
},
City.PARIS: {
City.ORLEANS: -30,
City.STRASBOURG: -10,
City.LILLE: 50
},
City.RENNES: {
City.ROUEN: 10,
City.PARIS: 20
},
City.ROUEN: {
City.PARIS: -50
},
City.STRASBOURG: {
City.LILLE: 50
},
City.TOULOUSE: {
City.LYON: -75,
City.MARSEILLE: 40
},
City.ORLEANS: {
City.PARIS: 40,
City.STRASBOURG: 15
}
}
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.NANTES: 44,
City.ORLEANS: 27,
City.PARIS: 25,
City.RENNES: 43,
City.ROUEN: 31,
City.TOULOUSE: 46,
City.STRASBOURG: 0
}
53 changes: 53 additions & 0 deletions pathfinder/pathfinder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from pathfinder.city import City
from pathfinder.graphs import Graph
from pathfinder.type import Path


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

def get_shortest_path(self, start: City, end: City) -> Path:
"""
Returns the shortest path between two cities using Dijkstra's algorithm
"""
paths: dict[City, Path] = {start: {"total": 0, "steps": [start]}}
visited_cities: list[City] = []
current_city: City = start

while not self.is_loop_ended(current_city, end, paths):
for city, distance in self.graph[current_city].items():
if city in visited_cities:
continue

path_distance: float = paths[current_city]["total"] + distance

# If the city is not in the paths or the new path is shorter
if city not in paths or path_distance < paths[city]["total"]:
steps: list[City] = paths[current_city]["steps"] + [city]
paths[city] = {"total": path_distance, "steps": steps}

visited_cities.append(current_city)

current_city = (
self.find_next_city(current_city, paths, visited_cities))

return paths[end]

def is_loop_ended(self, current_city: City, end: City,
paths: dict[City, Path]) -> bool:
"""Check if the loop should be ended"""
# in dijkstra, if the city we are visiting is the end city,
# it means we have found the shortest path
return current_city == end

def find_next_city(self, current_city: City, paths: dict[City, Path],
visited_cities: list[City]) -> City:
"""Find the next city to visit
(the one with the shortest path from the start city)"""
min_distance: float = float('inf')
for city, path in paths.items():
if city not in visited_cities and path["total"] < min_distance:
min_distance = path["total"]
current_city = city
return current_city
33 changes: 33 additions & 0 deletions pathfinder/spfa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from pathfinder.city import City
from pathfinder.pathfinder import Pathfinder
from pathfinder.type import Path


class SPFA(Pathfinder):
def get_shortest_path(self, start: City, end: City) -> Path:
"""Returns the shortest path between two cities using SPFA algorithm"""
paths: dict[City, Path] = {}
for city in self.graph:
paths[city] = {"total": float('inf'), "steps": []}

paths[start] = {"total": 0, "steps": [start]}

max_iterations: int = len(self.graph) - 2

i: int = 0
modified: bool = True
while i < max_iterations and modified is True:
modified = False
for city, distances in self.graph.items():
for next_city, distance in distances.items():
potential_distance: float = paths[city]["total"] + distance
if paths[next_city]["total"] <= potential_distance:
continue
# change the path only if the new path is shorter
paths[next_city]["total"] = potential_distance
paths[next_city]["steps"] = paths[city]["steps"] + [
next_city]
modified = True
i += 1

return paths[end]
8 changes: 8 additions & 0 deletions pathfinder/type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from typing import TypedDict

from pathfinder.city import City


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