parent
fcbeb4bfdd
commit
bad86889d6
20 changed files with 0 additions and 1291 deletions
2
2023-python/.gitignore
vendored
2
2023-python/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
__pycache__
|
||||
input
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
Advent of Code 2023
|
||||
===================
|
||||
|
||||
Solutions for #aoc2023 in Python 3 (3.11.5).
|
||||
|
||||
Help scripts
|
||||
------------
|
||||
|
||||
Display all solved puzzles:
|
||||
|
||||
python aoc.py
|
||||
|
||||
To bootstrap a new puzzle (creates `input/<day_no>.txt` and `output/day_<day_no>.py`):
|
||||
|
||||
python aoc.py <day_no> <puzzle_name>
|
||||
|
||||
Manually copy the puzzle input from https://adventofcode.com and paste it in `input/<day_no>.txt`
|
||||
to start coding.
|
||||
|
||||
wl-paste > input/<day_no>.txt
|
||||
|
||||
Solve separate puzzle (replace `XX` with the puzzle number):
|
||||
|
||||
python -m output.day_XX
|
||||
|
||||
Solve separate puzzle using stdin (replace `XX` with the puzzle number):
|
||||
|
||||
wl-paste | python -m output.day_XX
|
||||
cat tmpfile | python -m output.day_XX
|
||||
|
||||
Execute separate puzzle on file save (replace `XX` with the puzzle number):
|
||||
|
||||
ls output/*.py | entr -c -s 'xclip -selection clipboard -o | python -m output.day_XX'
|
||||
ls output/*.py | entr -c -s 'cat tmpfile | python -m output.day_XX'
|
||||
ls output/*.py | entr -c -r python -m output.day_XX
|
||||
|
||||
(requires `entr` and `wl-paste`, Mac users can instead use `pbpaste`. If you
|
||||
prefer X at Linux, use `xclip -selection clipboard -o`).
|
||||
|
||||
To lint files:
|
||||
|
||||
ls output/*.py | entr -r -c flake8 output --ignore=E741,E501,E203
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def headline(n, title):
|
||||
"""Print day number and name, followed by a ruler. Used by the answer decorator"""
|
||||
print(f"\n--- Day {n}: {title} ---\n")
|
||||
|
||||
|
||||
year = 2023
|
||||
|
||||
try:
|
||||
_, day_no, *name = sys.argv
|
||||
except ValueError:
|
||||
day_no = None
|
||||
name = None
|
||||
|
||||
print(
|
||||
f"\nAdvent of Code {year}" "\n###################" "\n\nby Anders Englöf Ytterström"
|
||||
)
|
||||
|
||||
if day_no and name:
|
||||
name = " ".join(name)
|
||||
padded_no = day_no.zfill(2)
|
||||
print(f"\n- creating output/day_{padded_no}.py")
|
||||
with open("output/day_{}.py".format(padded_no), "w") as s:
|
||||
s.write(
|
||||
f"""
|
||||
from output import answer # , matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg
|
||||
|
||||
n = {day_no}
|
||||
title = "{name}"
|
||||
|
||||
|
||||
@answer(1, "Answer is {{}}")
|
||||
def part_1(presolved):
|
||||
return presolved[0]
|
||||
|
||||
|
||||
@answer(2, "Actually, answer is {{}}")
|
||||
def part_2(presolved):
|
||||
return presolved[1]
|
||||
|
||||
|
||||
def solve(data):
|
||||
return 1, 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# use dummy data
|
||||
inp = \"\"\"
|
||||
replace me
|
||||
\"\"\".strip()
|
||||
|
||||
# uncomment to instead use stdin
|
||||
# import sys; inp = sys.stdin.read().strip()
|
||||
|
||||
# uncomment to use AoC provided puzzle input
|
||||
# with open("./input/{padded_no}.txt", "r") as f:
|
||||
# inp = f.read().strip()
|
||||
|
||||
# uncomment to do initial data processing shared by part 1-2
|
||||
inp = solve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
# b = part_2(inp)
|
||||
|
||||
# uncomment and replace 0 with actual output to refactor code
|
||||
# and ensure nonbreaking changes
|
||||
# assert a == 0
|
||||
# assert b == 0
|
||||
""".strip()
|
||||
+ "\n"
|
||||
)
|
||||
print("- making sure input dir exists")
|
||||
if not os.path.exists("input"):
|
||||
os.makedirs("input")
|
||||
|
||||
print(
|
||||
f"""
|
||||
Done! start coding.
|
||||
|
||||
Puzzle link:
|
||||
https://adventofcode.com/{year}/day/{day_no}
|
||||
|
||||
Puzzle input (copy and paste to input/{day_no.zfill(2)}.txt):
|
||||
https://adventofcode.com/{year}/day/{day_no}/input
|
||||
"""
|
||||
)
|
||||
exit(0)
|
||||
|
||||
|
||||
stars = 0
|
||||
for i in [str(n).zfill(2) for n in range(1, 26)]:
|
||||
if not day_no or day_no.zfill(2) == i:
|
||||
try:
|
||||
day = __import__(
|
||||
"output.day_{}".format(i),
|
||||
globals(),
|
||||
locals(),
|
||||
["n", "title", "part_1", "part_2"],
|
||||
0,
|
||||
)
|
||||
with open(f"./input/{i}.txt", "r") as f:
|
||||
data = f.read().strip()
|
||||
headline(day.n, day.title)
|
||||
try:
|
||||
data = day.presolve(data)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
data = day.solve(data)
|
||||
except AttributeError:
|
||||
pass
|
||||
if day.part_1(data, decorate=True):
|
||||
stars += 1
|
||||
if day.part_2(data, decorate=True):
|
||||
stars += 1
|
||||
except IOError:
|
||||
pass
|
||||
except ImportError:
|
||||
pass
|
||||
if not day_no:
|
||||
print(f"\nStars: {stars}")
|
||||
print("".join("*" if n < stars else "•" for n in range(50)))
|
||||
print("")
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
import functools
|
||||
import re
|
||||
|
||||
# Directions/Adjacents for 2D matrices, in the order UP, RIGHT, DOWN, LEFT
|
||||
D = [
|
||||
(-1, 0),
|
||||
(0, 1),
|
||||
(1, 0),
|
||||
(0, -1),
|
||||
]
|
||||
|
||||
# Directions for 2D matrices, as a dict with keys U, R, D, L
|
||||
DD = {
|
||||
"U": (-1, 0),
|
||||
"R": (0, 1),
|
||||
"D": (1, 0),
|
||||
"L": (0, -1),
|
||||
}
|
||||
|
||||
# Adjacent relative positions including diagonals for 2D matrices, in the order NW, N, NE, W, E, SW, S, SE
|
||||
ADJ = [
|
||||
(-1, -1),
|
||||
(-1, 0),
|
||||
(1, -1),
|
||||
(0, -1),
|
||||
(0, 1),
|
||||
(1, 1),
|
||||
(1, 0),
|
||||
(1, -1),
|
||||
]
|
||||
|
||||
|
||||
def answer(part_index, fmt_string):
|
||||
"""Decorator to present a solution in a human readable format"""
|
||||
|
||||
def decorator_aoc(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_aoc(*args, **kwargs):
|
||||
decorate = kwargs.get("decorate", False)
|
||||
if decorate:
|
||||
del kwargs["decorate"]
|
||||
answer = func(*args, **kwargs)
|
||||
if not decorate:
|
||||
print(answer)
|
||||
else:
|
||||
formatted = fmt_string.format(answer)
|
||||
print(f" {part_index}) {formatted}")
|
||||
return answer
|
||||
|
||||
return wrapper_aoc
|
||||
|
||||
return decorator_aoc
|
||||
|
||||
|
||||
def ints(s):
|
||||
"""Extract all integers from a string"""
|
||||
return [int(n) for n in re.findall(r"\d+", s)]
|
||||
|
||||
|
||||
def mhd(a, b):
|
||||
"""Calculates the Manhattan distance between 2 positions in the format (y, x) or (x, y)"""
|
||||
ar, ac = a
|
||||
br, bc = b
|
||||
return abs(ar - br) + abs(ac - bc)
|
||||
|
||||
|
||||
def matrix(d):
|
||||
"""Transform a string into an iterable matrix. Returns the matrix, row count and col count"""
|
||||
m = [tuple(r) for r in d.split()]
|
||||
return m, len(m), len(m[0])
|
||||
|
||||
|
||||
def mdbg(m):
|
||||
"""Print-debug a matrix"""
|
||||
for r in m:
|
||||
print("".join(r))
|
||||
|
||||
|
||||
def vdbg(seen, h, w):
|
||||
"""Print-debug visited positions of a matrix"""
|
||||
for r in range(h):
|
||||
print("".join(["#" if (r, c) in seen else "." for c in range(w)]))
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import re
|
||||
from output import answer
|
||||
|
||||
n = 1
|
||||
title = "Trebuchet?!"
|
||||
|
||||
|
||||
@answer(1, "Calibration values sum: {}, excluding spelled out digits")
|
||||
def part_1(data):
|
||||
def value(s):
|
||||
s = [int(c) for c in s if c.isdigit()]
|
||||
return s[0] * 10 + s[-1]
|
||||
|
||||
return sum(value(line) for line in data.split())
|
||||
|
||||
|
||||
@answer(2, "Calibration values sum: {}, including spelled out digits")
|
||||
def part_2(data):
|
||||
mp = {
|
||||
"one": 1,
|
||||
"two": 2,
|
||||
"three": 3,
|
||||
"four": 4,
|
||||
"five": 5,
|
||||
"six": 6,
|
||||
"seven": 7,
|
||||
"eight": 8,
|
||||
"nine": 9,
|
||||
}
|
||||
|
||||
def value(l):
|
||||
s = [
|
||||
int(c) if c.isdigit() else mp[c]
|
||||
for c in re.findall(
|
||||
r"(?=(\d|one|two|three|four|five|six|seven|eight|nine))", l
|
||||
)
|
||||
]
|
||||
return s[0] * 10 + s[-1]
|
||||
|
||||
return sum(value(line) for line in data.split())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/01.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 54634
|
||||
assert b == 53855
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import re
|
||||
from math import prod
|
||||
from collections import defaultdict
|
||||
from output import answer
|
||||
|
||||
n = 2
|
||||
title = "Cube Conundrum"
|
||||
|
||||
|
||||
@answer(1, "Sum of all possible game IDs: {}")
|
||||
def part_1(data):
|
||||
return sum(
|
||||
[
|
||||
id + 1
|
||||
for id, game in enumerate(data.splitlines())
|
||||
if not sum(
|
||||
any(
|
||||
[
|
||||
c == "red" and int(n) > 12,
|
||||
c == "green" and int(n) > 13,
|
||||
c == "blue" and int(n) > 14,
|
||||
]
|
||||
)
|
||||
for n, c in re.findall(r"(\d+) (\w+)", game)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@answer(2, "Sum of all cube set powers: {}")
|
||||
def part_2(data):
|
||||
def power(game):
|
||||
seen = defaultdict(int)
|
||||
for n, c in re.findall(r"(\d+) (\w+)", game):
|
||||
seen[c] = max([seen[c], int(n)])
|
||||
return prod([seen["blue"], seen["red"], seen["green"]])
|
||||
|
||||
return sum(power(game) for game in data.splitlines())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/02.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 2439
|
||||
assert b == 63711
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
from collections import deque
|
||||
from output import answer
|
||||
|
||||
n = 3
|
||||
title = "Gear Ratios"
|
||||
|
||||
|
||||
@answer(1, "Sum of all part numbers is {} in the engine schematic")
|
||||
def part_1(presolved):
|
||||
s, _ = presolved
|
||||
return s
|
||||
|
||||
|
||||
@answer(2, "Gear ratio sums is {} in the engine schematic")
|
||||
def part_2(presolved):
|
||||
_, gr = presolved
|
||||
return gr
|
||||
|
||||
|
||||
def presolve(data):
|
||||
data = data.split()
|
||||
adj = (-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)
|
||||
w = len(data[0])
|
||||
h = len(data)
|
||||
s = list()
|
||||
gr = list()
|
||||
for y in range(w):
|
||||
for x in range(h):
|
||||
if data[y][x] != "." and not data[y][x].isdigit():
|
||||
seen = set()
|
||||
t = list()
|
||||
for oy, ox in adj:
|
||||
if (y + oy, x + ox) in seen:
|
||||
continue
|
||||
if data[y + oy][x + ox].isdigit():
|
||||
n = deque([data[y + oy][x + ox]])
|
||||
i = x + ox - 1
|
||||
while i in range(w) and data[y + oy][i].isdigit():
|
||||
n.append(data[y + oy][i])
|
||||
seen.add((y + oy, i))
|
||||
i -= 1
|
||||
i = x + ox + 1
|
||||
while i in range(w) and data[y + oy][i].isdigit():
|
||||
n.appendleft(data[y + oy][i])
|
||||
seen.add((y + oy, i))
|
||||
i += 1
|
||||
t.append(sum(10**m * int(d) for m, d in enumerate(n)))
|
||||
# part 1
|
||||
s.append(sum(t))
|
||||
# part 2
|
||||
if data[y][x] == "*" and len(t) == 2:
|
||||
a, b = t
|
||||
gr.append(a * b)
|
||||
|
||||
return sum(s), sum(gr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/03.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
parsed = presolve(inp)
|
||||
|
||||
a = part_1(parsed)
|
||||
b = part_2(parsed)
|
||||
|
||||
assert a == 553825
|
||||
assert b == 93994191
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import re
|
||||
from collections import defaultdict
|
||||
from output import answer
|
||||
|
||||
n = 4
|
||||
title = "Scratchcards"
|
||||
|
||||
|
||||
@answer(1, "Sum of all scratchcard points are {}")
|
||||
def part_1(presolved):
|
||||
scores, _ = presolved
|
||||
return scores
|
||||
|
||||
|
||||
@answer(2, "Ends up wih a total of {} scratchcards")
|
||||
def part_2(presolved):
|
||||
_, count = presolved
|
||||
return count
|
||||
|
||||
|
||||
def presolve(data):
|
||||
scores = []
|
||||
count = defaultdict(int)
|
||||
for cid, line in enumerate(data.splitlines()):
|
||||
a, b = line.split("|")
|
||||
a = set(re.findall(r"\d+", a)[1:])
|
||||
b = set(re.findall(r"\d+", b))
|
||||
ab = len(a & b)
|
||||
if ab > 0:
|
||||
scores.append(2 ** (ab - 1))
|
||||
count[cid] += 1
|
||||
for i in range(cid + 1, cid + ab + 1):
|
||||
count[i] += count[cid]
|
||||
return sum(scores), sum(count.values())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/04.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 21919
|
||||
assert b == 9881048
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
import re
|
||||
from math import inf
|
||||
from output import answer
|
||||
|
||||
n = 5
|
||||
title = "If You Give A Seed A Fertilizer"
|
||||
|
||||
|
||||
@answer(1, "Nearest location for seed id list is {}")
|
||||
def part_1(presolved):
|
||||
l, _ = presolved
|
||||
return l
|
||||
|
||||
|
||||
@answer(2, "Interpreting ranges of seeds, nearest location is {}")
|
||||
def part_2(presolved):
|
||||
_, l = presolved
|
||||
return l
|
||||
|
||||
|
||||
def presolve(data):
|
||||
seeds, *process = data.split("\n\n")
|
||||
seed_ranges = [[int(x) for x in ar.split()] for ar in re.findall(r"\d+ \d+", seeds)]
|
||||
seed_values = [int(v) for v in seeds.split()[1:]]
|
||||
processes = [
|
||||
[tuple(map(int, line.split())) for line in step.splitlines()[1:]]
|
||||
for step in process
|
||||
]
|
||||
|
||||
p1 = _process(seed_values, processes)
|
||||
|
||||
p2 = 26829000 # takes 5m if starting from 0
|
||||
while True:
|
||||
g = _process([p2], processes, reverse=True)
|
||||
if any(g >= a and g < a + r for a, r in seed_ranges):
|
||||
break
|
||||
p2 += 1
|
||||
|
||||
return p1, p2
|
||||
|
||||
|
||||
def _process(seeds, processes, reverse=False):
|
||||
n = inf
|
||||
for start in seeds:
|
||||
n = min(n, _nearest(start, processes, reverse=reverse))
|
||||
return n
|
||||
|
||||
|
||||
def _nearest(start, processes, reverse=False):
|
||||
procs = processes if not reverse else processes[::-1]
|
||||
v = start
|
||||
for steps in procs:
|
||||
for line in steps:
|
||||
dest, src, r = line
|
||||
if reverse:
|
||||
dest, src = src, dest
|
||||
if v >= src and v < src + r:
|
||||
v = dest + v - src
|
||||
break
|
||||
return v
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/05.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 278755257
|
||||
assert b == 26829166
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
from math import prod, sqrt, ceil, floor
|
||||
from output import answer
|
||||
|
||||
n = 6
|
||||
title = "Wait For It"
|
||||
|
||||
|
||||
@answer(1, "The product of all record times for all races is {}")
|
||||
def part_1(presolved):
|
||||
return presolved[0]
|
||||
|
||||
|
||||
@answer(2, "The product of all record times for the single long race is {}")
|
||||
def part_2(presolved):
|
||||
return presolved[1]
|
||||
|
||||
|
||||
def presolve(data):
|
||||
values = data.split()
|
||||
l = len(values) // 2
|
||||
races = list(
|
||||
map(
|
||||
lambda x: (int(x[0]), int(x[1])), list(zip(values[: l + 1], values[l:]))[1:]
|
||||
)
|
||||
)
|
||||
p1 = prod(sum(bpt * (t - bpt) > d for bpt in range(t)) for t, d in races)
|
||||
t = int("".join(values[1:l]))
|
||||
d = int("".join(values[l + 1 :]))
|
||||
# quadratic formula:
|
||||
# https://en.wikipedia.org/wiki/Quadratic_formula
|
||||
l = ceil((-t + sqrt(t**2 - 4 * d)) / -2)
|
||||
h = floor((-t - sqrt(t**2 - 4 * d)) / -2)
|
||||
p2 = h - l + 1
|
||||
return p1, p2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/06.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 1083852
|
||||
assert b == 23501589
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
from collections import Counter
|
||||
from output import answer
|
||||
|
||||
n = 7
|
||||
title = "Camel Cards"
|
||||
|
||||
|
||||
@answer(1, "Total winnings are {}")
|
||||
def part_1(data):
|
||||
return _solve(data)
|
||||
|
||||
|
||||
@answer(2, "Including jokers, total winnings are {}")
|
||||
def part_2(data):
|
||||
return _solve(data.replace("J", "0"))
|
||||
|
||||
|
||||
def _solve(data):
|
||||
return sum(
|
||||
c[-1] * i for i, c in enumerate(sorted(_hand(h) for h in data.splitlines()), 1)
|
||||
)
|
||||
|
||||
|
||||
def _hand(hb):
|
||||
h, b = hb.split()
|
||||
b = int(b)
|
||||
h = h.translate(M)
|
||||
return (_rank(h), h, b)
|
||||
|
||||
|
||||
def _rank(h):
|
||||
"""
|
||||
Rank hand 0-6, letting jokers (0) aid the highest possible hand
|
||||
|
||||
>>> _rank("10110")
|
||||
6
|
||||
>>> _rank("11110")
|
||||
6
|
||||
>>> _rank("12110")
|
||||
5
|
||||
>>> _rank("12100")
|
||||
5
|
||||
>>> _rank("12000")
|
||||
5
|
||||
>>> _rank("12120")
|
||||
4
|
||||
>>> _rank("12300")
|
||||
3
|
||||
>>> _rank("12310")
|
||||
3
|
||||
>>> _rank("12340")
|
||||
1
|
||||
"""
|
||||
hc = Counter(h)
|
||||
hcv = hc.values()
|
||||
j = hc["0"]
|
||||
del hc["0"]
|
||||
p = sum(c == 2 for v, c in hc.items() if v != 0)
|
||||
if j == 5 or max(hcv) + j == 5:
|
||||
return 6
|
||||
if max(hcv) + j == 4:
|
||||
return 5
|
||||
if (3 in hcv and 2 in hcv) or (p == 2 and j > 0):
|
||||
return 4
|
||||
if max(hcv) + j == 3:
|
||||
return 3
|
||||
if p + j > 1:
|
||||
return 2
|
||||
if p + j > 0:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
M = dict(
|
||||
[
|
||||
(ord(o), ord(n))
|
||||
for o, n in {"T": "A", "J": "B", "Q": "C", "K": "D", "A": "E"}.items()
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
with open("./input/07.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 249390788
|
||||
assert b == 248750248
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
import re
|
||||
from math import lcm
|
||||
from output import answer
|
||||
|
||||
n = 8
|
||||
title = "Haunted Wasteland"
|
||||
|
||||
|
||||
@answer(1, "One can reach Z in {} steps")
|
||||
def part_1(presolved):
|
||||
steps, _ = presolved
|
||||
return steps
|
||||
|
||||
|
||||
@answer(2, "Ghost path takes {} steps before all ghosts are at a Z positon")
|
||||
def part_2(presolved):
|
||||
_, ghost_meet_point = presolved
|
||||
return ghost_meet_point
|
||||
|
||||
|
||||
def presolve(data):
|
||||
d, els = data.split("\n\n")
|
||||
r = len(d)
|
||||
e = dict()
|
||||
for el in els.splitlines():
|
||||
k, *lr = re.match(r"(\w+) = \((\w+), (\w+)\)", el).groups()
|
||||
e[k] = lr
|
||||
|
||||
p1 = 0
|
||||
pos = "AAA"
|
||||
while pos != "ZZZ":
|
||||
i = 0 if d[p1 % r] == "L" else 1
|
||||
pos = e[pos][i]
|
||||
p1 += 1
|
||||
|
||||
p2 = 0
|
||||
z = list()
|
||||
for spos in [p for p in e.keys() if p.endswith("A")]:
|
||||
s = 0
|
||||
pos = spos
|
||||
while not pos.endswith("Z"):
|
||||
i = 0 if d[s % r] == "L" else 1
|
||||
pos = e[pos][i]
|
||||
s += 1
|
||||
z.append(s)
|
||||
p2 = lcm(*z)
|
||||
|
||||
return p1, p2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/08.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 17141
|
||||
assert b == 10818234074807
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
from output import answer
|
||||
|
||||
n = 9
|
||||
title = "Mirage Maintenance"
|
||||
|
||||
|
||||
@answer(1, "OASIS report extrapolated values sum is {}")
|
||||
def part_1(data):
|
||||
lines = [[int(d) for d in line.split()] for line in data.splitlines()]
|
||||
return _solve(lines)
|
||||
|
||||
|
||||
@answer(2, "Using prepending, OASIS report extrapolated values sum is {}")
|
||||
def part_2(data):
|
||||
lines = [[int(d) for d in line.split()[::-1]] for line in data.splitlines()]
|
||||
return _solve(lines)
|
||||
|
||||
|
||||
def _solve(lines):
|
||||
vs = 0
|
||||
for l in lines:
|
||||
h = [l]
|
||||
while any(n != 0 for n in h[-1]):
|
||||
h.append([b - a for a, b in zip(h[-1], h[-1][1:])])
|
||||
h = h[::-1]
|
||||
for i in range(len(h)):
|
||||
h[i].append(0 if i == 0 else h[i - 1][-1] + h[i][-1])
|
||||
vs += h[-1][-1]
|
||||
return vs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/09.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 1702218515
|
||||
assert b == 925
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
from collections import deque
|
||||
from output import answer
|
||||
|
||||
n = 10
|
||||
title = "Pipe Maze"
|
||||
|
||||
|
||||
D = (-1, 0), (0, 1), (1, 0), (0, -1)
|
||||
|
||||
C = {
|
||||
(-1, 0): ["|", "7", "F"],
|
||||
(0, 1): ["-", "7", "J"],
|
||||
(1, 0): ["|", "L", "J"],
|
||||
(0, -1): ["-", "L", "F"],
|
||||
}
|
||||
|
||||
A = {
|
||||
"S": [0, 1, 2, 3],
|
||||
"-": [1, 3],
|
||||
"|": [0, 2],
|
||||
"F": [1, 2],
|
||||
"L": [0, 1],
|
||||
"7": [2, 3],
|
||||
"J": [0, 3],
|
||||
}
|
||||
|
||||
|
||||
@answer(1, "Farthest away pipe is at {}")
|
||||
def part_1(presolved):
|
||||
return presolved[0]
|
||||
|
||||
|
||||
@answer(2, "{} spots are encapsulated by pipes")
|
||||
def part_2(presolved):
|
||||
return presolved[1]
|
||||
|
||||
|
||||
def presolve(data):
|
||||
matrix = data.split()
|
||||
w = len(matrix[0])
|
||||
h = len(matrix)
|
||||
q = deque()
|
||||
visited = set()
|
||||
|
||||
for r in range(h):
|
||||
if "S" in matrix[r]:
|
||||
start = (r, matrix[r].index("S"))
|
||||
q.append(start)
|
||||
break
|
||||
|
||||
while q:
|
||||
o = q.popleft()
|
||||
visited.add(o)
|
||||
for di in A[matrix[o[0]][o[1]]]:
|
||||
d = D[di]
|
||||
r = o[0] + d[0]
|
||||
c = o[1] + d[1]
|
||||
if r >= 0 and r < h and c >= 0 and c < w:
|
||||
t = matrix[r][c]
|
||||
p = (r, c)
|
||||
if p not in visited and t != "." and t in C[d]:
|
||||
q.append(p)
|
||||
p1 = len(visited) // 2
|
||||
|
||||
p2 = 0
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
if (y, x) in visited:
|
||||
continue
|
||||
crosses = 0
|
||||
y2, x2 = y, x
|
||||
while y2 < h and x2 < w:
|
||||
c2 = matrix[y2][x2]
|
||||
if (y2, x2) in visited and c2 not in "L7":
|
||||
crosses += 1
|
||||
x2 += 1
|
||||
y2 += 1
|
||||
if crosses % 2 == 1:
|
||||
p2 += 1
|
||||
|
||||
return p1, p2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/10.txt", "r") as f:
|
||||
inp = f.read()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 6846
|
||||
assert b == 325
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
from itertools import combinations
|
||||
from output import answer
|
||||
|
||||
n = 11
|
||||
title = "Cosmic Expansion"
|
||||
|
||||
|
||||
@answer(1, "Sum of all galaxy shortest distances is {}")
|
||||
def part_1(data):
|
||||
return data[0]
|
||||
|
||||
|
||||
@answer(2, "Exapanding by 1M, sum is {}")
|
||||
def part_2(data):
|
||||
return data[1]
|
||||
|
||||
|
||||
def presolve(data):
|
||||
m = data.splitlines()
|
||||
er = set()
|
||||
ec = set()
|
||||
for i, r in enumerate(m):
|
||||
if "#" not in r:
|
||||
er.add(i)
|
||||
for i, c in enumerate(zip(*m)):
|
||||
if "#" not in c:
|
||||
ec.add(i)
|
||||
h = len(m)
|
||||
w = len(m[0])
|
||||
g1 = []
|
||||
g2 = []
|
||||
e = 1e6
|
||||
for r in range(h):
|
||||
for c in range(w):
|
||||
if m[r][c] == "#":
|
||||
ro = len(er & set(range(r)))
|
||||
co = len(ec & set(range(c)))
|
||||
g1.append((r + ro, c + co))
|
||||
g2.append((ro * e + r - ro, co * e + c - co))
|
||||
p1 = sum(
|
||||
abs(rc1[0] - rc2[0]) + abs(rc1[1] - rc2[1]) for rc1, rc2 in combinations(g1, 2)
|
||||
)
|
||||
p2 = int(
|
||||
sum(
|
||||
abs(rc1[0] - rc2[0]) + abs(rc1[1] - rc2[1])
|
||||
for rc1, rc2 in combinations(g2, 2)
|
||||
)
|
||||
)
|
||||
|
||||
return p1, p2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/11.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 9370588
|
||||
assert b == 746207878188
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
from functools import cache
|
||||
from output import answer
|
||||
|
||||
n = 12
|
||||
title = "Hot Springs"
|
||||
|
||||
|
||||
@answer(1, "sum of all possible combinations is {}")
|
||||
def part_1(presolved):
|
||||
return presolved[0]
|
||||
|
||||
|
||||
@answer(2, "sum of all possible combinations is {} when unfolded")
|
||||
def part_2(presolved):
|
||||
return presolved[1]
|
||||
|
||||
|
||||
def presolve(data):
|
||||
lines = [
|
||||
(a, list(map(int, b.split(","))))
|
||||
for a, b in (line.split() for line in data.splitlines())
|
||||
]
|
||||
p1 = sum(_inspect(a, tuple(b)) for a, b in lines)
|
||||
p2 = sum(_inspect("?".join([a] * 5), tuple(b * 5)) for a, b in lines)
|
||||
return p1, p2
|
||||
|
||||
|
||||
@cache
|
||||
def _inspect(s, cs):
|
||||
r = len(s)
|
||||
csl = len(cs)
|
||||
if r == 0:
|
||||
return 1 if csl == 0 else 0
|
||||
o, *f = s
|
||||
f = "".join(f)
|
||||
if o == ".":
|
||||
return _inspect(f, cs)
|
||||
if o == "?":
|
||||
return _inspect("." + f, cs) + _inspect("#" + f, cs)
|
||||
if not csl:
|
||||
return 0
|
||||
g = cs[0]
|
||||
if g > r or "." in s[0:g]:
|
||||
return 0
|
||||
elif csl > 1:
|
||||
if g + 1 > r or s[g] == "#":
|
||||
return 0
|
||||
else:
|
||||
return _inspect(s[g + 1 :], cs[1:])
|
||||
elif csl == 1:
|
||||
return _inspect(s[g:], ())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/12.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 7118
|
||||
assert b == 7030194981795
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
from output import answer
|
||||
|
||||
n = 13
|
||||
title = "Point of Incidence"
|
||||
|
||||
|
||||
@answer(1, "Summarizing the notes gives {}")
|
||||
def part_1(presolved):
|
||||
return presolved[0]
|
||||
|
||||
|
||||
@answer(2, "Summarizing the notes allowing off-by-1 gives {}")
|
||||
def part_2(presolved):
|
||||
return presolved[1]
|
||||
|
||||
|
||||
def presolve(data):
|
||||
g = [l.split() for l in data.split("\n\n")]
|
||||
|
||||
p1 = sum(d * n for d, n in _inspect(g))
|
||||
p2 = sum(d * n for d, n in _inspect(g, 1))
|
||||
|
||||
return p1, p2
|
||||
|
||||
|
||||
def _inspect(g, a=0):
|
||||
af = []
|
||||
for m in g:
|
||||
for d, n in [(100, m), (1, tuple(zip(*m)))]:
|
||||
af.append((d, _compare(n, a)))
|
||||
return af
|
||||
|
||||
|
||||
def _compare(l, a=0):
|
||||
for i in range(1, len(l)):
|
||||
if (
|
||||
sum(
|
||||
sum(a != b for a, b in zip(x, y)) for x, y in zip(l[i - 1 :: -1], l[i:])
|
||||
)
|
||||
== a
|
||||
):
|
||||
return i
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/13.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 29213
|
||||
assert b == 37453
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
from output import answer
|
||||
|
||||
n = 14
|
||||
title = "Parabolic Reflector Dish"
|
||||
|
||||
|
||||
@answer(1, "Total initial load on the northern beams: {}")
|
||||
def part_1(presolved):
|
||||
return presolved[0]
|
||||
|
||||
|
||||
@answer(2, "After some humble load testing, the northern beam load is {}")
|
||||
def part_2(presolved):
|
||||
return presolved[1]
|
||||
|
||||
|
||||
BAEST = 1000_000_000
|
||||
|
||||
|
||||
def presolve(data):
|
||||
m = [list(l) for l in data.split()]
|
||||
s = len(m[0])
|
||||
m1 = _tilt(m)
|
||||
|
||||
p1 = sum(sum((s - w) * o.count("O") for o in r) for w, r in enumerate(m1))
|
||||
|
||||
def impl(rc):
|
||||
return "".join(["".join(r) for r in rc])
|
||||
|
||||
i = 0
|
||||
seen = []
|
||||
while True:
|
||||
i += 1
|
||||
for _ in range(4):
|
||||
m = _tilt(m)
|
||||
m = _rotate(m)
|
||||
im = impl(m)
|
||||
if im in seen:
|
||||
break
|
||||
else:
|
||||
seen.append(im)
|
||||
m2 = m
|
||||
c = seen.index(im) + 1
|
||||
for _ in range((BAEST - i) % (i - c)):
|
||||
for j in range(4):
|
||||
m2 = _tilt(m2)
|
||||
m2 = _rotate(m2)
|
||||
p2 = sum(sum((s - w) * o.count("O") for o in r) for w, r in enumerate(m2))
|
||||
return p1, p2
|
||||
|
||||
|
||||
def _rotate(m):
|
||||
return [list(l) for l in zip(*m[::-1])]
|
||||
|
||||
|
||||
def _tilt(m):
|
||||
m = [list(l) for l in zip(*m)]
|
||||
h = len(m[0])
|
||||
for c in m:
|
||||
u = True
|
||||
while u:
|
||||
u = False
|
||||
for i in range(h - 1):
|
||||
j = i + 1
|
||||
if c[i] == "#" or c[j] == "#":
|
||||
continue
|
||||
if c[i] < c[j]:
|
||||
c[j], c[i] = c[i], c[j]
|
||||
u = True
|
||||
return [list(l) for l in zip(*m)]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/14.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 109596
|
||||
assert b == 96105
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
from collections import OrderedDict, defaultdict
|
||||
|
||||
from output import answer
|
||||
|
||||
n = 15
|
||||
title = "Lens Library"
|
||||
|
||||
|
||||
@answer(1, "Sum of HASH algorithm results: {}")
|
||||
def part_1(presolved):
|
||||
return presolved[0]
|
||||
|
||||
|
||||
@answer(2, "Focusing power of the resulting configuration: {}")
|
||||
def part_2(presolved):
|
||||
return presolved[1]
|
||||
|
||||
|
||||
def presolve(data):
|
||||
def h(s):
|
||||
v = 0
|
||||
for a in s:
|
||||
if a == "\n":
|
||||
continue
|
||||
v += ord(a)
|
||||
v *= 17
|
||||
v = v % 256
|
||||
return v
|
||||
|
||||
p1 = sum(h(c) for c in data.split(","))
|
||||
|
||||
b = defaultdict(OrderedDict)
|
||||
for lr in data.split(","):
|
||||
if "=" in lr:
|
||||
l, r = lr.split("=")
|
||||
if r == "":
|
||||
continue
|
||||
k = h(l)
|
||||
b[k][l] = r
|
||||
if "-" in lr:
|
||||
l, _r = lr.split("-")
|
||||
k = h(l)
|
||||
if l in b[k]:
|
||||
del b[k][l]
|
||||
p2 = 0
|
||||
for i, c in b.items():
|
||||
for j, f in enumerate(b[i].values(), 1):
|
||||
p2 += (i + 1) * j * int(f)
|
||||
|
||||
return p1, p2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/16.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 509784
|
||||
assert b == 230197
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
from itertools import chain
|
||||
|
||||
from output import D, answer, matrix
|
||||
|
||||
n = 16
|
||||
title = "The Floor Will Be Lava"
|
||||
|
||||
|
||||
@answer(1, "Energized tiles count, starting at top-left facing right: {}")
|
||||
def part_1(presolved):
|
||||
return presolved[0]
|
||||
|
||||
|
||||
@answer(2, "Max energized tiles count, starting from all edges: {}")
|
||||
def part_2(presolved):
|
||||
return presolved[1]
|
||||
|
||||
|
||||
def presolve(data):
|
||||
m, w, h = matrix(data)
|
||||
p1 = 0
|
||||
p2 = 0
|
||||
for sp in chain(
|
||||
[(h - 1, n, 0) for n in range(w)],
|
||||
[(n, 0, 1) for n in range(h)],
|
||||
[(0, n, 2) for n in range(w)],
|
||||
[(n, w - 1, 3) for n in range(h)],
|
||||
):
|
||||
q = [sp]
|
||||
seen = set()
|
||||
while q:
|
||||
rcd = q.pop(0)
|
||||
if (rcd) in seen:
|
||||
continue
|
||||
r, c, d = rcd
|
||||
if r < 0 or r >= h or c < 0 or c >= w:
|
||||
continue
|
||||
seen.add((r, c, d))
|
||||
match m[r][c]:
|
||||
case ".":
|
||||
o1, o2 = D[d]
|
||||
q.append((o1 + r, o2 + c, d))
|
||||
case "|":
|
||||
if d in [0, 2]:
|
||||
o1, o2 = D[d]
|
||||
q.append((o1 + r, o2 + c, d))
|
||||
else:
|
||||
for d in [(d - 1) % 4, (d + 1) % 4]:
|
||||
o1, o2 = D[d]
|
||||
q.append((o1 + r, o2 + c, d))
|
||||
case "-":
|
||||
if d in [1, 3]:
|
||||
o1, o2 = D[d]
|
||||
q.append((o1 + r, o2 + c, d))
|
||||
else:
|
||||
for d in [(d - 1) % 4, (d + 1) % 4]:
|
||||
o1, o2 = D[d]
|
||||
q.append((o1 + r, o2 + c, d))
|
||||
case "\\":
|
||||
d += 1 if d in [1, 3] else -1
|
||||
d = d % 4
|
||||
o1, o2 = D[d]
|
||||
q.append((o1 + r, o2 + c, d))
|
||||
case "/":
|
||||
d += 1 if d in [0, 2] else -1
|
||||
d = d % 4
|
||||
o1, o2 = D[d % 4]
|
||||
q.append((o1 + r, o2 + c, d))
|
||||
b = len(set([(r, c) for r, c, d in seen]))
|
||||
if sp == (0, 0, 1):
|
||||
p1 = b
|
||||
p2 = max(p2, b)
|
||||
return p1, p2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("./input/16.txt", "r") as f:
|
||||
inp = f.read().strip()
|
||||
|
||||
inp = presolve(inp)
|
||||
|
||||
a = part_1(inp)
|
||||
b = part_2(inp)
|
||||
|
||||
assert a == 7884
|
||||
assert b == 8185
|
||||
Loading…
Add table
Reference in a new issue