Cleanup day 1 solution

This commit is contained in:
Nettika
2023-12-16 10:29:16 -08:00
parent 0b30503e72
commit dd52297b08
10 changed files with 169 additions and 1138 deletions

View File

@@ -1,20 +0,0 @@
from pathlib import Path
from puzzle import solve_pt_1, solve_pt_2
def main():
input = (
Path(__file__)
.parent.joinpath("calibration_document.txt")
.read_text()
.strip()
.split("\n")
)
print("Calibration Numbers:")
print("Part 1 -", solve_pt_1(input))
print("Part 2 -", solve_pt_2(input))
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +0,0 @@
import re
pt_1_first_digit_pattern = re.compile("([0-9])")
pt_1_last_digit_pattern = pt_1_first_digit_pattern
pt_1_symbol_map = {str(n): str(n) for n in range(10)}
pt_2_first_digit_pattern = re.compile(
"([0-9]|one|two|three|four|five|six|seven|eight|nine|zero)"
)
pt_2_last_digit_pattern = re.compile(
"([0-9]|eno|owt|eerht|ruof|evif|xis|neves|thgie|enin|orez)"
)
pt_2_symbol_map = {
**pt_1_symbol_map,
"one": "1",
"two": "2",
"three": "3",
"four": "4",
"five": "5",
"six": "6",
"seven": "7",
"eight": "8",
"nine": "9",
"zero": "0",
}
def _solve(
input: list[str],
first_digit_pattern: re.Pattern,
last_digit_pattern: re.Pattern,
symbol_map: dict[str, str],
) -> int:
total = 0
for line in input:
first_digit_match = first_digit_pattern.search(line)
last_digit_match = last_digit_pattern.search(line[::-1])
if not first_digit_match or not last_digit_match:
raise ValueError(f'Line "{line}" calibration document contains no digits.')
first_digit = symbol_map[first_digit_match.group(1)]
last_digit = symbol_map[last_digit_match.group(1)[::-1]]
line_calibration_number = int(first_digit + last_digit)
total += line_calibration_number
return total
def solve_pt_1(input: list[str]) -> int:
return _solve(
input,
pt_1_first_digit_pattern,
pt_1_first_digit_pattern,
pt_1_symbol_map,
)
def solve_pt_2(input: list[str]) -> int:
return _solve(
input,
pt_2_first_digit_pattern,
pt_2_last_digit_pattern,
pt_2_symbol_map,
)

View File

@@ -1,33 +0,0 @@
from puzzle import (pt_1_first_digit_pattern, pt_1_last_digit_pattern,
solve_pt_1, solve_pt_2)
def test_solve_pt_1():
assert (
solve_pt_1(
[
"1abc2",
"pqr3stu8vwx",
"a1b2c3d4e5f",
"treb7uchet",
]
)
== 142
)
def test_solve_pt_2():
assert (
solve_pt_2(
[
"two1nine",
"eightwothree",
"abcone2threexyz",
"xtwone3four",
"4nineeightseven2",
"zoneight234",
"7pqrstsixteen",
]
)
== 281
)

View File

@@ -2,3 +2,16 @@
__author__ = "Nettika <nettika@leaf.ninja>"
__version__ = "1.0.0"
from typing import Callable
from advent_of_code.trebuchet import (
recover_calibration_digits_and_words,
recover_calibration_digits_only,
)
Solver = Callable[[str], str]
solvers: dict[int, tuple[Solver, Solver]] = {
1: (recover_calibration_digits_only, recover_calibration_digits_and_words),
}

View File

@@ -3,10 +3,7 @@ from argparse import ArgumentParser
import requests
from advent_of_code import __version__
from advent_of_code.solver import Solver
solvers: dict[int, Solver] = {}
from advent_of_code import __version__, solvers
def main():
@@ -57,10 +54,10 @@ def main():
# Execute solvers
print(f"Solving day {args.day}'s puzzle")
start = time.time()
print("Part 1 -", solver.solve_part_1(input))
print("Part 2 -", solver.solve_part_2(input))
print("Part 1 -", solver[0](input))
print("Part 2 -", solver[1](input))
end = time.time()
print(f"Solved in {end-start:,} seconds.")
print(f"Solved in {end-start:,.4f} seconds.")
if __name__ == "__main__":

