Implemented DFS

This commit is contained in:
Jan Mrna 2025-09-19 15:30:37 +02:00
parent 744ccaf478
commit 29f08036c2

View File

@ -46,6 +46,9 @@ class Map:
return x_in_bounds and y_in_bounds return x_in_bounds and y_in_bounds
def GetNeighbours(self, center_point: Point2D) -> list[Point2D]: def GetNeighbours(self, center_point: Point2D) -> list[Point2D]:
"""
Get list of neighboring points (without actually visiting them)
"""
points: list[Point2D] = [] points: list[Point2D] = []
x_center, y_center = center_point x_center, y_center = center_point
for x in range(-1,2): for x in range(-1,2):
@ -145,38 +148,47 @@ class DFS:
def CalculatePath(self, start: Point2D, end: Point2D) -> Path: def CalculatePath(self, start: Point2D, end: Point2D) -> Path:
assert self._map is not None, "SetMap must be called first" assert self._map is not None, "SetMap must be called first"
self._map.ResetVisitedCount() self._map.ResetVisitedCount()
start_time = time.perf_counter_ns() start_time = time.perf_counter_ns()
res = self._CalculatePath(start, end)
print(f"{res=}")
stop_time = time.perf_counter_ns() stop_time = time.perf_counter_ns()
self._elapsed_time_ns = stop_time - start_time self._elapsed_time_ns = stop_time - start_time
self._visited_node_count = self._map.GetVisitedCount() self._visited_node_count = self._map.GetVisitedCount()
return [(0,0), (5,5), (6,6), (1,9)] return res
def GetStats(self) -> (ElapsedTime_ns, VisitedNodeCount): def GetStats(self) -> (ElapsedTime_ns, VisitedNodeCount):
return self._elapsed_time_ns, self._visited_node_count return self._elapsed_time_ns, self._visited_node_count
def _visit(point: Point2D) def _CalculatePath(self,
point: Point2D,
class BFS: end_point: Point2D,
path: Optional[list[Point2D]] = None,
name = "Breadth First Search" visited: Optional[set[Point2D]] = None) -> Optional[Path]:
_map: Optional[Map] """
Find (any) path, not guaranteed to be optimal (and it most probably won't be)
def __init__(self) -> None: """
self._map = None if visited is None:
visited = set()
def SetMap(self, m: Map) -> None: if path is None:
self._map = m path = list()
# We don't need to know cost in this case, but we still want to track
def CalculatePath(self, start: Point2D, end: Point2D) -> Path: # how many nodes we've visited
assert self._map is not None, "SetMap must be called first" _ = self._map.Visit(point)
return [(0,0), (1,0), (2,0), (3,0)] # we keep visited nodes in separate list and set,
# as membership check is faster for set than for list,
def GetStats(self) -> (ElapsedTime_ns, VisitedNodeCount): # but set is not ordered
return 300.0, 21 visited.add(point)
path.append(point)
if point == end_point:
return path
for neighbor in self._map.GetNeighbours(point):
if neighbor not in visited:
res = self._CalculatePath(neighbor, end_point, path, visited)
if res:
return res
return None
# #
# Calculate paths using various methods and visualize them # Calculate paths using various methods and visualize them
@ -186,11 +198,11 @@ def main():
# Define the map and start/stop points # Define the map and start/stop points
m = Map(15,10) m = Map(15,10)
m.Randomize() m.Randomize()
starting_point: Point2D = (0,0) starting_point: Point2D = (0,9)
end_point: Point2D = (9,9) end_point: Point2D = (14,1)
path_finder_classes: list[PathFinder] = { path_finder_classes: list[PathFinder] = {
DFS, BFS DFS,
} }
v = Visualizer() v = Visualizer()
@ -201,13 +213,8 @@ def main():
path_finder.SetMap(m) path_finder.SetMap(m)
path = path_finder.CalculatePath(starting_point, end_point) path = path_finder.CalculatePath(starting_point, end_point)
elapsed_time, visited_nodes = path_finder.GetStats() elapsed_time, visited_nodes = path_finder.GetStats()
print(f"{path_finder.name:22}: took {elapsed_time} ns, visited {visited_nodes} nodes") print(f"{path_finder.name:22}: took {elapsed_time/1e6} ms, visited {visited_nodes} nodes")
v.DrawPath(path) v.DrawPath(path)
#
p = (1,9)
# print(f"{m.IsPointValid(p)=}")
# print(f"{m.GetCost(p)=}")
print(f"{m.GetNeighbours(p)}")
plt.show() plt.show()