|
|
@@ -16,12 +16,15 @@ class Vertex: |
|
|
|
def __init__(self, value): |
|
|
|
self.value = value |
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
return str(self.value) |
|
|
|
|
|
|
|
class Graph: |
|
|
|
"""A graph.""" |
|
|
|
def insert_vertex(self, name: str): |
|
|
|
raise NotImplementedError("Please implement this method in subclass") |
|
|
|
|
|
|
|
def connect(self, name1: str, name2: str): |
|
|
|
def connect(self, name1: str, name2: str, weight: float = 1): |
|
|
|
raise NotImplementedError("Please implement this method in subclass") |
|
|
|
|
|
|
|
def all_vertices(self) -> List[Vertex]: |
|
|
@@ -33,6 +36,10 @@ class Graph: |
|
|
|
def get_adjacent_vertices(self, name: str) -> List[Vertex]: |
|
|
|
raise NotImplementedError("Please implement this method in subclass") |
|
|
|
|
|
|
|
def get_adjacent_vertices_with_weight(self, name: str) -> List[tuple[Vertex, float]]: |
|
|
|
raise NotImplementedError("Please implement this method in subclass") |
|
|
|
|
|
|
|
|
|
|
|
def bfs(self, start_name: str): |
|
|
|
""" |
|
|
|
Perform a breadth-first search starting at the given vertex. |
|
|
@@ -74,6 +81,7 @@ class Graph: |
|
|
|
# Return the distance and predecessor maps |
|
|
|
return distance_map, predecessor_map |
|
|
|
|
|
|
|
|
|
|
|
def path(self, destination, map): |
|
|
|
""" |
|
|
|
Compute the path from the start vertex to the given destination vertex. |
|
|
@@ -100,10 +108,10 @@ class AdjacencyListGraph(Graph): |
|
|
|
if name not in self.adjacency_map: |
|
|
|
self.adjacency_map[name] = [] |
|
|
|
|
|
|
|
def connect(self, name1: str, name2: str): |
|
|
|
def connect(self, name1: str, name2: str, weight: float = 1): |
|
|
|
adjacency_list = self.adjacency_map[name1] |
|
|
|
dest = self.vertex_map[name2] |
|
|
|
adjacency_list.append(dest) |
|
|
|
adjacency_list.append((dest, weight)) |
|
|
|
|
|
|
|
def all_vertices(self) -> List[Vertex]: |
|
|
|
return list(self.vertex_map.values()) |
|
|
@@ -112,8 +120,14 @@ class AdjacencyListGraph(Graph): |
|
|
|
return self.vertex_map[name] |
|
|
|
|
|
|
|
def get_adjacent_vertices(self, name: str) -> List[Vertex]: |
|
|
|
return list(map(lambda x: x[0], self.adjacency_map[name])) |
|
|
|
|
|
|
|
def get_adjacent_vertices_with_weight(self, name: str) -> List[tuple[Vertex, float]]: |
|
|
|
return self.adjacency_map[name] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AdjacencyMatrixGraph(Graph): |
|
|
|
"""A graph implemented as an adjacency matrix.""" |
|
|
|
def __init__(self): |
|
|
@@ -126,13 +140,13 @@ class AdjacencyMatrixGraph(Graph): |
|
|
|
self.index_map[name] = len(self.vertex_list) |
|
|
|
self.vertex_list.append(Vertex(name)) |
|
|
|
for row in self.adjacency_matrix: # add a new column to each row |
|
|
|
row.append(0) |
|
|
|
self.adjacency_matrix.append([0] * len(self.vertex_list)) # add a new row |
|
|
|
row.append(None) |
|
|
|
self.adjacency_matrix.append([None] * len(self.vertex_list)) # add a new row |
|
|
|
|
|
|
|
def connect(self, name1: str, name2: str): |
|
|
|
def connect(self, name1: str, name2: str, weight: float = 1): |
|
|
|
index1 = self.index_map[name1] |
|
|
|
index2 = self.index_map[name2] |
|
|
|
self.adjacency_matrix[index1][index2] = 1 |
|
|
|
self.adjacency_matrix[index1][index2] = weight |
|
|
|
|
|
|
|
def all_vertices(self) -> List[Vertex]: |
|
|
|
return self.vertex_list |
|
|
@@ -145,31 +159,46 @@ class AdjacencyMatrixGraph(Graph): |
|
|
|
index = self.index_map[name] |
|
|
|
result = [] |
|
|
|
for i in range(len(self.vertex_list)): |
|
|
|
if self.adjacency_matrix[index][i] == 1: |
|
|
|
if self.adjacency_matrix[index][i] is not None: |
|
|
|
name = self.vertex_list[i].value |
|
|
|
result.append(self.get_vertex(name)) |
|
|
|
return result |
|
|
|
|
|
|
|
def read_cave_into_graph(graph: Graph, filename: str): |
|
|
|
"""Read a cave description from a file and insert it into the given graph.""" |
|
|
|
with open(filename, "r") as file: |
|
|
|
lines = file.readlines() |
|
|
|
for line in lines: |
|
|
|
# match a line with two node names and an optional direction |
|
|
|
m = re.match(r"(^\s*\"(.*)\"\s*([<>]*)\s*\"(.*)\"\s*)", line) |
|
|
|
if m: |
|
|
|
startnode = m.group(2) |
|
|
|
endnode = m.group(4) |
|
|
|
opcode = m.group(3) |
|
|
|
graph.insert_vertex(startnode) |
|
|
|
graph.insert_vertex(endnode) |
|
|
|
if '>' in opcode: |
|
|
|
graph.connect(startnode, endnode) |
|
|
|
if '<' in opcode: |
|
|
|
graph.connect(endnode, startnode) |
|
|
|
def get_adjacent_vertices_with_weight(self, name: str) -> List[tuple[Vertex, float]]: |
|
|
|
index = self.index_map[name] |
|
|
|
result = [] |
|
|
|
for i in range(len(self.vertex_list)): |
|
|
|
if self.adjacency_matrix[index][i] is not None: |
|
|
|
name = self.vertex_list[i].value |
|
|
|
result.append((self.get_vertex(name), self.adjacency_matrix[index][i])) |
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
|
|
def read_cave_into_graph(graph: Graph, filename: str): |
|
|
|
"""Read a cave description from a file and insert it into the given graph.""" |
|
|
|
with open(filename, "r") as file: |
|
|
|
lines = file.readlines() |
|
|
|
for line in lines: |
|
|
|
# match a line with two node names and an optional direction |
|
|
|
m = re.match(r"(^\s*\"(.*)\"\s*([<>]*)\s*\"(.*)\"\s*)", line) |
|
|
|
if m: |
|
|
|
startnode = m.group(2) |
|
|
|
endnode = m.group(4) |
|
|
|
opcode = m.group(3) |
|
|
|
graph.insert_vertex(startnode) |
|
|
|
graph.insert_vertex(endnode) |
|
|
|
if '>' in opcode: |
|
|
|
graph.connect(startnode, endnode) |
|
|
|
if '<' in opcode: |
|
|
|
graph.connect(endnode, startnode) |
|
|
|
|
|
|
|
|
|
|
|
graph = AdjacencyListGraph() |
|
|
|
#graph = AdjacencyMatrixGraph() |
|
|
|
read_cave_into_graph(graph, "../../hoehle.txt") |
|
|
@@ -179,3 +208,4 @@ if __name__ == "__main__": |
|
|
|
_, predecessor_map = graph.bfs('Schatzkammer') |
|
|
|
path = graph.path('Höhleneingang', predecessor_map) |
|
|
|
print(path) |
|
|
|
|