+ def _generate_dijkstra(self, source: str) -> None:
+ """Internal helper that runs Dijkstra's from source"""
+ unvisited_nodes = self.get_vertices()
+
+ shortest_path: Dict[str, Numeric] = {}
+ for node in unvisited_nodes:
+ shortest_path[node] = math.inf
+ shortest_path[source] = 0
+
+ previous_nodes: Dict[str, str] = {}
+ while unvisited_nodes:
+ current_min_node = None
+ for node in unvisited_nodes:
+ if current_min_node is None:
+ current_min_node = node
+ elif shortest_path[node] < shortest_path[current_min_node]:
+ current_min_node = node
+
+ assert current_min_node
+ neighbors = self.graph[current_min_node]
+ for neighbor in neighbors:
+ tentative_value = (
+ shortest_path[current_min_node]
+ + self.graph[current_min_node][neighbor]
+ )
+ if tentative_value < shortest_path[neighbor]:
+ shortest_path[neighbor] = tentative_value
+ previous_nodes[neighbor] = current_min_node
+ unvisited_nodes.remove(current_min_node)
+ self.dijkstra = (source, previous_nodes, shortest_path)
+
+ def minimum_path_between(self, source: str, dest: str) -> Tuple[Numeric, List[str]]:
+ """Compute the minimum path (lowest cost path) between source
+ and dest.
+
+ .. note::
+
+ This method runs Dijkstra's algorithm
+ (https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm)
+ internally and caches the results. Subsequent calls made with
+ the same source node before modifying the graph are less
+ expensive due to these cached intermediate results.
+
+ Returns:
+ A tuple containing the minimum distance of the path and the path itself.
+ If there is no path between the requested nodes, returns (None, []).
+
+ .. graphviz::
+
+ graph g {
+ node [shape=record];
+ A -- B [w=3];
+ B -- D;
+ A -- C [w=2];
+ C -- D -- E -- F;
+ F -- F;
+ E -- G;
+ H;
+ }
+
+ >>> g = Graph()
+ >>> g.add_edge('A', 'B', 3)
+ >>> g.add_edge('A', 'C', 2)
+ >>> g.add_edge('B', 'D')
+ >>> g.add_edge('C', 'D')
+ >>> g.add_edge('D', 'E')
+ >>> g.add_edge('E', 'F')
+ >>> g.add_edge('E', 'G')
+ >>> g.add_edge('F', 'F')
+ >>> g.add_vertex('H')
+ True
+ >>> g.minimum_path_between('A', 'D')
+ (3, ['A', 'C', 'D'])
+ >>> g.minimum_path_between('A', 'H')
+ (None, [])
+
+ """
+ if self.dijkstra is None or self.dijkstra[0] != source:
+ self._generate_dijkstra(source)
+
+ assert self.dijkstra
+ path = []
+ node = dest
+ while node != source:
+ path.append(node)
+ node = self.dijkstra[1].get(node, None)
+ if node is None:
+ return (None, [])
+ path.append(source)
+ path = path[::-1]
+
+ cost: Numeric = 0
+ for (a, b) in list_utils.ngrams(path, 2):
+ cost += self.graph[a][b]
+ return (cost, path)
+