From 6868e089a47f924fd52a5c5e16d8787419caf9f0 Mon Sep 17 00:00:00 2001 From: Oliver Hofmann Date: Tue, 3 Jun 2025 22:48:08 +0200 Subject: [PATCH] rendeer maze --- data/aoc2416test.txt | 15 ++++++++ praktika/09_aoc/rendeer.py | 59 ++++++++++++++++++++++++++++++++ utils/priority_queue.py | 40 ++++++++++++++++++++++ vorlesung/L08_graphen/aoc2212.py | 45 ++++++++++++++++++++++++ vorlesung/L08_graphen/graph.py | 58 +++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 data/aoc2416test.txt create mode 100644 praktika/09_aoc/rendeer.py create mode 100644 utils/priority_queue.py create mode 100644 vorlesung/L08_graphen/aoc2212.py 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/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/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 index 9b93b39..9f484ae 100644 --- a/vorlesung/L08_graphen/graph.py +++ b/vorlesung/L08_graphen/graph.py @@ -4,6 +4,7 @@ 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): @@ -18,6 +19,13 @@ class Vertex: 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.""" @@ -147,6 +155,56 @@ class Graph: 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."""