lots of cleanup
This commit is contained in:
parent
7d0d3e504e
commit
0385cbd62e
26 changed files with 337 additions and 417 deletions
|
|
@ -1,17 +1,17 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import day_num, part1, part2
|
||||
|
||||
|
||||
def test_part1():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 24_000
|
||||
result = part1(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 45_000
|
||||
result = part2(data)
|
||||
assert result == expected
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Iterator
|
||||
from typing import Iterator, Self
|
||||
from enum import Enum
|
||||
|
||||
day_num = 2
|
||||
|
|
@ -26,8 +26,8 @@ class Shape(Enum):
|
|||
Paper = 2
|
||||
Scissors = 3
|
||||
|
||||
@staticmethod
|
||||
def parse(line: str) -> tuple[Shape, Shape]:
|
||||
@classmethod
|
||||
def parse(cls, line: str) -> tuple[Self, Self]:
|
||||
"""
|
||||
Parses a line into a game of RPC
|
||||
Parameters
|
||||
|
|
@ -47,11 +47,11 @@ class Shape(Enum):
|
|||
or either of the shapes is unknown
|
||||
"""
|
||||
match line.strip().split():
|
||||
case [o, p]: return Shape.parse_opponent(o), Shape.parse_player(p)
|
||||
case [o, p]: return cls.parse_opponent(o), cls.parse_player(p)
|
||||
case _: raise Exception(f"Unknown line: {line}")
|
||||
|
||||
@staticmethod
|
||||
def parse_opponent(char: str) -> Shape:
|
||||
@classmethod
|
||||
def parse_opponent(cls, char: str) -> Self:
|
||||
"""
|
||||
Parses a shape for RPC
|
||||
A -> Rock
|
||||
|
|
@ -73,13 +73,13 @@ class Shape(Enum):
|
|||
If the character does not describe a valid shape
|
||||
"""
|
||||
match char.strip().upper():
|
||||
case 'A': return Shape.Rock
|
||||
case 'B': return Shape.Paper
|
||||
case 'C': return Shape.Scissors
|
||||
case 'A': return cls.Rock
|
||||
case 'B': return cls.Paper
|
||||
case 'C': return cls.Scissors
|
||||
case _: raise Exception(f"Unknown char : {char}")
|
||||
|
||||
@staticmethod
|
||||
def parse_player(char: str) -> Shape:
|
||||
@classmethod
|
||||
def parse_player(cls, char: str) -> Self:
|
||||
"""
|
||||
Parses a shape for RPC using rules for player shapes
|
||||
X -> Rock
|
||||
|
|
@ -101,9 +101,9 @@ class Shape(Enum):
|
|||
If the character does not describe a valid shape
|
||||
"""
|
||||
match char.strip().upper():
|
||||
case 'X': return Shape.Rock
|
||||
case 'Y': return Shape.Paper
|
||||
case 'Z': return Shape.Scissors
|
||||
case 'X': return cls.Rock
|
||||
case 'Y': return cls.Paper
|
||||
case 'Z': return cls.Scissors
|
||||
case _: raise Exception(f"Unknown char : {char}")
|
||||
|
||||
def prev(self) -> Shape:
|
||||
|
|
@ -135,8 +135,8 @@ class Result(Enum):
|
|||
Draw = 2
|
||||
Win = 3
|
||||
|
||||
@staticmethod
|
||||
def parse(line: str) -> tuple[Shape, Result]:
|
||||
@classmethod
|
||||
def parse(cls, line: str) -> tuple[Shape, Self]:
|
||||
"""
|
||||
Parses a line into a game of RPC with anm expected outcome
|
||||
Parameters
|
||||
|
|
@ -156,11 +156,11 @@ class Result(Enum):
|
|||
or either the shape or result is unknown
|
||||
"""
|
||||
match line.strip().split():
|
||||
case [o, r]: return Shape.parse_opponent(o), Result.parse_result(r)
|
||||
case [o, r]: return Shape.parse_opponent(o), cls.parse_result(r)
|
||||
case _: raise Exception(f"Unknown line: {line}")
|
||||
|
||||
@staticmethod
|
||||
def parse_result(char: str) -> Result:
|
||||
@classmethod
|
||||
def parse_result(cls, char: str) -> Self:
|
||||
"""
|
||||
Parses an expected result for RPC
|
||||
X -> Lose
|
||||
|
|
@ -182,9 +182,9 @@ class Result(Enum):
|
|||
If the character does not describe a valid result
|
||||
"""
|
||||
match char.strip().upper():
|
||||
case 'X': return Result.Lose
|
||||
case 'Y': return Result.Draw
|
||||
case 'Z': return Result.Win
|
||||
case 'X': return cls.Lose
|
||||
case 'Y': return cls.Draw
|
||||
case 'Z': return cls.Win
|
||||
case _: raise Exception(f"Unknown char : {char}")
|
||||
|
||||
def player_shape(self, other: Shape) -> Shape:
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import day_num, part1, part2, Shape, Result
|
||||
|
||||
|
||||
def test_part1():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 15
|
||||
result = part1(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 12
|
||||
result = part2(data)
|
||||
assert result == expected
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import day_num, part1, part2
|
||||
|
||||
|
||||
def test_part1():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 157
|
||||
result = part1(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 70
|
||||
result = part2(data)
|
||||
assert result == expected
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing import Iterator
|
||||
from typing import Iterator, Self
|
||||
|
||||
day_num = 4
|
||||
|
||||
|
|
@ -19,10 +19,10 @@ class Range:
|
|||
start: int
|
||||
end: int
|
||||
|
||||
@staticmethod
|
||||
def parse(line: str) -> Range:
|
||||
@classmethod
|
||||
def parse(cls, line: str) -> Self:
|
||||
match line.split('-'):
|
||||
case [s, e]: return Range(int(s), int(e))
|
||||
case [s, e]: return cls(int(s), int(e))
|
||||
case _: raise Exception(f"Not a valid range: {line}")
|
||||
|
||||
def includes(self, other: Range) -> bool:
|
||||
|
|
@ -39,10 +39,10 @@ class Pair:
|
|||
first: Range
|
||||
second: Range
|
||||
|
||||
@staticmethod
|
||||
def parse(line: str) -> Pair:
|
||||
@classmethod
|
||||
def parse(cls, line: str) -> Self:
|
||||
match line.split(','):
|
||||
case [f, s]: return Pair(Range.parse(f), Range.parse(s))
|
||||
case [f, s]: return cls(Range.parse(f), Range.parse(s))
|
||||
case _: raise Exception(f"Not a valid Pair: {line}")
|
||||
|
||||
def includes(self) -> bool:
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import Pair, Range, day_num, part1, part2
|
||||
|
||||
|
||||
def test_part1():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 2
|
||||
result = part1(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 4
|
||||
result = part2(data)
|
||||
assert result == expected
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing import ClassVar, Iterator
|
||||
from typing import ClassVar, Iterator, Self
|
||||
|
||||
from advent.parser.parser import P
|
||||
|
||||
|
|
@ -31,13 +31,13 @@ class Move:
|
|||
to_parser: ClassVar[P[int]] = P.second(P.string(" to "), P.unsigned())
|
||||
move_parser: ClassVar[P[tuple[int, int, int]]] = P.seq(amount_parser, from_parser, to_parser)
|
||||
|
||||
@staticmethod
|
||||
def parse(line: str) -> Move | None:
|
||||
parsed = Move.move_parser.parse(line)
|
||||
@classmethod
|
||||
def parse(cls, line: str) -> Self | None:
|
||||
parsed = cls.move_parser.parse(line)
|
||||
if parsed.is_fail():
|
||||
return None
|
||||
amount, frm, to = parsed.get()
|
||||
return Move(amount, frm - 1, to - 1)
|
||||
return cls(amount, frm - 1, to - 1)
|
||||
|
||||
def do_move(self, crates: list[str], as_9001: bool) -> list[str]:
|
||||
"""
|
||||
|
|
@ -62,12 +62,12 @@ class Crane:
|
|||
P.one_char().in_brackets(), P.string(" ").replace(None))
|
||||
crate_row_parser: ClassVar[P[list[str | None]]] = crate_parser.sep_by(P.is_char(' '))
|
||||
|
||||
@staticmethod
|
||||
def parse_crate_row(line: str) -> list[None | str] | None:
|
||||
@classmethod
|
||||
def parse_crate_row(cls, line: str) -> list[None | str] | None:
|
||||
return Crane.crate_row_parser.parse(line).get_or(None)
|
||||
|
||||
@staticmethod
|
||||
def parse_stacks(lines: Iterator[str]) -> list[str]:
|
||||
@classmethod
|
||||
def parse_stacks(cls, lines: Iterator[str]) -> list[str]:
|
||||
stacks: list[str] = []
|
||||
for line in lines:
|
||||
crate_row = Crane.parse_crate_row(line)
|
||||
|
|
@ -83,14 +83,14 @@ class Crane:
|
|||
|
||||
raise Exception("Can never happen")
|
||||
|
||||
@staticmethod
|
||||
def parse(lines: Iterator[str], is_9001: bool) -> Crane:
|
||||
drawing = Crane.parse_stacks(lines)
|
||||
@classmethod
|
||||
def parse(cls, lines: Iterator[str], is_9001: bool) -> Self:
|
||||
drawing = cls.parse_stacks(lines)
|
||||
moves = [p for p in (Move.parse(line) for line in lines) if p is not None]
|
||||
return Crane(drawing, moves, is_9001)
|
||||
return cls(drawing, moves, is_9001)
|
||||
|
||||
@staticmethod
|
||||
def top(crates: list[str]) -> str:
|
||||
@classmethod
|
||||
def top(cls, crates: list[str]) -> str:
|
||||
""" Lists the last item in the given stacks. Fails if any stack is empty """
|
||||
return ''.join(stack[-1] for stack in crates)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import Move, day_num, part1, part2, Crane
|
||||
|
||||
|
||||
def test_part1():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = "CMZ"
|
||||
result = part1(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = "MCD"
|
||||
result = part2(data)
|
||||
assert result == expected
|
||||
|
|
@ -32,7 +32,7 @@ def test_parse_line2():
|
|||
|
||||
|
||||
def test_drawing():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = ["ZN", "MCD", "P"]
|
||||
result = Crane.parse_stacks(data)
|
||||
assert result == expected
|
||||
|
|
@ -46,7 +46,7 @@ def test_parse_move():
|
|||
|
||||
|
||||
def test_parse_all():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = Crane(
|
||||
["ZN", "MCD", "P"],
|
||||
[Move(1, 1, 0), Move(3, 0, 2), Move(2, 1, 0), Move(1, 0, 1)], True)
|
||||
|
|
@ -55,7 +55,7 @@ def test_parse_all():
|
|||
|
||||
|
||||
def test_all_moves():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
crane = Crane.parse(data, False)
|
||||
expected = ["C", "M", "PDNZ"]
|
||||
result = crane.perform_all_moves()
|
||||
|
|
@ -63,7 +63,7 @@ def test_all_moves():
|
|||
|
||||
|
||||
def test_all_moves9001():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
crane = Crane.parse(data, True)
|
||||
expected = ["M", "C", "PZND"]
|
||||
result = crane.perform_all_moves()
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import day_num, marker, part1, part2
|
||||
|
||||
|
||||
def test_part1():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 7
|
||||
result = part1(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 19
|
||||
result = part2(data)
|
||||
assert result == expected
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from typing import Iterator
|
||||
from typing import Iterator, Self
|
||||
|
||||
day_num = 7
|
||||
|
||||
|
|
@ -15,32 +15,44 @@ def part2(lines: Iterator[str]) -> int:
|
|||
return directory.get_min_delete_size(70_000_000, 30_000_000)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@dataclass(slots=True, eq=False)
|
||||
class Directory:
|
||||
name: str
|
||||
parent: Directory | None
|
||||
subdirs: list[Directory] = field(default_factory=list)
|
||||
files: list[tuple[str, int]] = field(default_factory=list)
|
||||
size: int | None = None
|
||||
subdirs: list[Directory] = field(default_factory=list, init=False)
|
||||
files: list[tuple[str, int]] = field(default_factory=list, init=False)
|
||||
size: int | None = field(default=None, init=False, repr=False)
|
||||
|
||||
@classmethod
|
||||
def create_root(cls) -> Self:
|
||||
return cls('/', None)
|
||||
|
||||
def cd_into(self, name: str) -> Directory:
|
||||
"""
|
||||
Returns the named sub directory or .. for parent
|
||||
May fail if unkown subdirectory - or already in root
|
||||
"""
|
||||
if name == "..":
|
||||
if self.parent is None:
|
||||
raise Exception('Already at root Directory')
|
||||
return self.parent
|
||||
match name:
|
||||
case '/':
|
||||
current = self
|
||||
while current.parent is not None:
|
||||
current = current.parent
|
||||
return current
|
||||
|
||||
for sub in self.subdirs:
|
||||
if sub.name == name:
|
||||
return sub
|
||||
raise Exception(f"Could not find subdir {name}")
|
||||
case '..':
|
||||
if self.parent is None:
|
||||
raise Exception('Already at root Directory')
|
||||
return self.parent
|
||||
|
||||
case _:
|
||||
for sub in self.subdirs:
|
||||
if sub.name == name:
|
||||
return sub
|
||||
raise Exception(f"Could not find subdir {name}")
|
||||
|
||||
def add_directory(self, name: str):
|
||||
""" Adds the named directory."""
|
||||
self.subdirs.append(Directory(name, self))
|
||||
self.subdirs.append(Directory(name, parent=self))
|
||||
|
||||
def add_file(self, name: str, size: int):
|
||||
""" Adds the given file and size """
|
||||
|
|
@ -69,7 +81,7 @@ class Directory:
|
|||
"""
|
||||
Returns the size of the smallest directory that must be removed to created the free space
|
||||
given as a parameter and the given disk size
|
||||
#"""
|
||||
"""
|
||||
unused = disk_size - self.get_size()
|
||||
minimum: int | None = None
|
||||
for dir in self.get_all_directories():
|
||||
|
|
@ -82,14 +94,11 @@ class Directory:
|
|||
|
||||
return minimum
|
||||
|
||||
@staticmethod
|
||||
def parse(lines: Iterator[str]) -> Directory:
|
||||
line = next(lines)
|
||||
if line != '$ cd /':
|
||||
raise Exception(f"Illegal first line: {line}")
|
||||
|
||||
root = Directory('/', None)
|
||||
@classmethod
|
||||
def parse(cls, lines: Iterator[str]) -> Self:
|
||||
root = cls.create_root()
|
||||
current = root
|
||||
|
||||
for line in lines:
|
||||
match line.split():
|
||||
case ['$', 'cd', name]:
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import day_num, part1, part2, Directory
|
||||
|
||||
|
||||
def test_part1():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 95437
|
||||
result = part1(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 24933642
|
||||
result = part2(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_size():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 48381165
|
||||
directory = Directory.parse(data)
|
||||
result = directory.get_size()
|
||||
|
|
@ -26,7 +26,7 @@ def test_size():
|
|||
|
||||
|
||||
def test_maxed_size():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 95437
|
||||
directory = Directory.parse(data)
|
||||
result = directory.get_maxed_size(100_000)
|
||||
|
|
@ -34,7 +34,7 @@ def test_maxed_size():
|
|||
|
||||
|
||||
def test_find_to_delete():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 24933642
|
||||
directory = Directory.parse(data)
|
||||
result = directory.get_min_delete_size(70_000_000, 30_000_000)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing import Iterator
|
||||
from typing import Iterator, Self
|
||||
|
||||
day_num = 8
|
||||
|
||||
|
|
@ -20,10 +20,10 @@ class Forest:
|
|||
width: int
|
||||
height: int
|
||||
|
||||
@staticmethod
|
||||
def parse(lines: Iterator[str]) -> Forest:
|
||||
@classmethod
|
||||
def parse(cls, lines: Iterator[str]) -> Self:
|
||||
trees = [[int(tree) for tree in line] for line in lines]
|
||||
return Forest(trees, len(trees[0]), len(trees))
|
||||
return cls(trees, len(trees[0]), len(trees))
|
||||
|
||||
def count_visible_trees(self) -> int:
|
||||
visible: set[tuple[int, int]] = set()
|
||||
|
|
|
|||
|
|
@ -1,45 +1,45 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import Forest, day_num, part1, part2
|
||||
|
||||
|
||||
def test_part1():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 21
|
||||
result = part1(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 8
|
||||
result = part2(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_visible():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 21
|
||||
result = Forest.parse(data).count_visible_trees()
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_distance():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 4
|
||||
result = Forest.parse(data).single_scenic_score(2, 1)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_distance2():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 8
|
||||
result = Forest.parse(data).single_scenic_score(2, 3)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_max_distance():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 8
|
||||
result = Forest.parse(data).max_scenic_score()
|
||||
assert result == expected
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing import Iterator
|
||||
from typing import Iterator, Self
|
||||
|
||||
day_num = 9
|
||||
|
||||
|
||||
def part1(lines: Iterator[str]) -> int:
|
||||
lst = [Command.parse(line) for line in lines]
|
||||
return Command.walk(lst, 2)
|
||||
commands = (Command.parse(line) for line in lines)
|
||||
return simulate(commands, 2)
|
||||
|
||||
|
||||
def part2(lines: Iterator[str]) -> int:
|
||||
lst = [Command.parse(line) for line in lines]
|
||||
return Command.walk(lst, 10)
|
||||
commands = (Command.parse(line) for line in lines)
|
||||
return simulate(commands, 10)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
|
|
@ -21,18 +21,18 @@ class Point:
|
|||
x: int
|
||||
y: int
|
||||
|
||||
@staticmethod
|
||||
def parse_direction(char: str) -> Point:
|
||||
@classmethod
|
||||
def parse_direction(cls, char: str) -> Self:
|
||||
""" Parses the given direction to a Point. May raise if invalid """
|
||||
match char:
|
||||
case 'R':
|
||||
return Point(1, 0)
|
||||
return cls(1, 0)
|
||||
case 'U':
|
||||
return Point(0, 1)
|
||||
return cls(0, 1)
|
||||
case 'L':
|
||||
return Point(-1, 0)
|
||||
return cls(-1, 0)
|
||||
case 'D':
|
||||
return Point(0, -1)
|
||||
return cls(0, -1)
|
||||
case _:
|
||||
raise Exception(f"Unkown Direction: {char}")
|
||||
|
||||
|
|
@ -65,8 +65,8 @@ class Command:
|
|||
dir: Point
|
||||
steps: int
|
||||
|
||||
@staticmethod
|
||||
def parse(line: str) -> Command:
|
||||
@classmethod
|
||||
def parse(cls, line: str) -> Self:
|
||||
""" Parse a command line. My raise exception if the was an illegal line"""
|
||||
match line.split():
|
||||
case [dir, steps]:
|
||||
|
|
@ -74,20 +74,20 @@ class Command:
|
|||
case _:
|
||||
raise Exception(f"Illegal line: {line}")
|
||||
|
||||
@staticmethod
|
||||
def walk(lst: list[Command], rope_length: int):
|
||||
""" Walks the whole rope in Planck length steps according to commands """
|
||||
rope = [Point(0, 0)] * rope_length
|
||||
visited = {rope[-1]}
|
||||
for command in lst:
|
||||
for _ in range(command.steps):
|
||||
rope[0] = rope[0].add(command.dir)
|
||||
for n in range(1, rope_length):
|
||||
moved_piece = rope[n].step_to(rope[n - 1])
|
||||
if not moved_piece:
|
||||
break
|
||||
rope[n] = moved_piece
|
||||
if n == rope_length - 1:
|
||||
visited.add(rope[n])
|
||||
|
||||
return len(visited)
|
||||
def simulate(lst: Iterator[Command], rope_length: int) -> int:
|
||||
""" Walks the whole rope in Planck length steps according to commands """
|
||||
rope = [Point(0, 0)] * rope_length
|
||||
visited = {rope[-1]}
|
||||
for command in lst:
|
||||
for _ in range(command.steps):
|
||||
rope[0] = rope[0].add(command.dir)
|
||||
for n in range(1, rope_length):
|
||||
moved_piece = rope[n].step_to(rope[n - 1])
|
||||
if not moved_piece:
|
||||
break
|
||||
rope[n] = moved_piece
|
||||
if n == rope_length - 1:
|
||||
visited.add(rope[n])
|
||||
|
||||
return len(visited)
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import Command, day_num, part1, part2
|
||||
from .solution import Command, day_num, part1, part2, simulate
|
||||
|
||||
|
||||
def test_part1():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 13
|
||||
result = part1(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
data = utils.read_data(day_num, 'test02.txt')
|
||||
data = input.read_lines(day_num, 'test02.txt')
|
||||
expected = 36
|
||||
result = part2(data)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_short():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 13
|
||||
lst = [Command.parse(line) for line in data]
|
||||
result = Command.walk(lst, 2)
|
||||
lst = (Command.parse(line) for line in data)
|
||||
result = simulate(lst, 2)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_long1():
|
||||
data = utils.read_data(day_num, 'test01.txt')
|
||||
data = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 1
|
||||
lst = [Command.parse(line) for line in data]
|
||||
result = Command.walk(lst, 10)
|
||||
lst = (Command.parse(line) for line in data)
|
||||
result = simulate(lst, 10)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_long2():
|
||||
data = utils.read_data(day_num, 'test02.txt')
|
||||
data = input.read_lines(day_num, 'test02.txt')
|
||||
expected = 36
|
||||
lst = [Command.parse(line) for line in data]
|
||||
result = Command.walk(lst, 10)
|
||||
lst = (Command.parse(line) for line in data)
|
||||
result = simulate(lst, 10)
|
||||
assert result == expected
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ def draw(lines: Iterator[str], width: int, height: int) -> list[str]:
|
|||
picture = ""
|
||||
for cycle, sprite in enumerate(cycles(lines)):
|
||||
crt_pos = cycle % width
|
||||
if sprite - 1 <= crt_pos and crt_pos <= sprite + 1:
|
||||
if sprite - 1 <= crt_pos <= sprite + 1:
|
||||
picture += '#'
|
||||
else:
|
||||
picture += ' '
|
||||
|
|
|
|||
|
|
@ -1,38 +1,38 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import cycles, day_num, draw, grab_values, part1, part2
|
||||
|
||||
|
||||
def test_part1():
|
||||
lines = utils.read_data(day_num, 'test01.txt')
|
||||
lines = input.read_lines(day_num, 'test01.txt')
|
||||
expected = 13140
|
||||
result = part1(lines)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
lines = utils.read_data(day_num, 'test01.txt')
|
||||
expected = list(utils.read_data(day_num, 'expected.txt'))
|
||||
lines = input.read_lines(day_num, 'test01.txt')
|
||||
expected = list(input.read_lines(day_num, 'expected.txt'))
|
||||
result = part2(lines)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_small():
|
||||
lines = utils.read_data(day_num, 'test02.txt')
|
||||
lines = input.read_lines(day_num, 'test02.txt')
|
||||
expected = [1, 1, 1, 4, 4, -1]
|
||||
result = list(cycles(lines))
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_grab_values():
|
||||
lines = utils.read_data(day_num, 'test01.txt')
|
||||
lines = input.read_lines(day_num, 'test01.txt')
|
||||
expected = [420, 1140, 1800, 2940, 2880, 3960]
|
||||
result = list(grab_values(lines))
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_draw():
|
||||
lines = utils.read_data(day_num, 'test01.txt')
|
||||
expected = list(utils.read_data(day_num, 'expected.txt'))
|
||||
lines = input.read_lines(day_num, 'test01.txt')
|
||||
expected = list(input.read_lines(day_num, 'expected.txt'))
|
||||
result = draw(lines, 40, 6)
|
||||
assert result == expected
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
from advent.common import utils
|
||||
from advent.common import input
|
||||
|
||||
from .solution import day_num, part1, part2
|
||||
|
||||
|
||||
def test_part1():
|
||||
lines = utils.read_data(day_num, 'test01.txt')
|
||||
lines = input.read_lines(day_num, 'test01.txt')
|
||||
expected = None
|
||||
result = part1(lines)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_part2():
|
||||
lines = utils.read_data(day_num, 'test01.txt')
|
||||
lines = input.read_lines(day_num, 'test01.txt')
|
||||
expected = None
|
||||
result = part2(lines)
|
||||
assert result == expected
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue