AoC 2024 day 8
authorPat Thoyts <pat.thoyts@gmail.com>
Sun, 8 Dec 2024 17:10:03 +0000 (17:10 +0000)
committerPat Thoyts <pat.thoyts@gmail.com>
Sun, 8 Dec 2024 17:10:03 +0000 (17:10 +0000)
day8/__init__.py [new file with mode: 0644]
day8/__main__.py [new file with mode: 0644]

diff --git a/day8/__init__.py b/day8/__init__.py
new file mode 100644 (file)
index 0000000..89a67b0
--- /dev/null
@@ -0,0 +1,84 @@
+from dataclasses import dataclass
+from typing import List, Tuple
+from collections import defaultdict
+from copy import copy, deepcopy
+
+
+map_width = 0
+map_height = 0
+
+
+class Position:
+    row: int
+    col: int
+
+    def __init__(self, row: int, col: int):
+        self.row = row
+        self.col = col
+
+    def __repr__(self) -> str:
+        return f"({self.row},{self.col})"
+
+    def __hash__(self):
+        return hash((self.row, self.col))
+
+    def is_valid(self) -> bool:
+        global map_height, map_width
+        return self.row >= 0 and self.row < map_height \
+            and self.col >= 0 and self.col < map_width
+
+    def antinodes_for(self, other: 'Position', part2: bool = False):
+        dr = self.row - other.row
+        dc = self.col - other.col
+        n = 1
+        while True:
+            valid = 0
+            a = Position(self.row + (n * dr), self.col + (n * dc))
+            if a.is_valid():
+                valid += 1
+                yield a
+            b = Position(other.row - (n * dr), other.col - (n * dc))
+            if b.is_valid():
+                valid += 1
+                yield b
+            if not part2:
+                break
+            if valid == 0:
+                break
+            n += 1
+        if part2:
+            yield self
+            yield other
+
+@dataclass
+class Problem:
+    data: List[str]
+    towers: defaultdict
+
+    def run(self, part2: bool = False, debug: bool = False) -> int:
+        newdata = deepcopy(self.data)
+        for tower in self.towers:
+            for ndx, node in enumerate(self.towers[tower]):
+                others = copy(self.towers[tower])
+                del others[ndx]
+                for other in others:
+                    for anti in node.antinodes_for(other, part2):
+                        newdata[anti.row][anti.col] = '#'
+        if debug:
+            for line in newdata:
+                print("".join(line))
+        res = "".join(["".join(x) for x in newdata])
+        return sum([1 for x in res if x == '#'])
+
+    @staticmethod
+    def from_stream(stream) -> 'Problem':
+        towers = defaultdict(list)
+        data = [list(line.strip()) for line in stream]
+        global map_width, map_height
+        map_height = len(data)
+        map_width = len(data[0])
+        for row, cells in enumerate(data):
+            for col, cell in enumerate(cells):
+                if cell != '.':
+                    towers[cell].append(Position(row, col))
+        return Problem(data, towers)
diff --git a/day8/__main__.py b/day8/__main__.py
new file mode 100644 (file)
index 0000000..41da195
--- /dev/null
@@ -0,0 +1,25 @@
+import sys
+import argparse
+from . import Problem
+
+
+def main(args=None):
+    parser = argparse.ArgumentParser(description="AOC 2024 day 8")
+    parser.add_argument('filename', type=str)
+    parser.add_argument('-d', '--debug', action='store_true')
+    parser.add_argument('-2', '--part2', action='store_true')
+    options = parser.parse_args(args)
+
+    with open(options.filename) as f:
+        problem = Problem.from_stream(f)
+    if options.debug:
+        for row in problem.data:
+            print("".join(row))
+        print()
+
+    print(f"result {problem.run(part2=options.part2, debug=options.debug)}")
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[1:]))