Initial commit with days 1 to 4.
This commit is contained in:
32
day1/solver.py
Normal file
32
day1/solver.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
def part1():
|
||||||
|
first, second = read_ids()
|
||||||
|
distance = sum(abs(id1 - id2) for id1, id2 in zip(first, second))
|
||||||
|
print(f"Total distance is: {distance}")
|
||||||
|
|
||||||
|
def part2():
|
||||||
|
first, second = read_ids()
|
||||||
|
|
||||||
|
# Make freqmap for second list
|
||||||
|
freq = defaultdict(int)
|
||||||
|
for id in second:
|
||||||
|
freq[id] += 1
|
||||||
|
|
||||||
|
# Sum first list weighted by freqmap
|
||||||
|
similarity = sum(freq[id] * id for id in first)
|
||||||
|
print(f"Similarity is {similarity}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def read_ids():
|
||||||
|
idlist = []
|
||||||
|
with open("id-list") as f:
|
||||||
|
for line in f:
|
||||||
|
if line:
|
||||||
|
idlist.append(map(int, line.split()))
|
||||||
|
return map(sorted, zip(*idlist))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
part1()
|
||||||
|
part2()
|
||||||
69
day2/solver.py
Normal file
69
day2/solver.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
from typing import Callable, Iterator
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def check_report_basic(levels: tuple[int, ...]) -> bool:
|
||||||
|
safe = True
|
||||||
|
differences = [next_level - level for level, next_level in zip(levels, levels[1:])]
|
||||||
|
safe = safe and (
|
||||||
|
all(-3 <= diff <= -1 for diff in differences)
|
||||||
|
or all(1 <= diff <= 3 for diff in differences)
|
||||||
|
)
|
||||||
|
return safe
|
||||||
|
|
||||||
|
def check_report_damp(levels: tuple[int, ...]) -> bool:
|
||||||
|
# Obvious solution is using check_report_basic iteratively..
|
||||||
|
# Is there a better solution?
|
||||||
|
safe = check_report_basic(levels)
|
||||||
|
safe = safe or any(check_report_basic(levels[:i] + levels[i+1:]) for i in range(len(levels)))
|
||||||
|
return safe
|
||||||
|
|
||||||
|
def tester(data: dict[tuple[int, ...], bool], checker: Callable[[tuple[int, ...]], bool]):
|
||||||
|
for report, expected in data.items():
|
||||||
|
assert (checker(report) is expected), f"Check failed: {report=} should be {expected=}"
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
print("Testing basic check")
|
||||||
|
data = {
|
||||||
|
(7, 6, 4, 2, 1): True,
|
||||||
|
(1, 2, 7, 8, 9): False,
|
||||||
|
(9, 7, 6, 2, 1): False,
|
||||||
|
(1, 3, 2, 4, 5): False,
|
||||||
|
(8, 6, 4, 4, 1): False,
|
||||||
|
(1, 3, 6, 7, 9): True,
|
||||||
|
}
|
||||||
|
tester(data, check_report_basic)
|
||||||
|
print("Basic check passed.")
|
||||||
|
|
||||||
|
print("Testing dampened check.")
|
||||||
|
data = {
|
||||||
|
(7, 6, 4, 2, 1): True,
|
||||||
|
(1, 2, 7, 8, 9): False,
|
||||||
|
(9, 7, 6, 2, 1): False,
|
||||||
|
(1, 3, 2, 4, 5): True,
|
||||||
|
(8, 6, 4, 4, 1): True,
|
||||||
|
(1, 3, 6, 7, 9): True,
|
||||||
|
}
|
||||||
|
tester(data, check_report_damp)
|
||||||
|
print("Damp check passed.")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
tests()
|
||||||
|
filename = sys.argv[1]
|
||||||
|
reports = load_data(filename)
|
||||||
|
total = sum(map(check_report_basic, reports))
|
||||||
|
print(f"Total safe reports without damping: {total}")
|
||||||
|
|
||||||
|
reports = load_data(filename)
|
||||||
|
total = sum(map(check_report_damp, reports))
|
||||||
|
print(f"Total safe reports with damping: {total}")
|
||||||
|
|
||||||
|
|
||||||
|
def load_data(filename) -> Iterator[tuple[int, ...]]:
|
||||||
|
with open(filename) as f:
|
||||||
|
for line in f:
|
||||||
|
if line.strip():
|
||||||
|
yield tuple(map(int, line.split()))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
67
day3/solver.py
Normal file
67
day3/solver.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
from typing import Iterator
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
mul_re = re.compile(
|
||||||
|
r"mul\((?P<n1>\d+),(?P<n2>\d+)\)"
|
||||||
|
)
|
||||||
|
|
||||||
|
mul_re_cond = re.compile(
|
||||||
|
r"mul\((?P<n1>\d+),(?P<n2>\d+)\)|(?P<do>do\(\))|(?P<dont>don't\(\))"
|
||||||
|
)
|
||||||
|
|
||||||
|
def process_basic(data) -> int:
|
||||||
|
total = 0
|
||||||
|
for match in re.finditer(mul_re, data):
|
||||||
|
n1 = int(match.group('n1'))
|
||||||
|
n2 = int(match.group('n2'))
|
||||||
|
total += n1 * n2
|
||||||
|
return total
|
||||||
|
|
||||||
|
def process_cond(data):
|
||||||
|
# Could also split into segments between do and the next don't
|
||||||
|
# And process each block between them
|
||||||
|
# But this is simpler
|
||||||
|
total = 0
|
||||||
|
enabled = True
|
||||||
|
for match in re.finditer(mul_re_cond, data):
|
||||||
|
if match.group('do'):
|
||||||
|
enabled = True
|
||||||
|
elif match.group('dont'):
|
||||||
|
enabled = False
|
||||||
|
elif enabled:
|
||||||
|
n1 = int(match.group('n1'))
|
||||||
|
n2 = int(match.group('n2'))
|
||||||
|
total += n1 * n2
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
def load_data(filename) -> str:
|
||||||
|
with open(filename) as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
def test():
|
||||||
|
print("Beginning Test 1")
|
||||||
|
data = r"xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"
|
||||||
|
assert process_basic(data) == 161
|
||||||
|
print("Test 1 passed")
|
||||||
|
|
||||||
|
print("Beginning Test 2")
|
||||||
|
data = r"xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"
|
||||||
|
assert process_cond(data) == 48
|
||||||
|
print("Test 2 passed")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
test()
|
||||||
|
data = load_data(sys.argv[1])
|
||||||
|
total = process_basic(data)
|
||||||
|
print(f"Total from data without conditionals: {total}")
|
||||||
|
total = process_cond(data)
|
||||||
|
print(f"Total from data with conditionals: {total}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
131
day4/solver.py
Normal file
131
day4/solver.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import itertools
|
||||||
|
from typing import Iterator
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
test_data = r"""
|
||||||
|
MMMSXXMASM
|
||||||
|
MSAMXMSMSA
|
||||||
|
AMXSXMAAMM
|
||||||
|
MSAMASMSMX
|
||||||
|
XMASAMXAMM
|
||||||
|
XXAMMXXAMA
|
||||||
|
SMSMSASXSS
|
||||||
|
SAXAMASAAA
|
||||||
|
MAMMMXMMMM
|
||||||
|
MXMXAXMASX
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
XMAS_RULES = list(zip(
|
||||||
|
*[(
|
||||||
|
(0, i), (0, -i),
|
||||||
|
(i, 0), (-i, 0),
|
||||||
|
(i, i), (-i, -i),
|
||||||
|
(i, -i), (-i, i)
|
||||||
|
) for i in range(4)]
|
||||||
|
))
|
||||||
|
MAS_RULES = [
|
||||||
|
( (-1, -1), (0, 0), (1, 1) ),
|
||||||
|
( (-1, 1), (0, 0), (1, -1) )
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
def print_debug_matrix(rows, cols, letter_map: dict[tuple[int, int], str]):
|
||||||
|
for i in range(rows):
|
||||||
|
for j in range(cols):
|
||||||
|
print(letter_map.get((i, j), '.'), end='')
|
||||||
|
print('\n')
|
||||||
|
|
||||||
|
def test_XMAS():
|
||||||
|
print("Beginning test matrix search")
|
||||||
|
test_matrix = list(filter(bool,[line.strip() for line in test_data.splitlines()]))
|
||||||
|
assert letter_matrix_search_XMAS(test_matrix) == 18
|
||||||
|
print("Test matrix search passed")
|
||||||
|
|
||||||
|
def test_MAS():
|
||||||
|
print("Beginning MAS matrix search")
|
||||||
|
test_matrix = list(filter(bool,[line.strip() for line in test_data.splitlines()]))
|
||||||
|
assert letter_matrix_search_MAS(test_matrix) == 9
|
||||||
|
print("MAS matrix search passed")
|
||||||
|
|
||||||
|
def tests():
|
||||||
|
test_XMAS()
|
||||||
|
test_MAS()
|
||||||
|
|
||||||
|
|
||||||
|
def matrix_neighbour_rule(matrix: list[str], rowi, colj, rules: list[tuple[tuple[int, int], ...]]):
|
||||||
|
"""
|
||||||
|
Get the neighbours of the given point (rowi, colj) by the given rules.
|
||||||
|
E.g. if rules = [((-1, 0), (0, 0), (0, 1))], get a forwards diagonal.
|
||||||
|
Does not return a ruleset if it would go out of bounds.
|
||||||
|
"""
|
||||||
|
star = []
|
||||||
|
rows, cols = len(matrix), len(matrix[0])
|
||||||
|
for ruleset in rules:
|
||||||
|
this_line = []
|
||||||
|
for rowstep, colstep in ruleset:
|
||||||
|
rowii = rowi + rowstep
|
||||||
|
colji = colj + colstep
|
||||||
|
|
||||||
|
if not (0 <= rowii < rows and 0 <= colji < cols):
|
||||||
|
break
|
||||||
|
|
||||||
|
this_line.append(((rowii, colji), matrix[rowii][colji]))
|
||||||
|
else:
|
||||||
|
star.append(this_line)
|
||||||
|
return star
|
||||||
|
|
||||||
|
|
||||||
|
def letter_matrix_search_XMAS(matrix: list[str], DEBUG=DEBUG):
|
||||||
|
total = 0
|
||||||
|
debug_map = {}
|
||||||
|
|
||||||
|
for rowi, row in enumerate(matrix):
|
||||||
|
for colj, letter in enumerate(row):
|
||||||
|
if letter == 'X':
|
||||||
|
star = matrix_neighbour_rule(matrix, rowi, colj, XMAS_RULES)
|
||||||
|
for line in star:
|
||||||
|
if ''.join(letter for _, letter in line) == 'XMAS':
|
||||||
|
total += 1
|
||||||
|
debug_map |= dict(line)
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
print_debug_matrix(len(matrix), len(matrix[0]), debug_map)
|
||||||
|
|
||||||
|
return total
|
||||||
|
|
||||||
|
def letter_matrix_search_MAS(matrix: list[str], DEBUG=DEBUG):
|
||||||
|
total = 0
|
||||||
|
debug_map = {}
|
||||||
|
|
||||||
|
for rowi, row in enumerate(matrix):
|
||||||
|
for colj, letter in enumerate(row):
|
||||||
|
if letter == 'A':
|
||||||
|
star = matrix_neighbour_rule(matrix, rowi, colj, MAS_RULES)
|
||||||
|
valid = star and all(''.join(list(zip(*line))[1]) in ('MAS', 'SAM') for line in star)
|
||||||
|
if valid:
|
||||||
|
total += 1
|
||||||
|
for line in star:
|
||||||
|
debug_map |= dict(line)
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
print_debug_matrix(len(matrix), len(matrix[0]), debug_map)
|
||||||
|
return total
|
||||||
|
|
||||||
|
def load_data(filename) -> list[str]:
|
||||||
|
with open(filename) as f:
|
||||||
|
return list(filter(bool, (line.strip() for line in f.readlines())))
|
||||||
|
|
||||||
|
def main():
|
||||||
|
tests()
|
||||||
|
data = load_data(sys.argv[1])
|
||||||
|
total = letter_matrix_search_XMAS(data, DEBUG=False)
|
||||||
|
print(f"Total XMAS found: {total}")
|
||||||
|
total = letter_matrix_search_MAS(data, DEBUG=False)
|
||||||
|
print(f"Total MAS found: {total}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user