Skip to content

Commit 6eb265d

Browse files
committed
Feat Lux
0 parents  commit 6eb265d

File tree

3 files changed

+253
-0
lines changed

3 files changed

+253
-0
lines changed

a_star.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import collections
2+
import heapq
3+
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+
class DijkstraHeap(list):
19+
"""
20+
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.
24+
25+
This class will have three main elements:
26+
27+
- A heap that will act as a priority 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
30+
"""
31+
def __init__(self, first_node = None):
32+
self.visited = dict()
33+
self.costs = dict()
34+
35+
if first_node is not None:
36+
self.insert(first_node)
37+
38+
def insert(self, element):
39+
"""
40+
Insert an element into the Dijkstra Heap.
41+
42+
:param element: A Node object.
43+
:return: None
44+
"""
45+
46+
if element.value not in self.visited:
47+
heapq.heappush(self,element)
48+
49+
def pop(self):
50+
"""
51+
Pop an element from the Dijkstra Heap, adding it to the visited and cost dicts.
52+
53+
:return: A Node object
54+
"""
55+
56+
while self and self[0].value in self.visited:
57+
heapq.heappop(self)
58+
59+
next_elem = heapq.heappop(self)
60+
self.visited[next_elem.value] = next_elem.came_from
61+
self.costs[next_elem.value] = next_elem.priority
62+
return next_elem
63+
64+
65+
66+
67+
Node = collections.namedtuple("Node","priority value came_from")
68+
69+
def a_star(graph, start, end):
70+
"""
71+
Calculates the shortest path from start to end.
72+
73+
:param graph: A graph object. The graph object can be anything that implements the following methods:
74+
75+
graph.neighbors( (x:int, y:int) ) : Iterable( (x:int,y:int), (x:int,y:int), ...)
76+
graph.cost( (x:int,y:int) ) : int
77+
78+
:param start: Tuple of two ints representing the starting point.
79+
:param end: Tuple of two ints representing the ending point.
80+
:returns: A DijkstraHeap object.
81+
82+
"""
83+
84+
frontier = DijkstraHeap( Node(0, start, None) )
85+
86+
while frontier:
87+
88+
current_node = frontier.pop()
89+
90+
if current_node.value == end:
91+
return frontier
92+
93+
for neighbor in graph.neighbors( current_node.value ):
94+
95+
new_cost = ( current_node.priority
96+
+ graph.cost(current_node.value, neighbor)
97+
+ heuristic( current_node.value, neighbor) )
98+
99+
new_node = Node(new_cost, neighbor, current_node.value)
100+
101+
frontier.insert(new_node)
102+

doc/sample.png

30.7 KB
Loading

