advent-2022-python/advent/days/day20/solution.py
2022-12-21 05:59:43 +01:00

116 lines
3.2 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from typing import Iterable, Iterator, Self
day_num = 20
def part1(lines: Iterator[str]) -> int:
ring = Ring.create(int(line) for line in lines)
ring.process(1)
return sum(ring.get_ordered([1000, 2000, 3000]))
def part2(lines: Iterator[str]) -> int:
ring = Ring.create(int(line) * 811589153 for line in lines)
ring.process(10)
return sum(ring.get_ordered([1000, 2000, 3000]))
@dataclass(slots=True)
class Ring:
""" Datastructure that stores Items in a Ring """
items: list[RingItem]
@classmethod
def create(cls, values: Iterable[int]):
""" Crates ring from an Iterable """
value_iterator = iter(values)
current = RingItem.create(next(value_iterator))
items = [current]
for value in value_iterator:
current = current.append(value)
items.append(current)
return Ring(items)
@property
def zero(self) -> RingItem:
""" Helper to find the first item with value zero. Raises Exception if there is none. """
for item in self.items:
if item.value == 0:
return item
raise Exception("No Zero Item found")
def process(self, rounds: int):
""" Processes the given number of complete rounds. """
for _ in range(rounds):
for item in self.items:
item.move(len(self.items))
def get_ordered(self, values: list[int]) -> Iterator[int]:
""" Returns the values at the given ordered positions """
for n, item in zip(range(max(values) + 1), self.zero):
if n in values:
yield item
@dataclass(slots=True)
class RingItem:
""" A class to store one Item in a Ring"""
value: int
next: RingItem
prev: RingItem
@classmethod
def create(cls, value: int) -> Self:
""" Creates a single Ring Element that points to itself"""
root = RingItem(value, None, None) # type: ignore
root.next = root
root.prev = root
return root
def append(self, value: int) -> RingItem:
""" Appends the given value to the current element """
next = RingItem(value, self.next, self)
self.next.prev = next
self.next = next
return next
def move(self, item_count: int):
""" Moves the current element according to its value """
steps = self.value % (item_count - 1)
if steps == 0:
return
self.next.prev = self.prev
self.prev.next = self.next
new_pos = self
for _ in range(steps):
new_pos = new_pos.next
self.prev = new_pos
self.next = new_pos.next
new_pos.next.prev = self
new_pos.next = self
def __iter__(self) -> Iterator[int]:
""" Never ending iterator through items"""
current = self
while True:
yield current.value
current = current.next
def stopping(self) -> Iterator[int]:
""" Iterator that iterates exactly once through all items """
current = self
while True:
yield current.value
current = current.next
if current == self:
break