From 1b1d5350680b3dee215443143d166424793374d7 Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Thu, 14 Dec 2023 14:25:18 +0000 Subject: day 14 --- 14.py | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 14.py (limited to '14.py') diff --git a/14.py b/14.py new file mode 100644 index 0000000..7047839 --- /dev/null +++ b/14.py @@ -0,0 +1,132 @@ +# pyright: strict +from enum import Enum, auto +from itertools import chain, count +from sys import stdin +from typing import Self, Type + + +class Dir(Enum): + NORTH = auto() + EAST = auto() + SOUTH = auto() + WEST = auto() + + +class Tile(Enum): + AIR = auto() + ROUND = auto() + CUBE = auto() + + @staticmethod + def from_str(s: str) -> "Tile": + match s: + case ".": + return Tile.AIR + case "O": + return Tile.ROUND + case "#": + return Tile.CUBE + case _: + raise ValueError + + def __str__(self) -> str: + match self: + case Tile.AIR: + return "." + case Tile.ROUND: + return "O" + case Tile.CUBE: + return "#" + + +class Platform: + platform: list[Tile] + width: int + height: int + + def __init__(self, state: tuple[tuple[Tile, ...], ...]): + self.platform = list(chain.from_iterable(state)) + self.width = len(state[0]) + self.height = len(state) + + def settle(self, direction: Dir): + if direction in {Dir.NORTH, Dir.SOUTH}: + lines = self.width + line_length = self.height + if direction == Dir.NORTH: + xlate = lambda l, i: l + i * self.width + else: + xlate = lambda l, i: l + (self.height - i - 1) * self.width + else: + lines = self.height + line_length = self.width + if direction == Dir.EAST: + xlate = lambda l, i: l * self.width + i + else: + xlate = lambda l, i: l * self.width + (self.width - i - 1) + + for line in range(lines): + floor = 0 + for i in range(line_length): + pos = xlate(line, i) + if self.platform[pos] == Tile.ROUND: + self.platform[xlate(line, floor)], self.platform[pos] = ( + self.platform[pos], + self.platform[xlate(line, floor)], + ) + floor += 1 + elif self.platform[pos] == Tile.CUBE: + floor = i + 1 + + @property + def load(self) -> int: + return sum( + self.height - y + for y in range(self.height) + for x in range(self.width) + if self.platform[x + self.width * y] == Tile.ROUND + ) + + def cycle(self): + self.settle(Dir.NORTH) + self.settle(Dir.EAST) + self.settle(Dir.SOUTH) + self.settle(Dir.WEST) + + @classmethod + def from_str(cls: Type[Self], s: str) -> Self: + return cls(tuple(tuple(Tile.from_str(c) for c in l) for l in s.split("\n"))) + + def __str__(self) -> str: + return "\n".join( + "".join(map(str, self.platform[p : p + self.width])) + for p in range(0, self.width * self.height, self.width) + ) + + def __hash__(self) -> int: + return hash((self.width, self.height) + tuple(self.platform)) + + +p = Platform.from_str(stdin.read().rstrip()) + +past: dict[int, int] = dict() +past[hash(p)] = 0 + +p.settle(Dir.NORTH) +print(p.load) + +for i in count(1): + p.cycle() + h = hash(p) + if h in past: + cycle_start = past[h] + cycle_length = i - past[h] + break + past[hash(p)] = i +else: + raise ValueError + +for i in range((1000000000 - cycle_start) % cycle_length): + p.cycle() + +print(p.load) -- cgit v1.2.3-54-g00ecf