diff --git a/data/aoc2212test.txt b/data/aoc2212test.txt new file mode 100644 index 0000000..86e9cac --- /dev/null +++ b/data/aoc2212test.txt @@ -0,0 +1,5 @@ +Sabqponm +abcryxxl +accszExk +acctuvwj +abdefghi diff --git a/data/aoc2416test.txt b/data/aoc2416test.txt new file mode 100644 index 0000000..6a5bb85 --- /dev/null +++ b/data/aoc2416test.txt @@ -0,0 +1,15 @@ +############### +#.......#....E# +#.#.###.#.###.# +#.....#.#...#.# +#.###.#####.#.# +#.#.#.......#.# +#.#.#####.###.# +#...........#.# +###.#.#####.#.# +#...#.....#.#.# +#.#.#.###.#.#.# +#.....#...#.#.# +#.###.#.#.#.#.# +#S..#.....#...# +############### \ No newline at end of file diff --git a/data/elektro.txt b/data/elektro.txt new file mode 100644 index 0000000..1b0978e --- /dev/null +++ b/data/elektro.txt @@ -0,0 +1,8 @@ +"Höhleneingang";"Ost/West-Passage";5 +"Höhleneingang";"Nord/Süd-Passage";3 +"Nord/Süd-Passage";"Nebelraum";7 +"Steiniger Pfad";"Ost/West-Passage";2 +"Ost/West-Passage";"Schwefelgewölbe";4 +"Schwefelgewölbe";"Steiniger Pfad";1 +"Schatzkammer";"Nebelraum";2 +"Steiniger Pfad";"Schatzkammer";6 diff --git a/data/labyrinth.txt b/data/labyrinth.txt new file mode 100644 index 0000000..796bb65 --- /dev/null +++ b/data/labyrinth.txt @@ -0,0 +1,9 @@ +xxxxxxxxxxxxxxxxxxxxx +x x +x S x +x x +x xxxxxxxx x +x x +x x +x A x +xxxxxxxxxxxxxxxxxxxxx \ No newline at end of file diff --git a/data/labyrinth1.txt b/data/labyrinth1.txt new file mode 100644 index 0000000..dc47eaa --- /dev/null +++ b/data/labyrinth1.txt @@ -0,0 +1,5 @@ +xxxxxAxxxxxxxxx +x xSx +xxxxxxxxxx xx x +x x +xxxxxxxxxxxxxxx \ No newline at end of file diff --git a/praktika/07_hashtable/praktikum.py b/praktika/07_hashtable/praktikum.py index 883d094..35889fd 100644 --- a/praktika/07_hashtable/praktikum.py +++ b/praktika/07_hashtable/praktikum.py @@ -34,27 +34,9 @@ def fs(x: MemoryCell, i: Literal, m: Literal) -> Literal: return position % m -class TestHashTableOpenAddressing(unittest.TestCase): - - def test_hash_function(self): - x = MemoryCell(22) - m = Literal(20) - self.assertEqual(11, h(x, m).value) - - def test_probe_function(self): - x = MemoryCell(22) - i = Literal(0) - m = Literal(20) - self.assertEqual(11, f(x, i, m).value) - i = Literal(1) - self.assertEqual(17, f(x, i, m).value) - i = Literal(2) - self.assertEqual(13, f(x, i, m).value) - if __name__ == "__main__": - #unittest.main() print("*** Aufgabe 3 ***") size = Literal(20) diff --git a/praktika/08_graph/schatz.py b/praktika/08_graph/schatz.py new file mode 100644 index 0000000..c709144 --- /dev/null +++ b/praktika/08_graph/schatz.py @@ -0,0 +1,37 @@ +import re +from utils.project_dir import get_path +from vorlesung.L08_graphen.graph import Graph, AdjacencyListGraph, AdjacencyMatrixGraph + + +def read_cave_into_graph(graph: Graph, filename: str): + """Read a cave description from a file and insert it into the given graph.""" + filename = get_path(filename) + 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, "data/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) +graph.graph("Höhle") + + diff --git a/praktika/09_aoc/rendeer.py b/praktika/09_aoc/rendeer.py new file mode 100644 index 0000000..2f777cb --- /dev/null +++ b/praktika/09_aoc/rendeer.py @@ -0,0 +1,59 @@ +from utils.project_dir import get_path +from vorlesung.L08_graphen.graph import AdjacencyListGraph + +DIRECTIONS = [(1,0), (0, 1), (-1, 0), (0, -1)] + +class D16Solution: + + def __init__(self): + with open(get_path("data/aoc2416.txt"), "r") as file: + self.puzzle = [line.strip() for line in file] + self.cells = set() + self.start = None + self.end = None + + def get_cells_start_end(self): + for row in range(len(self.puzzle)): + for col in range(len(self.puzzle[row])): + if self.puzzle[row][col] != '#': + self.cells.add((col, row)) + if self.puzzle[row][col] == 'S': + self.start = (col, row) + if self.puzzle[row][col] == 'E': + self.end = (col, row) + + def get_label(self, cell, direction): + """Generate a label for a cell based on its coordinates and direction.""" + return f"{cell[0]},{cell[1]}_{direction}" + + def create_graph(self): + graph = AdjacencyListGraph() + for cell in self.cells: + for direction in DIRECTIONS: + label = self.get_label(cell, direction) + graph.insert_vertex(label) + for cell in self.cells: + for d, direction in enumerate(DIRECTIONS): + label = self.get_label(cell, direction) + dx, dy = direction + neighbor = (cell[0] + dx, cell[1] + dy) + if neighbor in self.cells: + graph.connect(label, self.get_label(neighbor, direction)) + direction_left = DIRECTIONS[(d - 1) % 4] + direction_right = DIRECTIONS[(d + 1) % 4] + graph.connect(label, self.get_label(cell, direction_left), 1000) + graph.connect(label, self.get_label(cell, direction_right), 1000) + return graph + +if __name__ == "__main__": + solution = D16Solution() + solution.get_cells_start_end() + graph = solution.create_graph() + start = solution.get_label(solution.start, DIRECTIONS[0]) + print(f"Start: {start}") + distance_map, predecessor_map = graph.dijkstra(start) + end_labels = [solution.get_label(solution.end, direction) for direction in DIRECTIONS] + end_vertices = [graph.get_vertex(label) for label in end_labels] + min_weight = min([distance_map[vertex] for vertex in end_vertices]) + print(f"Minimum distance to End {solution.end}: {min_weight}") + diff --git a/utils/priority_queue.py b/utils/priority_queue.py new file mode 100644 index 0000000..7b377bf --- /dev/null +++ b/utils/priority_queue.py @@ -0,0 +1,40 @@ +import heapq + +class PriorityQueue: + def __init__(self): + self.heap = [] + self.entry_finder = {} # map: item -> [priority, item] + self.REMOVED = '' + self.counter = 0 # unique sequence count to break ties + + def add_or_update(self, item, priority): + if item in self.entry_finder: + self.remove(item) + count = self.counter + entry = [priority, count, item] + self.entry_finder[item] = entry + heapq.heappush(self.heap, entry) + self.counter += 1 + + def remove(self, item): + entry = self.entry_finder.pop(item) + entry[-1] = self.REMOVED # mark as removed + + def pop(self): + while self.heap: + priority, count, item = heapq.heappop(self.heap) + if item != self.REMOVED: + del self.entry_finder[item] + return item, priority + return None + +if __name__ == "__main__": + pq = PriorityQueue() + pq.add_or_update('task1', 1) + pq.add_or_update('task2', float('inf')) + pq.add_or_update('task3', float('inf')) + + print(pq.pop()) # Should print ('task1', 1) + pq.add_or_update('task2', 0) # Update priority of 'task1' + print(pq.pop()) # Should print ('task2', 0) + print(pq.pop()) # Should print ('task3', 3) \ No newline at end of file diff --git a/vorlesung/L05_binaere_baeume/bin_tree.py b/vorlesung/L05_binaere_baeume/bin_tree.py index 855a1fb..e0b83cf 100644 --- a/vorlesung/L05_binaere_baeume/bin_tree.py +++ b/vorlesung/L05_binaere_baeume/bin_tree.py @@ -1,6 +1,4 @@ from vorlesung.L05_binaere_baeume.bin_tree_node import BinaryTreeNode -from utils.memory_manager import MemoryManager -from utils.memory_array import MemoryArray from utils.project_dir import get_path from datetime import datetime import graphviz diff --git a/vorlesung/L08_graphen/__init__.py b/vorlesung/L08_graphen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vorlesung/L08_graphen/aoc2212.py b/vorlesung/L08_graphen/aoc2212.py new file mode 100644 index 0000000..0c1147b --- /dev/null +++ b/vorlesung/L08_graphen/aoc2212.py @@ -0,0 +1,45 @@ +from vorlesung.L08_graphen.graph import Graph, AdjacencyMatrixGraph +from utils.project_dir import get_path + +graph = AdjacencyMatrixGraph() +start = "" +end = "" + +def read_file(filename: str = "data/aoc2212.txt"): + """Read a file and return the content as a string.""" + + def adjust_char(char): + """Adjust character for comparison.""" + if char == 'S': + return 'a' + elif char == 'E': + return 'z' + return char + + global start, end + with open(get_path(filename), "r") as file: + quest = file.read().strip().splitlines() + for row, line in enumerate(quest): + for col, char in enumerate(line): + label = f"{row},{col}" + graph.insert_vertex(label) + if char == "S": + start = label + if char == "E": + end = label + for row, line in enumerate(quest): + for col, char in enumerate(line): + for neighbor in [(row - 1, col), (row, col - 1), (row + 1, col), (row, col + 1)]: + if 0 <= neighbor[0] < len(quest) and 0 <= neighbor[1] < len(line): + if ord(adjust_char(quest[neighbor[0]][neighbor[1]])) <= ord(adjust_char(char)) + 1: + label1 = f"{row},{col}" + label2 = f"{neighbor[0]},{neighbor[1]}" + graph.connect(label1, label2) + + +# Lösung des Adventskalenders 2022, Tag 12 +read_file("data/aoc2212test.txt") +graph.graph() +distance_map, predecessor_map = graph.bfs(start) +print(distance_map[graph.get_vertex(end)]) +print(graph.path(end, predecessor_map)) \ No newline at end of file diff --git a/vorlesung/L08_graphen/graph.py b/vorlesung/L08_graphen/graph.py new file mode 100644 index 0000000..9f484ae --- /dev/null +++ b/vorlesung/L08_graphen/graph.py @@ -0,0 +1,303 @@ +from collections import deque +from typing import List +from enum import Enum +import graphviz +from datetime import datetime +from utils.project_dir import get_path +from utils.priority_queue import PriorityQueue + + +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 __str__(self): + return str(self.value) + + def __repr__(self): + return f"Vertex({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 all_edges(self) -> List[tuple[str, str, 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 dfs(self): + """ + Perform a depth-first search starting at the first vertex. + :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 : dict[Vertex, NodeColor]= {} + enter_map : dict[Vertex, int] = {} + leave_map : dict[Vertex, int] = {} + predecessor_map : dict[Vertex, Vertex | None] = {} + white_vertices = set(self.all_vertices()) + time_counter = 0 + + def dfs_visit(vertex): + nonlocal time_counter + color_map[vertex] = NodeColor.GRAY + white_vertices.remove(vertex) + time_counter += 1 + enter_map[vertex] = time_counter + for dest in self.get_adjacent_vertices(vertex.value): + if color_map[dest] == NodeColor.WHITE: + predecessor_map[dest] = vertex + dfs_visit(dest) + color_map[vertex] = NodeColor.BLACK + time_counter += 1 + leave_map[vertex] = time_counter + + # Initialize the maps + for vertex in self.all_vertices(): + color_map[vertex] = NodeColor.WHITE + predecessor_map[vertex] = None + + while white_vertices: + v = white_vertices.pop() + dfs_visit(v) + + return enter_map, leave_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 + + def graph(self, filename: str = "Graph"): + dot = graphviz.Digraph( name=filename, + node_attr={"fontname": "Arial"}, + format="pdf" ) + for vertex in self.all_vertices(): + dot.node(str(id(vertex)), label=str(vertex.value)) + for edge in self.all_edges(): + dot.edge(str(id(self.get_vertex(edge[0]))), str(id(self.get_vertex(edge[1]))), label=str(edge[2])) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"{filename}_{timestamp}.gv" + filename = get_path(filename) + dot.render(filename) + + def dijkstra(self, start_name: str) -> tuple[dict[Vertex, float], dict[Vertex, Vertex | None]]: + """ + Führt den Dijkstra-Algorithmus für kürzeste Pfade durch, implementiert mit Knotenfarben. + + Args: + start_name: Name des Startknotens + + Returns: + Ein Tupel aus zwei Dictionaries: + - distance_map: Abbildung von Knoten auf ihre kürzeste Distanz vom Startknoten + - predecessor_map: Abbildung von Knoten auf ihre Vorgänger im kürzesten Pfad + """ + + def relax(vertex, dest, weight): + """ + Entspannt die Kante zwischen vertex und dest. + Aktualisiert die Distanz und den Vorgänger, wenn ein kürzerer Pfad gefunden wird. + """ + if distance_map[vertex] + weight < distance_map[dest]: + distance_map[dest] = distance_map[vertex] + weight + predecessor_map[dest] = vertex + queue.add_or_update(dest, distance_map[dest]) + + # Initialisierung der Maps + distance_map = {} # Speichert kürzeste Distanzen + predecessor_map = {} # Speichert Vorgänger + + # Initialisiere alle Knoten + queue = PriorityQueue() + for vertex in self.all_vertices(): + distance_map[vertex] = float('inf') # Initiale Distanz unendlich + predecessor_map[vertex] = None # Initialer Vorgänger None + queue.add_or_update(vertex, distance_map[vertex]) # Füge Knoten zur Prioritätswarteschlange hinzu + + + + # Setze Startknoten + start_node = self.get_vertex(start_name) + distance_map[start_node] = 0 + queue.add_or_update(start_node, distance_map[start_node]) + + while True: + entry = queue.pop() + if entry is None: + break + vertex = entry[0] + for dest, weight in self.get_adjacent_vertices_with_weight(vertex.value): + relax(vertex, dest, weight) + return distance_map, predecessor_map + + +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] + + def all_edges(self) -> List[tuple[str, str, float]]: + result = [] + for name in self.adjacency_map: + for (dest, weight) in self.adjacency_map[name]: + result.append((name, dest.value, weight)) + return result + + + + +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 + + def all_edges(self) -> List[tuple[str, str, float]]: + result = [] + for i in range(len(self.vertex_list)): + for j in range(len(self.vertex_list)): + if self.adjacency_matrix[i][j] is not None: + result.append((self.vertex_list[i].value, self.vertex_list[j].value, self.adjacency_matrix[i][j])) + return result + + +