day11 finished

This commit is contained in:
Ruediger Ludwig 2022-12-11 14:51:33 +01:00
parent dd3bba8a23
commit dab59aafa1
9 changed files with 411 additions and 20 deletions

View file

@ -8,6 +8,7 @@ I use python 3.11 without any libraries beyond the standard.
| Day | Time | Rank | Score | Time | Rank | Score | | Day | Time | Rank | Score | Time | Rank | Score |
| --- | --------- | ----- | ----- | -------- | ----- | ----- | | --- | --------- | ----- | ----- | -------- | ----- | ----- |
| 11 | 00:56:44 | 5414 | 0 | 02:42:24 | 7558 | 0 |
| 10 | 00:38:16 | 7637 | 0 | 01:16:55 | 7961 | 0 | | 10 | 00:38:16 | 7637 | 0 | 01:16:55 | 7961 | 0 |
| 9 | 00:54:18 | 7719 | 0 | 01:07:37 | 4901 | 0 | | 9 | 00:54:18 | 7719 | 0 | 01:07:37 | 4901 | 0 |
| 8 | 00:41:51 | 7831 | 0 | 00:59:27 | 6325 | 0 | | 8 | 00:41:51 | 7831 | 0 | 00:59:27 | 6325 | 0 |

View file

@ -40,16 +40,20 @@ def run(day: Day, part: int) -> None:
def run_from_string(day_str: str) -> None: def run_from_string(day_str: str) -> None:
match day_str.split('/'): match day_str.split('/'):
case [d]: case [d]:
day = get_day(int(d)) day_num = int(d)
day = get_day(day_num)
run(day, 1) if day_num == day.day_num:
run(day, 2) run(day, 1)
run(day, 2)
case [d, p]: case [d, p]:
day = get_day(int(d)) day_num = int(d)
part = int(p) day = get_day(day_num)
run(day, part) if day_num == day.day_num:
part = int(p)
run(day, part)
case _: case _:
raise Exception(f'{day_str} is not a valid day description') raise Exception(f'{day_str} is not a valid day description')
@ -61,8 +65,9 @@ def main() -> None:
try: try:
for day_num in range(1, 25): for day_num in range(1, 25):
day = get_day(day_num) day = get_day(day_num)
run(day, 1) if day_num == day.day_num:
run(day, 2) run(day, 1)
run(day, 2)
except ModuleNotFoundError: except ModuleNotFoundError:
pass pass

View file

View file

@ -0,0 +1,55 @@
Monkey 0:
Starting items: 97, 81, 57, 57, 91, 61
Operation: new = old * 7
Test: divisible by 11
If true: throw to monkey 5
If false: throw to monkey 6
Monkey 1:
Starting items: 88, 62, 68, 90
Operation: new = old * 17
Test: divisible by 19
If true: throw to monkey 4
If false: throw to monkey 2
Monkey 2:
Starting items: 74, 87
Operation: new = old + 2
Test: divisible by 5
If true: throw to monkey 7
If false: throw to monkey 4
Monkey 3:
Starting items: 53, 81, 60, 87, 90, 99, 75
Operation: new = old + 1
Test: divisible by 2
If true: throw to monkey 2
If false: throw to monkey 1
Monkey 4:
Starting items: 57
Operation: new = old + 6
Test: divisible by 13
If true: throw to monkey 7
If false: throw to monkey 0
Monkey 5:
Starting items: 54, 84, 91, 55, 59, 72, 75, 70
Operation: new = old * old
Test: divisible by 7
If true: throw to monkey 6
If false: throw to monkey 3
Monkey 6:
Starting items: 95, 79, 79, 68, 78
Operation: new = old + 3
Test: divisible by 3
If true: throw to monkey 1
If false: throw to monkey 3
Monkey 7:
Starting items: 61, 97, 67
Operation: new = old + 4
Test: divisible by 17
If true: throw to monkey 0
If false: throw to monkey 5

View file

@ -0,0 +1,27 @@
Monkey 0:
Starting items: 79, 98
Operation: new = old * 19
Test: divisible by 23
If true: throw to monkey 2
If false: throw to monkey 3
Monkey 1:
Starting items: 54, 65, 75, 74
Operation: new = old + 6
Test: divisible by 19
If true: throw to monkey 2
If false: throw to monkey 0
Monkey 2:
Starting items: 79, 60, 97
Operation: new = old * old
Test: divisible by 13
If true: throw to monkey 1
If false: throw to monkey 3
Monkey 3:
Starting items: 74
Operation: new = old + 3
Test: divisible by 17
If true: throw to monkey 0
If false: throw to monkey 1

