123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- from collections import deque
- from typing import List
- import re
- from enum import Enum
-
- class NodeColor(Enum):
- """Enumeration for node colors in a graph traversal."""
- WHITE = 1 # WHITE: not visited
- GRAY = 2 # GRAY: visited but not all neighbors visited
- BLACK = 3 # BLACK: visited and all neighbors visited
-
-
-
- class Vertex:
- """A vertex in a graph."""
- 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, weight: float = 1):
- raise NotImplementedError("Please implement this method in subclass")
-
- def all_vertices(self) -> List[Vertex]:
- raise NotImplementedError("Please implement this method in subclass")
-
- def get_vertex(self, name: str) -> Vertex:
- raise NotImplementedError("Please implement this method in subclass")
-
- 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.
- :param start_name: the name of the vertex to start at
- :return: a tuple of two dictionaries, the first mapping vertices to distances from the start vertex,
- the second mapping vertices to their predecessors in the traversal tree
- """
-
- color_map = {} # maps vertices to their color
- distance_map = {} # maps vertices to their distance from the start vertex
- predecessor_map = {} # maps vertices to their predecessor in the traversal tree
-
- # Initialize the maps
- for vertex in self.all_vertices():
- color_map[vertex] = NodeColor.WHITE
- distance_map[vertex] = None
- predecessor_map[vertex] = None
-
- # Start at the given vertex
- start_node = self.get_vertex(start_name)
- color_map[start_node] = NodeColor.GRAY
- distance_map[start_node] = 0
-
- # Initialize the queue with the start vertex
- queue = deque()
- queue.append(start_node)
-
- # Process the queue
- while len(queue) > 0:
- vertex = queue.popleft()
- for dest in self.get_adjacent_vertices(vertex.value):
- if color_map[dest] == NodeColor.WHITE:
- color_map[dest] = NodeColor.GRAY
- distance_map[dest] = distance_map[vertex] + 1
- predecessor_map[dest] = vertex
- queue.append(dest)
- color_map[vertex] = NodeColor.BLACK
-
- # 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.
- The map parameter is the predecessor map
- """
- path = []
- destination_node = self.get_vertex(destination)
- while destination_node is not None:
- path.insert(0, destination_node.value)
- destination_node = map[destination_node]
- return path
-
-
-
- class AdjacencyListGraph(Graph):
- """A graph implemented as an adjacency list."""
- def __init__(self):
- self.adjacency_map = {} # maps vertex names to lists of adjacent vertices
- self.vertex_map = {} # maps vertex names to vertices
-
- def insert_vertex(self, name: str):
- if name not in self.vertex_map:
- self.vertex_map[name] = Vertex(name)
- if name not in self.adjacency_map:
- self.adjacency_map[name] = []
-
- 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, weight))
-
- def all_vertices(self) -> List[Vertex]:
- return list(self.vertex_map.values())
-
- def get_vertex(self, name: str) -> Vertex:
- 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):
- self.index_map = {} # maps vertex names to indices
- self.vertex_list = [] # list of vertices
- self.adjacency_matrix = [] # adjacency matrix
-
- def insert_vertex(self, name: str):
- if name not in self.index_map:
- 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(None)
- self.adjacency_matrix.append([None] * len(self.vertex_list)) # add a new row
-
- 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] = weight
-
- def all_vertices(self) -> List[Vertex]:
- return self.vertex_list
-
- def get_vertex(self, name: str) -> Vertex:
- index = self.index_map[name]
- return self.vertex_list[index]
-
- def get_adjacent_vertices(self, name: str) -> List[Vertex]:
- 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))
- return result
-
- 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")
- _, predecessor_map = graph.bfs('Höhleneingang')
- path = graph.path('Schatzkammer', predecessor_map)
- print(path)
- _, predecessor_map = graph.bfs('Schatzkammer')
- path = graph.path('Höhleneingang', predecessor_map)
- print(path)
|