Day 20 - Advent of Code 2024
20 December 2024
Working solutions for the day 20 puzzles.
Part One
""" day_20_01.py """
# usage: python3 day_20_01.py 100 < input
import sys
from collections import namedtuple
Point = namedtuple('Point', 'x, y')
def moddiv(value, num):
""" reverse divmod """
return value % num, value // num
def scout(loc, track, obstacles):
""" single possible move from loc not in track """
deltas = [Point(0, -1), Point(1, 0), Point(0, 1), Point(-1, 0)]
dest = None
for d in deltas:
dest = Point(loc.x + d.x, loc.y + d.y)
if dest not in obstacles:
if dest not in track:
break
return dest
def solve(begin, finish, obstacles):
""" single path to go from begin to finish """
visited = []
pos = begin
while pos != finish:
visited.append(pos)
pos = scout(pos, visited, obstacles)
return visited
def cheats(loc, path, obstacles):
""" select cheat locations from loc """
deltas = [Point(0, -1), Point(1, 0), Point(0, 1), Point(-1, 0)]
outcome = []
for d in deltas:
if Point(loc.x + d.x, loc.y + d.y) in obstacles:
dest = Point(loc.x + 2 * d.x, loc.y + 2 * d.y)
if dest in path:
if path.index(loc) < path.index(dest):
outcome.append(dest)
return outcome
with sys.stdin as infile:
raw = infile.read()
saving = int(sys.argv[1])
width = raw.find('\n')
raw = raw.replace('\n', '')
height = len(raw) // width
start = Point(*moddiv(raw.find('S'), width))
end = Point(*moddiv(raw.find('E'), width))
cpu = {Point(*moddiv(i, width)) for i, obj in enumerate(raw) if obj == '#'}
single_path = solve(start, end, cpu)
log = [0 for _ in range(len(single_path))]
for i, step in enumerate(single_path):
for cheat in cheats(step, single_path, cpu):
time_saving = single_path.index(cheat) - i - 2
log[time_saving] += 1
print(sum(log[saving:]))
Part Two
""" day_20_02.py """
# usage: python3 day_20_02.py 100 < input
import sys
from collections import namedtuple
Point = namedtuple('Point', 'x, y')
def moddiv(value, num):
""" reverse divmod """
return value % num, value // num
def scout(loc, track, obstacles):
""" single possible move from loc not in track """
deltas = [Point(0, -1), Point(1, 0), Point(0, 1), Point(-1, 0)]
dest = None
for d in deltas:
dest = Point(loc.x + d.x, loc.y + d.y)
if dest not in obstacles:
if dest not in track:
break
return dest
def solve(begin, finish, obstacles):
""" single path to go from begin to finish """
visited = []
pos = begin
while pos != finish:
visited.append(pos)
pos = scout(pos, visited, obstacles)
return visited + [finish]
def cheats(loc, path):
""" select cheat locations from loc """
def dist(a, b):
""" Manhattan distance """
return abs(a.x - b.x) + abs(a.y - b.y)
outcome = set()
for pos in path[path.index(loc):]:
if (d := dist(loc, pos)) <= 20:
outcome.add((pos, d))
return outcome
with sys.stdin as infile:
raw = infile.read()
saving = int(sys.argv[1])
width = raw.find('\n')
raw = raw.replace('\n', '')
height = len(raw) // width
start = Point(*moddiv(raw.find('S'), width))
end = Point(*moddiv(raw.find('E'), width))
cpu = {Point(*moddiv(i, width)) for i, obj in enumerate(raw) if obj == '#'}
single_path = solve(start, end, cpu)
log = [0 for _ in range(len(single_path))]
for i, step in enumerate(single_path):
for cheat, time in cheats(step, single_path):
time_saving = single_path.index(cheat) - i - time
log[time_saving] += 1
print(sum(log[saving:]))