diff options
author | Tomasz Kramkowski <tomasz@kramkow.ski> | 2023-12-14 14:25:18 +0000 |
---|---|---|
committer | Tomasz Kramkowski <tomasz@kramkow.ski> | 2023-12-14 14:25:18 +0000 |
commit | 1b1d5350680b3dee215443143d166424793374d7 (patch) | |
tree | ce675aa2bc16d8db196ba08293157658fe175546 | |
parent | ef213fd0c871fc871f758941298ad839f998dc21 (diff) | |
download | aoc2023-1b1d5350680b3dee215443143d166424793374d7.tar.gz aoc2023-1b1d5350680b3dee215443143d166424793374d7.tar.xz aoc2023-1b1d5350680b3dee215443143d166424793374d7.zip |
day 14
-rw-r--r-- | 14.py | 132 |
1 files changed, 132 insertions, 0 deletions
@@ -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) |