116 lines
3.2 KiB
Python
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
|