test.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
2+
class SquareGrid():
3+
4+
def __init__(self, width, height):
5+
self.width = width
6+
self.height = height
7+
self.walls = set()
8+
9+
def is_inside(self, point):
10+
""" Helper function to know if a given point is inside the Grid.
11+
12+
:param point: Tuple of two ints
13+
:return: Boolean
14+
"""
15+
x, y = point
16+
return 0 <= x < self.width and 0 <= y < self.height
17+
18+
def is_wall(self, point):
19+
""" Helper function to know if a given point is a wall.
20+
21+
:param point: Tuple of two ints
22+
:return: Boolean
23+
"""
24+
return point not in self.walls
25+
26+
def neighbors(self, point):
27+
""" Yields the valid neighbours of a given point.
28+
29+
:param point: Tuple of two ints
30+
:return: Generator of tuples of ints
31+
"""
32+
33+
x, y = point
34+
candidates = [(x + 1, y), (x, y - 1), (x - 1, y), (x, y + 1)]
35+
candidates = filter(self.is_inside, candidates)
36+
candidates = filter(self.is_wall, candidates)
37+
yield from candidates
38+
39+
def _draw_tile(self, point, style, width):
40+
""" Returns a symbol for the current point given the style dictionary and the drawing width.
41+
42+
:param point: Tuple of two ints
43+
:param style: Hash map of the form (int,int) : int or (int,int) : (int,int)
44+
:param width: Integer representing the width of the graph
45+
:return: string (character)
46+
"""
47+
48+
character = "."
49+
if 'number' in style and point in style['number']:
50+
character = "%d" % style['number'][point]
51+
if 'point_to' in style and style['point_to'].get(point, None) is not None:
52+
(x1, y1) = point
53+
(x2, y2) = style['point_to'][point]
54+
if x2 == x1 + 1:
55+
character = "\u2192"
56+
if x2 == x1 - 1:
57+
character = "\u2190"
58+
if y2 == y1 + 1:
59+
character = "\u2193"
60+
if y2 == y1 - 1:
61+
character = "\u2191"
62+
if 'start' in style and point == style['start']:
63+
character = "S"
64+
if 'goal' in style and point == style['goal']:
65+
character = "E"
66+
if 'path' in style and point in style['path']:
67+
character = "@"
68+
if point in self.walls:
69+
character = "#" * width
70+
return character
71+
72+
def draw(self, width=2, **style):
73+
"""
74+
Draws the grid given the style dictionary and the width of the drawin.
75+
:param style: Hash map of the form (int,int) : int or (int,int) : (int,int)
76+
:param width: Integer representing the width of the graph
77+
:return: None
78+
"""
79+
for y in range(self.height):
80+
for x in range(self.width):
81+
print("%%-%ds" % width %
82+
self._draw_tile((x, y), style, width), end="")
83+
print()
84+
85+
86+
class GridWithWeights(SquareGrid):
87+
88+
def __init__(self, width, height):
89+
super().__init__(width, height)
90+
self.weights = {}
91+
92+
def cost(self, from_node, to_node):
93+
"""
94+
Gives the cost of going from from_node to to_node.
95+
96+
:param from_node: Tuple of two ints
97+
:param to_node: Tuple of two ints
98+
:returns: int
99+
"""
100+
return self.weights.get(to_node, 1)
101+
102+
103+
def from_id_width(point, width):
104+
"""
105+
Helper function to truncate a int to a point in a grid.
106+
107+
:param point: integer
108+
:param width: integer
109+
:returns: Tuple of two ints
110+
"""
111+
return (point % width, point // width)
112+
113+
if __name__ == '__main__':
114+
115+
import a_star
116+
import random
117+
118+
# Construct a cool wall collection from this aparently arbitraty points.
119+
WALLS = [from_id_width(point, width=30) for point in [21, 22, 51, 52, 81, 82, 93, 94, 111, 112,
120+
123, 124, 133, 134, 141, 142, 153, 154, 163,
121+
164, 171, 172, 173, 174, 175,183, 184, 193,
122+
194, 201, 202, 203, 204, 205, 213, 214, 223,
123+
224, 243, 244, 253, 254, 273, 274, 283, 284,
124+
303, 304, 313, 314, 333, 334, 343, 344, 373,
125+
374, 403, 404, 433, 434]]
126+
127+
# Instantiate a grid of 10 x 10
128+
graph = GridWithWeights(10, 10)
129+
130+
# Set the walls of the grid
131+
graph.walls = set(WALLS)
132+
133+
# Set the weighs of some points in the maze
134+
graph.weights = {location: random.randint(1,10) for location in [(3, 4), (3, 5), (4, 1), (4, 2),
135+
(4, 3), (4, 4), (4, 5), (4, 6),
136+
(4, 7), (4, 8), (5, 1), (5, 2),
137+
(5, 3), (5, 4), (5, 5), (5, 6),
138+
(5, 7), (5, 8), (6, 2), (6, 3),
139+
(6, 4), (6, 5), (6, 6), (6, 7),
140+
(7, 3), (7, 4), (7, 5)]}
141+
142+
# Call the A* algorithm and get the frontier
143+
frontier = a_star.a_star(graph = graph, start=(1, 4), end=(7, 8))
144+
145+
# Print the results
146+
147+
graph.draw(width=5, point_to = frontier.visited, start=(1, 4), goal=(7, 8))
148+
149+
print()
150+
151+
graph.draw(width=5, number = frontier.costs, start=(1, 4), goal=(7, 8))

0 commit comments

Comments
 (0)