View File

@@ -1,11 +0,0 @@
from abc import ABC, abstractmethod
class Solver(ABC):
@abstractmethod
def solve_part_1(self, input: str) -> None:
pass
@abstractmethod
def solve_part_2(self, input: str) -> None:
pass

View File

@@ -0,0 +1,83 @@
import re
number_word_map = {
"one": "1",
"two": "2",
"three": "3",
"four": "4",
"five": "5",
"six": "6",
"seven": "7",
"eight": "8",
"nine": "9",
"zero": "0",
}
decimal_only_pattern = re.compile("[0-9]")
left_decimal_and_words_pattern = re.compile(
"[0-9]|" + "|".join(word for word in number_word_map.keys())
)
right_decimal_and_words_pattern = re.compile(
"[0-9]|" + "|".join(word[::-1] for word in number_word_map.keys())
)
def _recover_calibration_value(
line: str,
left_symbol_pattern: re.Pattern,
right_symbol_pattern: re.Pattern,
symbol_map: dict[str, str],
) -> int:
left_symbol_match = left_symbol_pattern.search(line)
right_symbol_match = right_symbol_pattern.search(line[::-1])
if not left_symbol_match or not right_symbol_match:
raise ValueError(f'The calibration value of line "{line}" is unrecoverable.')
left_symbol = left_symbol_match.group()
right_symbol = right_symbol_match.group()[::-1]
first_digit = symbol_map.get(left_symbol, left_symbol)
second_digit = symbol_map.get(right_symbol, right_symbol)
return int(first_digit + second_digit)
def _recover_all_calibration_values(
input: str,
left_symbol_pattern: re.Pattern,
right_symbol_pattern: re.Pattern,
symbol_map: dict[str, str],
) -> int:
return sum(
_recover_calibration_value(
line,
left_symbol_pattern,
right_symbol_pattern,
symbol_map,
)
for line in input.split("\n")
)
def recover_calibration_digits_only(input: str) -> str:
return str(
_recover_all_calibration_values(
input,
decimal_only_pattern,
decimal_only_pattern,
{},
)
)
def recover_calibration_digits_and_words(input: str) -> str:
return str(
_recover_all_calibration_values(
input,
left_decimal_and_words_pattern,
right_decimal_and_words_pattern,
number_word_map,
)
)

View File

@@ -12,3 +12,6 @@ test = ["pytest", "types-requests"]
[project.scripts]
advent-of-code-2023 = "advent_of_code.__main__:main"
[tool.pytest.ini_options]
testpaths = ["tests"]

66
tests/trebuchet_test.py Normal file
View File

@@ -0,0 +1,66 @@
import re
from advent_of_code.trebuchet import (
recover_calibration_digits_and_words,
recover_calibration_digits_only,
_recover_all_calibration_values,
_recover_calibration_value,
)
def test_recover_calibration_value():
assert (
_recover_calibration_value(
"..ab..cd..",
re.compile("ab|cd"),
re.compile("ba|dc"),
{"ab": "1", "cd": "2"},
)
== 12
)
def test_recover_all_calibration_values():
assert (
_recover_all_calibration_values(
"..ab..cd..\n..cd..ab..",
re.compile("ab|cd"),
re.compile("ba|dc"),
{"ab": "1", "cd": "2"},
)
== 33
)
def test_recover_calibration_digits_only():
assert (
recover_calibration_digits_only(
"\n".join(
[
"1abc2",
"pqr3stu8vwx",
"a1b2c3d4e5f",
"treb7uchet",
]
)
)
== "142"
)
def test_recover_calibration_digits_and_words():
assert (
recover_calibration_digits_and_words(
"\n".join(
[
"two1nine",
"eightwothree",
"abcone2threexyz",
"xtwone3four",
"4nineeightseven2",
"zoneight234",
"7pqrstsixteen",
]
)
)
== "281"
)