|
2 | 2 | import heapq
|
3 | 3 |
|
4 | 4 |
|
5 |
| -def heuristic(a, b): |
6 |
| - """ |
7 |
| - The heuristic function of the A* algorithm. In this case the Manhattan distance. |
8 |
| -
|
9 |
| - :param a: Tuple of two ints ( Point A) |
10 |
| - :param b: Tuple of two ints ( Point B) |
11 |
| - :returns: integer ( Distance between A nd B ) |
12 |
| - """ |
13 |
| - (x1, y1) = a |
14 |
| - (x2, y2) = b |
15 |
| - return abs(x1 - x2) + abs(y1 - y2) |
16 |
| - |
17 |
| - |
18 | 5 | class DijkstraHeap(list):
|
19 | 6 | """
|
20 | 7 | An augmented heap for the A* algorithm. This class encapsulated the residual logic of
|
21 |
| - the A* algorithm like for example how to manage elements already visited that remain |
22 |
| - in the heap, elements already visited that are not in the heap and from where we came to |
23 |
| - a visited element. |
| 8 | + the A* algorithm like for example how to manage nodes already visited that remain |
| 9 | + in the heap, nodes already visited that are not in the heap and from where we came to |
| 10 | + a visited node. |
24 | 11 |
|
25 | 12 | This class will have three main elements:
|
26 | 13 |
|
27 | 14 | - A heap that will act as a cost queue (self).
|
28 |
| - - A visited dict that will act as a visited set and as a mapping of the form point:came_from |
29 |
| - - A costs dict that will act as a mapping of the form point:cost_so_far |
| 15 | + - A visited dict that will act as a visited set and as a mapping of the form vertex:node |
30 | 16 | """
|
31 |
| - def __init__(self, first_node = None): |
| 17 | + def __init__(self): |
32 | 18 | self.visited = dict()
|
33 |
| - self.costs = dict() |
34 | 19 |
|
35 |
| - if first_node is not None: |
36 |
| - self.insert(first_node) |
37 |
| - |
38 |
| - def insert(self, element): |
| 20 | + def insert(self, node): |
39 | 21 | """
|
40 |
| - Insert an element into the Dijkstra Heap. |
| 22 | + Insert a node into the Dijkstra Heap. |
41 | 23 |
|
42 |
| - :param element: A Node object. |
| 24 | + :param node: A Node object. |
43 | 25 | :return: None
|
44 | 26 | """
|
45 | 27 |
|
46 |
| - if element.point not in self.visited: |
47 |
| - heapq.heappush(self,element) |
| 28 | + if node.vertex not in self.visited: |
| 29 | + heapq.heappush(self, node) |
48 | 30 |
|
49 | 31 | def pop(self):
|
50 | 32 | """
|
51 |
| - Pop an element from the Dijkstra Heap, adding it to the visited and cost dicts. |
| 33 | + Pop a node from the Dijkstra Heap, adding it to the visited dict. |
52 | 34 |
|
53 | 35 | :return: A Node object
|
54 | 36 | """
|
55 | 37 |
|
56 |
| - while self and self[0].point in self.visited: |
| 38 | + while self and self[0].vertex in self.visited: |
57 | 39 | heapq.heappop(self)
|
58 | 40 |
|
59 | 41 | if self:
|
60 | 42 | next_elem = heapq.heappop(self)
|
61 |
| - self.visited[next_elem.point] = next_elem.came_from |
62 |
| - self.costs[next_elem.point] = next_elem.cost_estimate |
| 43 | + self.visited[next_elem.vertex] = next_elem |
63 | 44 | return next_elem
|
64 | 45 |
|
| 46 | + def backtrack(self, current): |
| 47 | + """ |
| 48 | + Retrieve the backward path, starting from current. |
65 | 49 |
|
| 50 | + :param current: The starting vertex of the backward path |
| 51 | + :return: A generator of vertices; listify and reverse to get forward path |
| 52 | + """ |
| 53 | + while current is not None: |
| 54 | + yield current |
| 55 | + current = self.visited[current].came_from |
66 | 56 |
|
| 57 | +Node = collections.namedtuple("Node", ["cost_estimate", "vertex", "came_from"]) |
67 | 58 |
|
68 |
| -Node = collections.namedtuple("Node","cost_estimate point came_from") |
69 |
| - |
70 |
| -def a_star_search(graph, start, end): |
| 59 | +def a_star_search(graph, start, end, heuristic): |
71 | 60 | """
|
72 | 61 | Calculates the shortest path from start to end.
|
73 | 62 |
|
74 |
| - :param graph: A graph object. The graph object can be anything that implements the following methods: |
| 63 | + :param graph: A graph object. The graph object can be anything that implements the following methods for a vertex of any comparable and hashable type V: |
75 | 64 |
|
76 |
| - graph.neighbors( (x:int, y:int) ) : Iterable( (x:int,y:int), (x:int,y:int), ...) |
77 |
| - graph.cost( (x:int,y:int) ) : int |
| 65 | + graph.neighbors( from_vertex:V ) : Iterable( to_vertex:V, to_vertex:V, ...) |
| 66 | + graph.cost( from_vertex:V, to_vertex:V ) : float |
78 | 67 |
|
79 |
| - :param start: Tuple of two ints representing the starting point. |
80 |
| - :param end: Tuple of two ints representing the ending point. |
| 68 | + :param start: The starting vertex, as type V. |
| 69 | + :param end: The ending vertex, as type V. |
| 70 | + :param heuristic: Heuristic lower-bound cost function taking arguments ( from_vertex:V, end:V ). |
81 | 71 | :returns: A DijkstraHeap object.
|
82 | 72 |
|
83 | 73 | """
|
84 | 74 |
|
85 |
| - frontier = DijkstraHeap( Node(heuristic(start, end), start, None) ) |
| 75 | + frontier = DijkstraHeap() |
| 76 | + frontier.insert( Node(heuristic(start, end), start, None) ) |
86 | 77 |
|
87 | 78 | while True:
|
88 | 79 |
|
89 | 80 | current_node = frontier.pop()
|
90 | 81 |
|
91 | 82 | if not current_node:
|
92 | 83 | raise ValueError("No path from start to end")
|
93 |
| - if current_node.point == end: |
| 84 | + if current_node.vertex == end: |
94 | 85 | return frontier
|
95 | 86 |
|
96 |
| - for neighbor in graph.neighbors( current_node.point ): |
| 87 | + for neighbor in graph.neighbors( current_node.vertex ): |
97 | 88 |
|
98 |
| - cost_so_far = current_node.cost_estimate - heuristic(current_node.point, end) |
| 89 | + cost_so_far = current_node.cost_estimate - heuristic(current_node.vertex, end) |
99 | 90 | new_cost = ( cost_so_far
|
100 |
| - + graph.cost(current_node.point, neighbor) |
| 91 | + + graph.cost(current_node.vertex, neighbor) |
101 | 92 | + heuristic(neighbor, end) )
|
102 | 93 |
|
103 |
| - new_node = Node(new_cost, neighbor, current_node.point) |
| 94 | + new_node = Node(new_cost, neighbor, current_node.vertex) |
104 | 95 |
|
105 | 96 | frontier.insert(new_node)
|
106 | 97 |
|
0 commit comments