-
Notifications
You must be signed in to change notification settings - Fork 0
/
A_star.py
196 lines (164 loc) · 5.78 KB
/
A_star.py
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
from typing import Any
import numpy as np
import time as tm
from utils.graph import Graph, Vertex
from utils.priority_queue import PriorityQueue
def load_location_data(graph: Graph, filename: str) -> None:
"""
Load location data from a file.
Format: Vertex,XCord|Latitude,YCord|Longitude or modify it to whatever you want
Args:
graph (Graph): graph to be filled with data.
"""
with open(filename, 'r') as file:
for line in file:
v_k, x, y = line.rstrip('\n').split(',')
vertex : Vertex = graph.get_vertex(v_k)
vertex.location = (float(x), float(y))
def build_graph_from_list(data: list) -> Graph:
"""
Given a list of tuples, build a graph
Args:
# Here each vertex is a key, so it can be anything
data (list): list of tuples (vertex1 : Any, vertex2 : Any, weight : int/float)
Returns:
Graph
"""
graph = Graph()
for vk_1, vk_2, weight in data:
graph.add_edge(vk_1, vk_2, weight)
return graph
def load_from_file(filename : str) -> Graph:
"""
Load data from a file to build a Graph
Extension: .txt - .csv
Data format: VertexKey,VertexKey,weight
## Example: Berlin,Amsterdam,655
Args:
filename (str): Name of the file
Returns:
Graph: graph loaded with data.
"""
data : list[tuple] = []
with open(filename, 'r') as file:
for line in file:
vertex1, vertex2, weight = line.rstrip('\n').split(',')
data.append((vertex1, vertex2, int(weight)))
return build_graph_from_list(data)
def load_heuristic_data(graph : Graph, filename : str) -> None:
"""
Loads heuristic data from file to the graph.
Args:
graph (Graph): Graph
filename (str): File name.
"""
with open(filename, 'r') as file:
for line in file:
v_k, heur = line.rstrip('\n').split(',')
vertex = graph.get_vertex(v_k)
vertex.heuristic = int(heur)
def get_path(graph: Graph, to: Any) -> list[Any]:
"""
Get the path from a certain vertex to another.
@Important: Impossible path not checked.
Args:
graph (Graph): Graph where the vertex are contained
to (Any): end vertex
Returns:
list[Any]: path
"""
it : Vertex = graph.get_vertex(to)
path : list[Any] = [it.key]
while it.predecessor:
path.append(it.predecessor.key)
it = it.predecessor
return path[::-1]
def heuristic_value(a: Vertex, end: Vertex):
"""
Choose a proper heuristic value here. For this we are doing a simple Mannhatan distance
Actual example: we just have pre-calculated heuristic data, so we don't need to use two vertex, I keep it this way as
for complex cases we would want to calculate it.
Args:
a (Vertex): Any vertex from the graph
b (Vertex): End point
"""
#loc_diff = end.location - a.location
#return loc_diff[0] + loc_diff[1]
return a.heuristic
def dijkstras_algorithm(graph: Graph, start: Vertex, end: Vertex) -> None:
"""
Dijkstra's Algorithm
Intended Big-O: O( (V + E) * log(V) )
Args:
graph (Graph): graph to run the Dijkstra's Algorithm
start (Vertex): starting vertex
"""
# counter
operations : int = 0
pq : PriorityQueue[int, Vertex] = PriorityQueue(descending = False)
for v in graph:
v.dist = np.Inf
start.dist = 0
pq.build_heap([(v.dist, v) for v in graph])
while not pq.empty():
current : Vertex = pq.advance()
if current == end:
# We're already there
break
for next in current.get_connections():
operations += 1
new_dist = current.dist + current.get_weight(next)
if new_dist < next.dist:
next.dist = new_dist
next.predecessor = current
pq.decrease_key(next, new_dist)
print(operations)
def a_star_algorithm(graph: Graph, start: Vertex, end: Vertex) -> None:
"""
A* Star Algorithm
Args:
graph (Graph): graph to run the A* Algorithm
start (Vertex): starting vertex
"""
# counter
operations : int = 0
pq : PriorityQueue[int, Vertex] = PriorityQueue(descending = False)
for v in graph:
v.dist = np.Inf
start.dist = 0
# this will know which nodes were already added to the queue.
visited : dict[Vertex, bool] = {start: True}
pq.insert((start.dist, start))
while not pq.empty():
current : Vertex = pq.advance()
if current == end:
# We're already there
break
for next in current.get_connections():
operations += 1
# Calculate g:
new_dist = current.dist + current.get_weight(next)
# Calculate f for the priority queue:
f = new_dist + heuristic_value(next, end)
if new_dist < next.dist:
next.dist = new_dist
next.predecessor = current
if next in visited:
pq.decrease_key(next, f)
else:
pq.insert((f, next))
visited[next] = True
print(operations)
if __name__ == '__main__':
graph1 = load_from_file('data.txt')
load_heuristic_data(graph1, 'heuristics.txt')
start = tm.perf_counter()
a_star_algorithm(graph1, graph1.get_vertex('A'), graph1.get_vertex('J'))
end = tm.perf_counter()
print(get_path(graph1, 'J'), 'Time: ', end - start)
graph2 = load_from_file('data.txt')
load_heuristic_data(graph2, 'heuristics.txt')
start = tm.perf_counter()
dijkstras_algorithm(graph2, graph2.get_vertex('A'), graph2.get_vertex('J'))
end = tm.perf_counter()
print(get_path(graph2, 'J'), 'Time: ', end - start)