day11 finished
This commit is contained in:
parent
dd3bba8a23
commit
dab59aafa1
9 changed files with 411 additions and 20 deletions
|
|
@ -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 |
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
0
advent/days/day11/__init__.py
Normal file
0
advent/days/day11/__init__.py
Normal file
55
advent/days/day11/data/input.txt
Normal file
55
advent/days/day11/data/input.txt
Normal 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
|
||||||
27
advent/days/day11/data/test01.txt
Normal file
27
advent/days/day11/data/test01.txt
Normal 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
|
||||||
134
advent/days/day11/solution.py
Normal file
134
advent/days/day11/solution.py
Normal 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)
|
||||||
56
advent/days/day11/test_solution.py
Normal file
56
advent/days/day11/test_solution.py
Normal 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
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue