Repository navigation
Expand file tree
/
Copy pathdijkstra.py
More file actions
82 lines (61 loc) · 2.35 KB
/
Copy pathdijkstra.py
File metadata and controls
82 lines (61 loc) · 2.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
"""Trusted, repository-controlled Dijkstra reference implementation."""
from __future__ import annotations
from dataclasses import dataclass
import heapq
import math
from typing import Mapping, Sequence
NodeId = str
Graph = Mapping[NodeId, Sequence[tuple[NodeId, float]]]
@dataclass(frozen=True)
class DijkstraResult:
found: bool
cost: float | None
path: list[NodeId]
def _validate_graph(graph: Graph, start: NodeId, goal: NodeId) -> None:
if start not in graph or goal not in graph:
raise ValueError("start and goal must be present in the graph")
for edges in graph.values():
for neighbor, weight in edges:
if neighbor not in graph:
raise ValueError(f"unknown node: {neighbor}")
if not math.isfinite(weight) or weight < 0:
raise ValueError("Dijkstra requires finite, nonnegative weights")
def dijkstra(graph: Graph, start: NodeId, goal: NodeId) -> DijkstraResult:
"""Return one deterministic shortest path from start to goal."""
_validate_graph(graph, start, goal)
distances = {node: math.inf for node in graph}
parents: dict[NodeId, NodeId] = {}
distances[start] = 0.0
frontier: list[tuple[float, NodeId]] = [(0.0, start)]
while frontier:
current_distance, current = heapq.heappop(frontier)
if current_distance != distances[current]:
continue
if current == goal:
break
for neighbor, weight in graph[current]:
candidate = current_distance + weight
if candidate >= distances[neighbor]:
continue
distances[neighbor] = candidate
parents[neighbor] = current
heapq.heappush(frontier, (candidate, neighbor))
if math.isinf(distances[goal]):
return DijkstraResult(found=False, cost=None, path=[])
path = [goal]
while path[-1] != start:
path.append(parents[path[-1]])
path.reverse()
return DijkstraResult(found=True, cost=distances[goal], path=path)
def _smoke_test() -> None:
graph: Graph = {
"A": [("B", 2), ("C", 7)],
"B": [("C", 1), ("D", 5)],
"C": [("D", 1)],
"D": [],
}
result = dijkstra(graph, "A", "D")
assert result == DijkstraResult(True, 4.0, ["A", "B", "C", "D"])
if __name__ == "__main__":
_smoke_test()
print("python dijkstra: ok")