Skip to content

Commit

Permalink
Day 15 (#24)
Browse files Browse the repository at this point in the history
* Boilerplate

* Solve 15.a

* Solve 15.b

* Factor out code and fix a testing woopsie
  • Loading branch information
tyler-hoffman authored Dec 15, 2023
1 parent e48ee3c commit 42a9be3
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 0 deletions.
Empty file added aoc_2023/day_15/__init__.py
Empty file.
29 changes: 29 additions & 0 deletions aoc_2023/day_15/a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from dataclasses import dataclass
from aoc_2023.day_15.common import hash_it
from aoc_2023.day_15.parser import Parser


@dataclass
class Day15PartASolver:
lines: list[str]

@property
def solution(self) -> int:
return sum(hash_it(line) for line in self.lines)


def solve(input: str) -> int:
data = Parser.parse(input)
solver = Day15PartASolver(data)

return solver.solution


def get_solution() -> int:
with open("aoc_2023/day_15/input.txt", "r") as f:
input = f.read()
return solve(input)


if __name__ == "__main__":
print(get_solution())
108 changes: 108 additions & 0 deletions aoc_2023/day_15/b.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from dataclasses import dataclass, field
from functools import cached_property
from typing import Optional
from aoc_2023.day_15.common import hash_it
from aoc_2023.day_15.parser import Parser


@dataclass
class Minus:
label: str


@dataclass
class Equal:
label: str
val: int


@dataclass
class Lens:
label: str
focus_length: int
deleted: bool = False


@dataclass(frozen=True)
class Box:
id: int
lenses: list[Optional[Lens]] = field(hash=False, default_factory=list)
lens_index_lookup: dict[str, Lens] = field(hash=False, default_factory=dict)

@property
def focusing_power(self) -> int:
output = 0
box_num = self.id + 1
lenses = [lens for lens in self.lenses if lens and not lens.deleted]
for i, lens in enumerate(lenses):
lens_num = i + 1
output += box_num * lens_num * lens.focus_length

return output

def remove_lens(self, label: str) -> None:
if label in self.lens_index_lookup:
self.lens_index_lookup[label].deleted = True
del self.lens_index_lookup[label]

def set_lens(self, label: str, value) -> None:
if label in self.lens_index_lookup:
lens = self.lens_index_lookup[label]
lens.focus_length = value
else:
lens = Lens(label, value)
self.lenses.append(lens)
self.lens_index_lookup[label] = lens


@dataclass
class Day15PartBSolver:
lines: list[str]

@property
def solution(self) -> int:
for op in self.operations:
self.do_operation(op)
return sum(box.focusing_power for box in self.boxes)

@cached_property
def boxes(self) -> list[Box]:
return [Box(i, []) for i in range(256)]

@cached_property
def operations(self) -> list[Minus | Equal]:
output = list[Minus | Equal]()
for line in self.lines:
if line[-1] == "-":
output.append(Minus(line[:-1]))
else:
label, val = line.split("=")
output.append(Equal(label, int(val)))
return output

def do_operation(self, op: Minus | Equal) -> None:
box_id = hash_it(op.label)
box = self.boxes[box_id]

match op:
case Minus(label):
box.remove_lens(label)
case Equal(label, val):
box.set_lens(label, val)


def solve(input: str) -> int:
data = Parser.parse(input)
solver = Day15PartBSolver(data)

return solver.solution


def get_solution() -> int:
with open("aoc_2023/day_15/input.txt", "r") as f:
input = f.read()
return solve(input)


if __name__ == "__main__":
print(get_solution())
7 changes: 7 additions & 0 deletions aoc_2023/day_15/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
def hash_it(line: str) -> int:
output = 0
for char in line:
output += ord(char)
output *= 17
output %= 256
return output
Binary file added aoc_2023/day_15/from_prompt.py
Binary file not shown.
Binary file added aoc_2023/day_15/input.txt
Binary file not shown.
4 changes: 4 additions & 0 deletions aoc_2023/day_15/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class Parser:
@staticmethod
def parse(input: str) -> list[str]:
return input.strip().split(",")
Empty file added tests/test_day_15/__init__.py
Empty file.
13 changes: 13 additions & 0 deletions tests/test_day_15/test_a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from aoc_2023.day_15.a import get_solution, solve
from aoc_2023.day_15.from_prompt import (
SAMPLE_DATA,
SAMPLE_SOLUTION_A,
)


def test_solve():
assert solve(SAMPLE_DATA) == SAMPLE_SOLUTION_A


def test_my_solution():
assert get_solution() == 517015
10 changes: 10 additions & 0 deletions tests/test_day_15/test_b.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from aoc_2023.day_15.b import get_solution, solve
from aoc_2023.day_15.from_prompt import SAMPLE_DATA, SAMPLE_SOLUTION_B


def test_solve():
assert solve(SAMPLE_DATA) == SAMPLE_SOLUTION_B


def test_my_solution():
assert get_solution() == 286104
6 changes: 6 additions & 0 deletions tests/test_day_15/test_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from aoc_2023.day_15.common import hash_it
from aoc_2023.day_15.from_prompt import HASH, HASH_HASHED


def test_hash():
assert hash_it(HASH) == HASH_HASHED

0 comments on commit 42a9be3

Please sign in to comment.