View file

@ -0,0 +1,134 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from math import prod
from typing import Callable, Iterator, Self
from advent.parser.parser import P
day_num = 11
def part1(lines: Iterator[str]) -> int:
horde = Troop_While_Worried.parse(lines)
horde.rounds(20)
return horde.inspected_result()
def part2(lines: Iterator[str]) -> int:
horde = Troop_While_Kinda_Relieved.parse(lines)
horde.rounds(10_000)
return horde.inspected_result()
def worry_increaser(op: str, value: int | str) -> WorryIncreaser:
match (op, value):
case '*', 'old': return lambda old: old * old
case '*', int(v): return lambda old: old * v
case '+', int(v): return lambda old: old + v
case _: raise Exception(f"Illegal line: {op} {value}")
class Parser:
""" All the parsers needed for this solution """
worry_inc: P[WorryIncreaser] = P.second(
P.tstring("Operation: new = old"),
P.map2(P.one_of('+*'), P.either(P.tstring('old'), P.tsigned()),
worry_increaser)).tline()
monkey_number: P[int] = P.unsigned().between(P.tstring('Monkey'), P.tchar(':')).tline()
items: P[list[int]] = P.second(
P.tstring('Starting items:'), P.unsigned().sep_by(sep=P.tchar(','))).tline()
modulo: P[int] = P.second(
P.tstring("Test: divisible by"), P.unsigned()).tline()
throw_parser: P[int] = P.second(
P.seq(
P.tstring("If"),
P.either(P.tstring("true"), P.tstring("false")),
P.tstring(": throw to monkey")),
P.unsigned()).tline()
test: P[tuple[int, int, int]] = P.seq(
modulo, throw_parser, throw_parser)
monkey: P[Monkey] = P.map4(monkey_number, items,
worry_inc, test,
lambda number, items, worry_inc, test:
Monkey(number, items, worry_inc, *test))
monkey_list: P[list[Monkey]] = P.first(monkey, P.eol().optional()).many()
WorryIncreaser = Callable[[int], int]
@dataclass(slots=True)
class Monkey:
number: int
items: list[int]
worry_increaser: WorryIncreaser
modulator: int
target_if_divides: int
catcher_if_not_divides: int
inspected: int = field(default=0, compare=False)
def inspect_items(self, worry_decrease: int | None) -> Iterator[tuple[int, int]]:
for item in self.items:
self.inspected += 1
next_level = self.worry_increaser(item)
if worry_decrease is not None:
next_level //= worry_decrease
if next_level % self.modulator == 0:
target_monkey = self.target_if_divides
else:
target_monkey = self.catcher_if_not_divides
yield target_monkey, next_level
self.items.clear()
def catch_item(self, item: int):
self.items.append(item)
@dataclass(slots=True)
class Troop(ABC):
monkeys: list[Monkey]
@abstractmethod
def single_round(self):
...
def rounds(self, count: int):
for _ in range(count):
self.single_round()
def inspected_result(self):
most = sorted((monkey.inspected for monkey in self.monkeys), reverse=True)
return most[0] * most[1]
@dataclass(slots=True)
class Troop_While_Worried(Troop):
""" The """
@classmethod
def parse(cls, lines: Iterator[str]) -> Self:
monkeys = Parser.monkey_list.parse_iterator(lines).get()
return Troop_While_Worried(monkeys)
def single_round(self):
for currentMonkey in self.monkeys:
for target_monkey, item in currentMonkey.inspect_items(3):
self.monkeys[target_monkey].catch_item(item)
@dataclass(slots=True)
class Troop_While_Kinda_Relieved(Troop):
modulator: int
@classmethod
def parse(cls, lines: Iterator[str]) -> Self:
monkeys = Parser.monkey_list.parse_iterator(lines).get()
return Troop_While_Kinda_Relieved(monkeys, prod(monkey.modulator for monkey in monkeys))
def single_round(self):
for current_monkey in self.monkeys:
for target_monkey, item in current_monkey.inspect_items(None):
self.monkeys[target_monkey].catch_item(item % self.modulator)

View file

@ -0,0 +1,56 @@
from advent.common import input
from .solution import Troop_While_Kinda_Relieved, Troop_While_Worried, day_num, part1, part2
def test_part1():
lines = input.read_lines(day_num, 'test01.txt')
expected = 10605
result = part1(lines)
assert result == expected
def test_part2():
lines = input.read_lines(day_num, 'test01.txt')
expected = 2713310158
result = part2(lines)
assert result == expected
def test_parse_all():
lines = input.read_lines(day_num, 'test01.txt')
expected = 4
result = Troop_While_Worried.parse(lines)
assert len(result.monkeys) == expected
def test_one_round():
lines = input.read_lines(day_num, 'test01.txt')
expected = [2080, 25, 167, 207, 401, 1046]
result = Troop_While_Worried.parse(lines)
result.single_round()
assert list(result.monkeys[1].items) == expected
def test_rounds():
lines = input.read_lines(day_num, 'test01.txt')
expected = 101
result = Troop_While_Worried.parse(lines)
result.rounds(20)
assert result.monkeys[0].inspected == expected
def test_inspected():
lines = input.read_lines(day_num, 'test01.txt')
expected = 10605
result = Troop_While_Worried.parse(lines)
result.rounds(20)
assert result.inspected_result() == expected
def test_inspected2():
lines = input.read_lines(day_num, 'test01.txt')
expected = 2713310158
result = Troop_While_Kinda_Relieved.parse(lines)
result.rounds(10_000)
assert result.inspected_result() == expected

View file

@ -1,9 +1,9 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass, field
from functools import reduce from functools import reduce
from itertools import chain from itertools import chain
from typing import Any, Callable, Generic, Iterator, Self, TypeVar, overload from typing import Any, Callable, Generic, Iterator, Protocol, Self, TypeVar, overload
import unicodedata import unicodedata
from .result import Result from .result import Result
@ -17,8 +17,16 @@ T5 = TypeVar('T5')
TR = TypeVar('TR') TR = TypeVar('TR')
class ParserInput(Protocol):
def step(self) -> tuple[Self, str]:
...
def has_data(self) -> bool:
...
@dataclass(slots=True, frozen=True) @dataclass(slots=True, frozen=True)
class ParserInput: class SimpleParserInput:
input: str input: str
start: int start: int
@ -26,7 +34,7 @@ class ParserInput:
if self.start >= len(self.input): if self.start >= len(self.input):
raise Exception("Already at End of Input") raise Exception("Already at End of Input")
return ParserInput(self.input, self.start + 1), self.input[self.start] return SimpleParserInput(self.input, self.start + 1), self.input[self.start]
def has_data(self) -> bool: def has_data(self) -> bool:
return self.start < len(self.input) return self.start < len(self.input)
@ -41,6 +49,61 @@ class ParserInput:
return f'{self.input[self.start-3:self.start-1]}->[{self.input[self.start:]}]' return f'{self.input[self.start-3:self.start-1]}->[{self.input[self.start:]}]'
@dataclass(slots=True)
class StringDispenser:
lines: Iterator[str]
input: str = field(default="", init=False)
length: int = field(default=0, init=False)
def read_more(self):
try:
part = next(self.lines)
if self.input:
self.input = f"{self.input}\n{part}"
else:
self.input = part
self.length = len(self.input)
return True
except StopIteration:
return False
def get_str(self, pos: int) -> str | None:
assert pos >= 0
if pos < self.length:
return self.input[pos]
elif pos == self.length and pos != 0:
return "\n"
elif self.read_more():
return self.get_str(pos)
else:
return None
def has_more(self, pos: int) -> bool:
assert pos >= 0
if pos <= self.length:
return True
elif self.read_more():
return self.has_more(pos)
else:
return False
@dataclass(slots=True, frozen=True)
class IteratorParserInput:
dispenser: StringDispenser
start: int
def step(self) -> tuple[Self, str]:
char = self.dispenser.get_str(self.start)
if char is None:
raise Exception("Already at End of Input")
return IteratorParserInput(self.dispenser, self.start + 1), char
def has_data(self) -> bool:
return self.dispenser.has_more(self.start)
ParserResult = Iterator[tuple[ParserInput, T]] ParserResult = Iterator[tuple[ParserInput, T]]
ParserFunc = Callable[[ParserInput], ParserResult[T]] ParserFunc = Callable[[ParserInput], ParserResult[T]]
@ -49,8 +112,16 @@ class P(Generic[T]):
def __init__(self, func: ParserFunc[T]): def __init__(self, func: ParserFunc[T]):
self.func = func self.func = func
def parse(self, s: str, i: int = 0) -> Result[T]: def parse(self, s: str) -> Result[T]:
all_results = self.func(ParserInput(s, i)) all_results = self.func(SimpleParserInput(s, 0))
try:
_, result = next(all_results)
return Result.of(result)
except StopIteration:
return Result.fail("No result")
def parse_iterator(self, it: Iterator[str]) -> Result[T]:
all_results = self.func(IteratorParserInput(StringDispenser(it), 0))
try: try:
_, result = next(all_results) _, result = next(all_results)
return Result.of(result) return Result.of(result)
@ -58,7 +129,7 @@ class P(Generic[T]):
return Result.fail("No result") return Result.fail("No result")
def parse_multi(self, s: str, i: int = 0) -> Iterator[T]: def parse_multi(self, s: str, i: int = 0) -> Iterator[T]:
return (v for _, v in self.func(ParserInput(s, i))) return (v for _, v in self.func(SimpleParserInput(s, i)))
@classmethod @classmethod
def pure(cls, value: T) -> P[T]: def pure(cls, value: T) -> P[T]:
@ -101,7 +172,7 @@ class P(Generic[T]):
def replace(self, value: TR) -> P[TR]: def replace(self, value: TR) -> P[TR]:
return self.fmap(lambda _: value) return self.fmap(lambda _: value)
def as_unit(self) -> P[tuple[()]]: def ignore(self) -> P[tuple[()]]:
return self.fmap(lambda _: ()) return self.fmap(lambda _: ())
def apply(self, p2: P[Callable[[T], TR]]) -> P[TR]: def apply(self, p2: P[Callable[[T], TR]]) -> P[TR]:
@ -363,8 +434,16 @@ class P(Generic[T]):
return P.char_func(lambda c: c == cmp) return P.char_func(lambda c: c == cmp)
@classmethod @classmethod
def string(cls, s: str) -> P[str]: def string(cls, cmp: str) -> P[str]:
return P.seq(*map(P.char, s)).replace(s) return P.seq(*map(P.char, cmp)).replace(cmp)
@classmethod
def tchar(cls, cmp: str) -> P[str]:
return P.char(cmp).trim()
@classmethod
def tstring(cls, cmp: str) -> P[str]:
return P.string(cmp).trim()
@classmethod @classmethod
def one_of(cls, s: str) -> P[str]: def one_of(cls, s: str) -> P[str]:
@ -390,6 +469,10 @@ class P(Generic[T]):
def upper(cls) -> P[str]: def upper(cls) -> P[str]:
return P.char_func(lambda c: c.isupper()) return P.char_func(lambda c: c.isupper())
@classmethod
def eol(cls) -> P[tuple[()]]:
return P.char('\n').ignore()
@classmethod @classmethod
def space(cls) -> P[str]: def space(cls) -> P[str]:
return P.char_func(lambda c: c.isspace()) return P.char_func(lambda c: c.isspace())
@ -408,6 +491,20 @@ class P(Generic[T]):
return P.map2(P.one_of('+-').optional(), P.unsigned(), return P.map2(P.one_of('+-').optional(), P.unsigned(),
lambda sign, num: num if sign != '-' else -num) lambda sign, num: num if sign != '-' else -num)
@classmethod
def tunsigned(cls) -> P[int]:
return P.unsigned().trim()
@classmethod
def tsigned(cls) -> P[int]:
return P.signed().trim()
def line(self) -> P[T]:
return P.first(self, P.eol())
def tline(self) -> P[T]:
return P.first(self.trim(), P.eol())
def in_parens(self) -> P[T]: def in_parens(self) -> P[T]:
return self.between(P.char('('), P.char(')')) return self.between(P.char('('), P.char(')'))
@ -430,5 +527,5 @@ class P(Generic[T]):
return self.between(WHITE_SPACE, WHITE_SPACE) return self.between(WHITE_SPACE, WHITE_SPACE)
WHITE_SPACE: P[tuple[()]] = P.space().many().as_unit() WHITE_SPACE: P[tuple[()]] = P.space().many().ignore()
SEP_SPACE: P[tuple[()]] = P.space().some().as_unit() SEP_SPACE: P[tuple[()]] = P.space().some().ignore()

View file

@ -269,3 +269,19 @@ def test_word2():
expected = [('123', 'a')] expected = [('123', 'a')]
result = list(parser.parse_multi(input)) result = list(parser.parse_multi(input))
assert result == expected assert result == expected
def test_iterator_input():
input = iter(['1', '2'])
parser = P.unsigned().line().many()
expected = [1, 2]
result = parser.parse_iterator(input).get()
assert result == expected
def test_iterator_trim_input():
input = iter(['1 ', '2 '])
parser = P.unsigned().trim().line().many()
expected = [1, 2]
result = parser.parse_iterator(input).get()
assert result == expected