Poetry stuff.
This commit is contained in:
0
src/holt59/aoc/2021/__init__.py
Normal file
0
src/holt59/aoc/2021/__init__.py
Normal file
14
src/holt59/aoc/2021/day1.py
Normal file
14
src/holt59/aoc/2021/day1.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import sys
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
values = [int(line) for line in lines]
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(v2 > v1 for v1, v2 in zip(values[:-1], values[1:]))
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
runnings = [sum(values[i : i + 3]) for i in range(len(values) - 2)]
|
||||
answer_2 = sum(v2 > v1 for v1, v2 in zip(runnings[:-1], runnings[1:]))
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day10.py
Normal file
13
src/holt59/aoc/2021/day10.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day11.py
Normal file
13
src/holt59/aoc/2021/day11.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day12.py
Normal file
13
src/holt59/aoc/2021/day12.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day13.py
Normal file
13
src/holt59/aoc/2021/day13.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day14.py
Normal file
13
src/holt59/aoc/2021/day14.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day15.py
Normal file
13
src/holt59/aoc/2021/day15.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day16.py
Normal file
13
src/holt59/aoc/2021/day16.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day17.py
Normal file
13
src/holt59/aoc/2021/day17.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day18.py
Normal file
13
src/holt59/aoc/2021/day18.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day19.py
Normal file
13
src/holt59/aoc/2021/day19.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
40
src/holt59/aoc/2021/day2.py
Normal file
40
src/holt59/aoc/2021/day2.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import sys
|
||||
from math import prod
|
||||
from typing import Literal, cast
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
commands = [
|
||||
(cast(Literal["forward", "up", "down"], (p := line.split())[0]), int(p[1]))
|
||||
for line in lines
|
||||
]
|
||||
|
||||
|
||||
def depth_and_position(use_aim: bool):
|
||||
aim, pos, depth = 0, 0, 0
|
||||
for command, value in commands:
|
||||
d_depth = 0
|
||||
match command:
|
||||
case "forward":
|
||||
pos += value
|
||||
depth += value * aim
|
||||
case "up":
|
||||
d_depth = -value
|
||||
case "down":
|
||||
d_depth = value
|
||||
|
||||
if use_aim:
|
||||
aim += d_depth
|
||||
else:
|
||||
depth += value
|
||||
|
||||
return depth, pos
|
||||
|
||||
|
||||
# part 1
|
||||
answer_1 = prod(depth_and_position(False))
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = prod(depth_and_position(True))
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day20.py
Normal file
13
src/holt59/aoc/2021/day20.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day21.py
Normal file
13
src/holt59/aoc/2021/day21.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day22.py
Normal file
13
src/holt59/aoc/2021/day22.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day23.py
Normal file
13
src/holt59/aoc/2021/day23.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day24.py
Normal file
13
src/holt59/aoc/2021/day24.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day25.py
Normal file
13
src/holt59/aoc/2021/day25.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
39
src/holt59/aoc/2021/day3.py
Normal file
39
src/holt59/aoc/2021/day3.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import sys
|
||||
from collections import Counter
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def generator_rating(
|
||||
values: list[str], most_common: bool, default: Literal["0", "1"]
|
||||
) -> str:
|
||||
index = 0
|
||||
most_common_idx = 0 if most_common else 1
|
||||
|
||||
while len(values) > 1:
|
||||
cnt = Counter(value[index] for value in values)
|
||||
bit = cnt.most_common(2)[most_common_idx][0]
|
||||
if cnt["0"] == cnt["1"]:
|
||||
bit = default
|
||||
values = [value for value in values if value[index] == bit]
|
||||
index += 1
|
||||
|
||||
return values[0]
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
|
||||
# part 1
|
||||
most_and_least_common = [
|
||||
tuple(Counter(line[col] for line in lines).most_common(2)[m][0] for m in range(2))
|
||||
for col in range(len(lines[0]))
|
||||
]
|
||||
gamma_rate = int("".join(most for most, _ in most_and_least_common), base=2)
|
||||
epsilon_rate = int("".join(least for _, least in most_and_least_common), base=2)
|
||||
print(f"answer 1 is {gamma_rate * epsilon_rate}")
|
||||
|
||||
# part 2
|
||||
oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2)
|
||||
co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2)
|
||||
answer_2 = oxygen_generator_rating * co2_scrubber_rating
|
||||
print(f"answer 2 is {answer_2}")
|
45
src/holt59/aoc/2021/day4.py
Normal file
45
src/holt59/aoc/2021/day4.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
numbers = [int(c) for c in lines[0].split(",")]
|
||||
|
||||
boards = np.asarray(
|
||||
[
|
||||
[[int(c) for c in line.split()] for line in lines[start : start + 5]]
|
||||
for start in range(2, len(lines), 6)
|
||||
]
|
||||
)
|
||||
|
||||
# (round, score) for each board (-1 when not found)
|
||||
winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))]
|
||||
marked = np.zeros_like(boards, dtype=bool)
|
||||
|
||||
for round, number in enumerate(numbers):
|
||||
# mark boards
|
||||
marked[boards == number] = True
|
||||
|
||||
# check each board for winning
|
||||
for index in range(len(boards)):
|
||||
if winning_rounds[index][0] > 0:
|
||||
continue
|
||||
|
||||
if np.any(np.all(marked[index], axis=0) | np.all(marked[index], axis=1)):
|
||||
winning_rounds[index] = (
|
||||
round,
|
||||
number * int(np.sum(boards[index][~marked[index]])),
|
||||
)
|
||||
|
||||
# all boards are winning - break
|
||||
if np.all(marked.all(axis=1) | marked.all(axis=2)):
|
||||
break
|
||||
|
||||
# part 1
|
||||
(_, score) = min(winning_rounds, key=lambda w: w[0])
|
||||
print(f"answer 1 is {score}")
|
||||
|
||||
# part 2
|
||||
(_, score) = max(winning_rounds, key=lambda w: w[0])
|
||||
print(f"answer 2 is {score}")
|
48
src/holt59/aoc/2021/day5.py
Normal file
48
src/holt59/aoc/2021/day5.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
lines: list[str] = sys.stdin.read().splitlines()
|
||||
|
||||
sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
|
||||
(
|
||||
(
|
||||
int(line.split(" -> ")[0].split(",")[0]),
|
||||
int(line.split(" -> ")[0].split(",")[1]),
|
||||
),
|
||||
(
|
||||
int(line.split(" -> ")[1].split(",")[0]),
|
||||
int(line.split(" -> ")[1].split(",")[1]),
|
||||
),
|
||||
)
|
||||
for line in lines
|
||||
]
|
||||
|
||||
np_sections = np.array(sections).reshape(-1, 4)
|
||||
|
||||
x_min, x_max, y_min, y_max = (
|
||||
min(np_sections[:, 0].min(), np_sections[:, 2].min()),
|
||||
max(np_sections[:, 0].max(), np_sections[:, 2].max()),
|
||||
min(np_sections[:, 1].min(), np_sections[:, 3].min()),
|
||||
max(np_sections[:, 1].max(), np_sections[:, 3].max()),
|
||||
)
|
||||
|
||||
counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int)
|
||||
counts_2 = counts_1.copy()
|
||||
|
||||
for (x1, y1), (x2, y2) in sections:
|
||||
x_rng = range(x1, x2 + 1, 1) if x2 >= x1 else range(x1, x2 - 1, -1)
|
||||
y_rng = range(y1, y2 + 1, 1) if y2 >= y1 else range(y1, y2 - 1, -1)
|
||||
|
||||
if x1 == x2 or y1 == y2:
|
||||
counts_1[list(y_rng), list(x_rng)] += 1
|
||||
counts_2[list(y_rng), list(x_rng)] += 1
|
||||
elif abs(x2 - x1) == abs(y2 - y1):
|
||||
for i, j in zip(y_rng, x_rng):
|
||||
counts_2[i, j] += 1
|
||||
|
||||
answer_1 = (counts_1 >= 2).sum()
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
answer_2 = (counts_2 >= 2).sum()
|
||||
print(f"answer 2 is {answer_2}")
|
21
src/holt59/aoc/2021/day6.py
Normal file
21
src/holt59/aoc/2021/day6.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import sys
|
||||
|
||||
values = [int(c) for c in sys.stdin.read().strip().split(",")]
|
||||
|
||||
days = 256
|
||||
lanterns = {day: 0 for day in range(days)}
|
||||
for value in values:
|
||||
for day in range(value, days, 7):
|
||||
lanterns[day] += 1
|
||||
|
||||
for day in range(days):
|
||||
for day2 in range(day + 9, days, 7):
|
||||
lanterns[day2] += lanterns[day]
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(v for k, v in lanterns.items() if k < 80) + len(values)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = sum(lanterns.values()) + len(values)
|
||||
print(f"answer 2 is {answer_2}")
|
21
src/holt59/aoc/2021/day7.py
Normal file
21
src/holt59/aoc/2021/day7.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
positions = np.asarray([int(c) for c in sys.stdin.read().strip().split(",")])
|
||||
|
||||
min_position, max_position = positions.min(), positions.max()
|
||||
|
||||
# part 1
|
||||
answer_1 = min(
|
||||
np.sum(np.abs(positions - position))
|
||||
for position in range(min_position, max_position + 1)
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = min(
|
||||
np.sum(abs(positions - position) * (abs(positions - position) + 1) // 2)
|
||||
for position in range(min_position, max_position + 1)
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
87
src/holt59/aoc/2021/day8.py
Normal file
87
src/holt59/aoc/2021/day8.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import itertools
|
||||
import os
|
||||
import sys
|
||||
|
||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
||||
|
||||
digits = {
|
||||
"abcefg": 0,
|
||||
"cf": 1,
|
||||
"acdeg": 2,
|
||||
"acdfg": 3,
|
||||
"bcdf": 4,
|
||||
"abdfg": 5,
|
||||
"abdefg": 6,
|
||||
"acf": 7,
|
||||
"abcdefg": 8,
|
||||
"abcdfg": 9,
|
||||
}
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)}
|
||||
answer_1 = sum(
|
||||
len(p) in lengths for line in lines for p in line.split("|")[1].strip().split()
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
values: list[int] = []
|
||||
|
||||
for line in lines:
|
||||
parts = line.split("|")
|
||||
broken_digits = sorted(parts[0].strip().split(), key=len)
|
||||
|
||||
per_length = {
|
||||
k: list(v)
|
||||
for k, v in itertools.groupby(sorted(broken_digits, key=len), key=len)
|
||||
}
|
||||
|
||||
# a can be found immediately
|
||||
a = next(u for u in per_length[3][0] if u not in per_length[2][0])
|
||||
|
||||
# c and f have only two possible values corresponding to the single entry of
|
||||
# length 2
|
||||
cf = list(per_length[2][0])
|
||||
|
||||
# the only digit of length 4 contains bcdf, so we can deduce bd by removing cf
|
||||
bd = [u for u in per_length[4][0] if u not in cf]
|
||||
|
||||
# the 3 digits of length 5 have a, d and g in common
|
||||
adg = [u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])]
|
||||
|
||||
# we can remove a
|
||||
dg = [u for u in adg if u != a]
|
||||
|
||||
# we can deduce d and g
|
||||
d = next(u for u in dg if u in bd)
|
||||
g = next(u for u in dg if u != d)
|
||||
|
||||
# then b
|
||||
b = next(u for u in bd if u != d)
|
||||
|
||||
# f is in the three 6-length digits, while c is only in 2
|
||||
f = next(u for u in cf if all(u in p for p in per_length[6]))
|
||||
|
||||
# c is not f
|
||||
c = next(u for u in cf if u != f)
|
||||
|
||||
# e is the last one
|
||||
e = next(u for u in "abcdefg" if u not in {a, b, c, d, f, g})
|
||||
|
||||
mapping = dict(zip((a, b, c, d, e, f, g), "abcdefg"))
|
||||
|
||||
value = 0
|
||||
for number in parts[1].strip().split():
|
||||
digit = "".join(sorted(mapping[c] for c in number))
|
||||
value = 10 * value + digits[digit]
|
||||
|
||||
if VERBOSE:
|
||||
print(value)
|
||||
|
||||
values.append(value)
|
||||
|
||||
|
||||
answer_2 = sum(values)
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2021/day9.py
Normal file
13
src/holt59/aoc/2021/day9.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
0
src/holt59/aoc/2022/__init__.py
Normal file
0
src/holt59/aoc/2022/__init__.py
Normal file
7
src/holt59/aoc/2022/day1.py
Normal file
7
src/holt59/aoc/2022/day1.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import sys
|
||||
|
||||
blocks = sys.stdin.read().split("\n\n")
|
||||
values = sorted(sum(map(int, block.split())) for block in blocks)
|
||||
|
||||
print(f"answer 1 is {values[-1]}")
|
||||
print(f"answer 2 is {sum(values[-3:])}")
|
38
src/holt59/aoc/2022/day10.py
Normal file
38
src/holt59/aoc/2022/day10.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import sys
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
cycle = 1
|
||||
x = 1
|
||||
|
||||
values = {cycle: x}
|
||||
|
||||
for line in lines:
|
||||
cycle += 1
|
||||
|
||||
if line == "noop":
|
||||
pass
|
||||
else:
|
||||
r = int(line.split()[1])
|
||||
|
||||
values[cycle] = x
|
||||
|
||||
cycle += 1
|
||||
x += r
|
||||
|
||||
values[cycle] = x
|
||||
|
||||
answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40))
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
|
||||
for i in range(6):
|
||||
for j in range(40):
|
||||
v = values[1 + i * 40 + j]
|
||||
|
||||
if j >= v - 1 and j <= v + 1:
|
||||
print("#", end="")
|
||||
else:
|
||||
print(".", end="")
|
||||
|
||||
print()
|
142
src/holt59/aoc/2022/day11.py
Normal file
142
src/holt59/aoc/2022/day11.py
Normal file
@@ -0,0 +1,142 @@
|
||||
import copy
|
||||
import sys
|
||||
from functools import reduce
|
||||
from typing import Callable, Final, Mapping, Sequence
|
||||
|
||||
|
||||
class Monkey:
|
||||
id: Final[int]
|
||||
items: Final[Sequence[int]]
|
||||
worry_fn: Final[Callable[[int], int]]
|
||||
test_value: Final[int]
|
||||
throw_targets: Final[Mapping[bool, int]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
id: int,
|
||||
items: list[int],
|
||||
worry_fn: Callable[[int], int],
|
||||
test_value: int,
|
||||
throw_targets: dict[bool, int],
|
||||
):
|
||||
self.id = id
|
||||
self.items = items
|
||||
self.worry_fn = worry_fn
|
||||
self.test_value = test_value
|
||||
self.throw_targets = throw_targets
|
||||
|
||||
def __eq__(self, o: object) -> bool:
|
||||
if not isinstance(o, Monkey):
|
||||
return False
|
||||
return self.id == o.id
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.id)
|
||||
|
||||
|
||||
def parse_monkey(lines: list[str]) -> Monkey:
|
||||
assert lines[0].startswith("Monkey")
|
||||
|
||||
monkey_id = int(lines[0].split()[-1][:-1])
|
||||
|
||||
# parse items
|
||||
items = [int(r.strip()) for r in lines[1].split(":")[1].split(",")]
|
||||
|
||||
# parse worry
|
||||
worry_fn: Callable[[int], int]
|
||||
worry_s = lines[2].split("new =")[1].strip()
|
||||
operand = worry_s.split()[2].strip()
|
||||
|
||||
if worry_s.startswith("old *"):
|
||||
if operand == "old":
|
||||
worry_fn = lambda w: w * w # noqa: E731
|
||||
else:
|
||||
worry_fn = lambda w: w * int(operand) # noqa: E731
|
||||
elif worry_s.startswith("old +"):
|
||||
if operand == "old":
|
||||
worry_fn = lambda w: w + w # noqa: E731
|
||||
else:
|
||||
worry_fn = lambda w: w + int(operand) # noqa: E731
|
||||
else:
|
||||
assert False, worry_s
|
||||
|
||||
# parse test
|
||||
assert lines[3].split(":")[1].strip().startswith("divisible by")
|
||||
test_value = int(lines[3].split()[-1])
|
||||
|
||||
assert lines[4].strip().startswith("If true")
|
||||
assert lines[5].strip().startswith("If false")
|
||||
throw_targets = {True: int(lines[4].split()[-1]), False: int(lines[5].split()[-1])}
|
||||
|
||||
assert monkey_id not in throw_targets.values()
|
||||
|
||||
return Monkey(monkey_id, items, worry_fn, test_value, throw_targets)
|
||||
|
||||
|
||||
def run(
|
||||
monkeys: list[Monkey], n_rounds: int, me_worry_fn: Callable[[int], int]
|
||||
) -> dict[Monkey, int]:
|
||||
"""
|
||||
Perform a full run.
|
||||
|
||||
Args:
|
||||
monkeys: Initial list of monkeys. The Monkey are not modified.
|
||||
n_rounds: Number of rounds to run.
|
||||
me_worry_fn: Worry function to apply after the Monkey operation (e.g., divide
|
||||
by 3 for round 1).
|
||||
|
||||
Returns:
|
||||
A mapping containing, for each monkey, the number of items inspected.
|
||||
"""
|
||||
# copy of the items
|
||||
items = {monkey: list(monkey.items) for monkey in monkeys}
|
||||
|
||||
# number of inspects
|
||||
inspects = {monkey: 0 for monkey in monkeys}
|
||||
|
||||
for _ in range(n_rounds):
|
||||
for monkey in monkeys:
|
||||
for item in items[monkey]:
|
||||
inspects[monkey] += 1
|
||||
|
||||
# compute the new worry level
|
||||
item = me_worry_fn(monkey.worry_fn(item))
|
||||
|
||||
# find the target
|
||||
target = monkey.throw_targets[item % monkey.test_value == 0]
|
||||
assert target != monkey.id
|
||||
|
||||
items[monkeys[target]].append(item)
|
||||
|
||||
# clear after the loop
|
||||
items[monkey].clear()
|
||||
|
||||
return inspects
|
||||
|
||||
|
||||
def monkey_business(inspects: dict[Monkey, int]) -> int:
|
||||
sorted_levels = sorted(inspects.values())
|
||||
return sorted_levels[-2] * sorted_levels[-1]
|
||||
|
||||
|
||||
monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")]
|
||||
|
||||
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
|
||||
answer_1 = monkey_business(
|
||||
run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3)
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
|
||||
# use the product of all "divisible by" test so that the test remains valid
|
||||
#
|
||||
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
|
||||
#
|
||||
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
|
||||
# we use the product of all test value
|
||||
#
|
||||
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
|
||||
answer_2 = monkey_business(
|
||||
run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value)
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
160
src/holt59/aoc/2022/day12.py
Normal file
160
src/holt59/aoc/2022/day12.py
Normal file
@@ -0,0 +1,160 @@
|
||||
import heapq
|
||||
import sys
|
||||
from typing import Callable, Iterator, TypeVar
|
||||
|
||||
Node = TypeVar("Node")
|
||||
|
||||
|
||||
def dijkstra(
|
||||
start: Node,
|
||||
neighbors: Callable[[Node], Iterator[Node]],
|
||||
cost: Callable[[Node, Node], float],
|
||||
) -> tuple[dict[Node, float], dict[Node, Node]]:
|
||||
"""
|
||||
Compute shortest paths from one node to all reachable ones.
|
||||
|
||||
Args:
|
||||
start: Starting node.
|
||||
neighbors: Function returning the neighbors of a node.
|
||||
cost: Function to compute the cost of an edge.
|
||||
|
||||
Returns:
|
||||
A tuple (lengths, parents) where lengths is a mapping from Node to distance
|
||||
(from the starting node) and parents a mapping from parents Node (in the
|
||||
shortest path). If keyset of lengths and parents is the same. If a Node is not
|
||||
in the mapping, it cannot be reached from the starting node.
|
||||
"""
|
||||
|
||||
queue: list[tuple[float, Node]] = []
|
||||
|
||||
visited: set[Node] = set()
|
||||
lengths: dict[Node, float] = {start: 0}
|
||||
parents: dict[Node, Node] = {}
|
||||
|
||||
heapq.heappush(queue, (0, start))
|
||||
|
||||
while queue:
|
||||
length, current = heapq.heappop(queue)
|
||||
|
||||
if current in visited:
|
||||
continue
|
||||
|
||||
visited.add(current)
|
||||
|
||||
for neighbor in neighbors(current):
|
||||
if neighbor in visited:
|
||||
continue
|
||||
|
||||
neighbor_cost = length + cost(current, neighbor)
|
||||
|
||||
if neighbor_cost < lengths.get(neighbor, float("inf")):
|
||||
lengths[neighbor] = neighbor_cost
|
||||
parents[neighbor] = current
|
||||
|
||||
heapq.heappush(queue, (neighbor_cost, neighbor))
|
||||
|
||||
return lengths, parents
|
||||
|
||||
|
||||
def make_path(parents: dict[Node, Node], start: Node, end: Node) -> list[Node] | None:
|
||||
if end not in parents:
|
||||
return None
|
||||
|
||||
path: list[Node] = [end]
|
||||
|
||||
while path[-1] is not start:
|
||||
path.append(parents[path[-1]])
|
||||
|
||||
return list(reversed(path))
|
||||
|
||||
|
||||
def print_path(path: list[tuple[int, int]], n_rows: int, n_cols: int) -> None:
|
||||
end = path[-1]
|
||||
|
||||
graph = [["." for _c in range(n_cols)] for _r in range(n_rows)]
|
||||
graph[end[0]][end[1]] = "E"
|
||||
|
||||
for i in range(0, len(path) - 1):
|
||||
cr, cc = path[i]
|
||||
nr, nc = path[i + 1]
|
||||
|
||||
if cr == nr and nc == cc - 1:
|
||||
graph[cr][cc] = "<"
|
||||
elif cr == nr and nc == cc + 1:
|
||||
graph[cr][cc] = ">"
|
||||
elif cr == nr - 1 and nc == cc:
|
||||
graph[cr][cc] = "v"
|
||||
elif cr == nr + 1 and nc == cc:
|
||||
graph[cr][cc] = "^"
|
||||
else:
|
||||
assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
|
||||
|
||||
print("\n".join("".join(row) for row in graph))
|
||||
|
||||
|
||||
def neighbors(
|
||||
grid: list[list[int]], node: tuple[int, int], up: bool
|
||||
) -> Iterator[tuple[int, int]]:
|
||||
n_rows = len(grid)
|
||||
n_cols = len(grid[0])
|
||||
|
||||
c_row, c_col = node
|
||||
for n_row, n_col in (
|
||||
(c_row - 1, c_col),
|
||||
(c_row + 1, c_col),
|
||||
(c_row, c_col - 1),
|
||||
(c_row, c_col + 1),
|
||||
):
|
||||
if not (n_row >= 0 and n_row < n_rows and n_col >= 0 and n_col < n_cols):
|
||||
continue
|
||||
|
||||
if up and grid[n_row][n_col] > grid[c_row][c_col] + 1:
|
||||
continue
|
||||
elif not up and grid[n_row][n_col] < grid[c_row][c_col] - 1:
|
||||
continue
|
||||
|
||||
yield n_row, n_col
|
||||
|
||||
|
||||
# === main code ===
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
|
||||
|
||||
start: tuple[int, int]
|
||||
end: tuple[int, int]
|
||||
|
||||
# for part 2
|
||||
start_s: list[tuple[int, int]] = []
|
||||
|
||||
for i_row, row in enumerate(grid):
|
||||
for i_col, col in enumerate(row):
|
||||
if chr(col + ord("a")) == "S":
|
||||
start = (i_row, i_col)
|
||||
start_s.append(start)
|
||||
elif chr(col + ord("a")) == "E":
|
||||
end = (i_row, i_col)
|
||||
elif col == 0:
|
||||
start_s.append((i_row, i_col))
|
||||
|
||||
# fix values
|
||||
grid[start[0]][start[1]] = 0
|
||||
grid[end[0]][end[1]] = ord("z") - ord("a")
|
||||
|
||||
|
||||
lengths_1, parents_1 = dijkstra(
|
||||
start=start, neighbors=lambda n: neighbors(grid, n, True), cost=lambda lhs, rhs: 1
|
||||
)
|
||||
path_1 = make_path(parents_1, start, end)
|
||||
assert path_1 is not None
|
||||
|
||||
print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
|
||||
|
||||
print(f"answer 1 is {lengths_1[end] - 1}")
|
||||
|
||||
lengths_2, parents_2 = dijkstra(
|
||||
start=end, neighbors=lambda n: neighbors(grid, n, False), cost=lambda lhs, rhs: 1
|
||||
)
|
||||
answer_2 = min(lengths_2.get(start, float("inf")) for start in start_s)
|
||||
print(f"answer 2 is {answer_2}")
|
41
src/holt59/aoc/2022/day13.py
Normal file
41
src/holt59/aoc/2022/day13.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import json
|
||||
import sys
|
||||
from functools import cmp_to_key
|
||||
from typing import TypeAlias, cast
|
||||
|
||||
blocks = sys.stdin.read().strip().split("\n\n")
|
||||
|
||||
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
|
||||
|
||||
Packet: TypeAlias = list[int | list["Packet"]]
|
||||
|
||||
|
||||
def compare(lhs: Packet, rhs: Packet) -> int:
|
||||
for lhs_a, rhs_a in zip(lhs, rhs):
|
||||
if isinstance(lhs_a, int) and isinstance(rhs_a, int):
|
||||
if lhs_a != rhs_a:
|
||||
return rhs_a - lhs_a
|
||||
else:
|
||||
if not isinstance(lhs_a, list):
|
||||
lhs_a = [lhs_a] # type: ignore
|
||||
elif not isinstance(rhs_a, list):
|
||||
rhs_a = [rhs_a] # type: ignore
|
||||
assert isinstance(rhs_a, list) and isinstance(lhs_a, list)
|
||||
r = compare(cast(Packet, lhs_a), cast(Packet, rhs_a))
|
||||
if r != 0:
|
||||
return r
|
||||
|
||||
return len(rhs) - len(lhs)
|
||||
|
||||
|
||||
answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
|
||||
print(f"answer_1 is {answer_1}")
|
||||
|
||||
dividers = [[[2]], [[6]]]
|
||||
|
||||
packets = [packet for packets in pairs for packet in packets]
|
||||
packets.extend(dividers)
|
||||
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
|
||||
|
||||
d_index = [packets.index(d) + 1 for d in dividers]
|
||||
print(f"answer 2 is {d_index[0] * d_index[1]}")
|
140
src/holt59/aoc/2022/day14.py
Normal file
140
src/holt59/aoc/2022/day14.py
Normal file
@@ -0,0 +1,140 @@
|
||||
import sys
|
||||
from enum import Enum, auto
|
||||
from typing import Callable, cast
|
||||
|
||||
|
||||
class Cell(Enum):
|
||||
AIR = auto()
|
||||
ROCK = auto()
|
||||
SAND = auto()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return {Cell.AIR: ".", Cell.ROCK: "#", Cell.SAND: "O"}[self]
|
||||
|
||||
|
||||
def print_blocks(blocks: dict[tuple[int, int], Cell]):
|
||||
"""
|
||||
Print the given set of blocks on a grid.
|
||||
|
||||
Args:
|
||||
blocks: Set of blocks to print.
|
||||
"""
|
||||
x_min, y_min, x_max, y_max = (
|
||||
min(x for x, _ in blocks),
|
||||
0,
|
||||
max(x for x, _ in blocks),
|
||||
max(y for _, y in blocks),
|
||||
)
|
||||
|
||||
for y in range(y_min, y_max + 1):
|
||||
print(
|
||||
"".join(str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1))
|
||||
)
|
||||
|
||||
|
||||
def flow(
|
||||
blocks: dict[tuple[int, int], Cell],
|
||||
stop_fn: Callable[[int, int], bool],
|
||||
fill_fn: Callable[[int, int], Cell],
|
||||
) -> dict[tuple[int, int], Cell]:
|
||||
"""
|
||||
Flow sands onto the given set of blocks
|
||||
|
||||
Args:
|
||||
blocks: Blocks containing ROCK position. Modified in-place.
|
||||
stop_fn: Function called with the last (assumed) position of a grain of
|
||||
sand BEFORE adding it to blocks. If the function returns True, the grain
|
||||
is added and a new one is flowed, otherwise, the whole procedure stops
|
||||
and the function returns (without adding the final grain).
|
||||
fill_fn: Function called when the target position of a grain (during the
|
||||
flowing process) is missing from blocks.
|
||||
|
||||
Returns:
|
||||
The input blocks.
|
||||
"""
|
||||
|
||||
y_max = max(y for _, y in blocks)
|
||||
|
||||
while True:
|
||||
x, y = 500, 0
|
||||
|
||||
while y <= y_max:
|
||||
moved = False
|
||||
for cx, cy in ((x, y + 1), (x - 1, y + 1), (x + 1, y + 1)):
|
||||
if (cx, cy) not in blocks and fill_fn(cx, cy) == Cell.AIR:
|
||||
x, y = cx, cy
|
||||
moved = True
|
||||
elif blocks[cx, cy] == Cell.AIR:
|
||||
x, y = cx, cy
|
||||
moved = True
|
||||
|
||||
if moved:
|
||||
break
|
||||
|
||||
if not moved:
|
||||
break
|
||||
|
||||
if stop_fn(x, y):
|
||||
break
|
||||
|
||||
blocks[x, y] = Cell.SAND
|
||||
|
||||
return blocks
|
||||
|
||||
|
||||
# === inputs ===
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
paths: list[list[tuple[int, int]]] = []
|
||||
for line in lines:
|
||||
parts = line.split(" -> ")
|
||||
paths.append(
|
||||
[
|
||||
cast(tuple[int, int], tuple(int(c.strip()) for c in part.split(",")))
|
||||
for part in parts
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
blocks: dict[tuple[int, int], Cell] = {}
|
||||
for path in paths:
|
||||
for start, end in zip(path[:-1], path[1:]):
|
||||
x_start = min(start[0], end[0])
|
||||
x_end = max(start[0], end[0]) + 1
|
||||
y_start = min(start[1], end[1])
|
||||
y_end = max(start[1], end[1]) + 1
|
||||
|
||||
for x in range(x_start, x_end):
|
||||
for y in range(y_start, y_end):
|
||||
blocks[x, y] = Cell.ROCK
|
||||
|
||||
print_blocks(blocks)
|
||||
print()
|
||||
|
||||
x_min, y_min, x_max, y_max = (
|
||||
min(x for x, _ in blocks),
|
||||
0,
|
||||
max(x for x, _ in blocks),
|
||||
max(y for _, y in blocks),
|
||||
)
|
||||
|
||||
# === part 1 ===
|
||||
|
||||
blocks_1 = flow(
|
||||
blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
|
||||
)
|
||||
print_blocks(blocks_1)
|
||||
print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}")
|
||||
print()
|
||||
|
||||
# === part 2 ===
|
||||
|
||||
blocks_2 = flow(
|
||||
blocks.copy(),
|
||||
stop_fn=lambda x, y: x == 500 and y == 0,
|
||||
fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
|
||||
)
|
||||
blocks_2[500, 0] = Cell.SAND
|
||||
print_blocks(blocks_2)
|
||||
print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}")
|
87
src/holt59/aoc/2022/day15.py
Normal file
87
src/holt59/aoc/2022/day15.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import parse
|
||||
|
||||
|
||||
def part1(sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], row: int) -> int:
|
||||
no_beacons_row_l: list[np.ndarray] = []
|
||||
|
||||
for (sx, sy), (bx, by) in sensor_to_beacon.items():
|
||||
d = abs(sx - bx) + abs(sy - by) # closest
|
||||
|
||||
no_beacons_row_l.append(sx - np.arange(0, d - abs(sy - row) + 1))
|
||||
no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1))
|
||||
|
||||
beacons_at_row = set(bx for (bx, by) in sensor_to_beacon.values() if by == row)
|
||||
no_beacons_row = set(np.concatenate(no_beacons_row_l)).difference(beacons_at_row)
|
||||
|
||||
return len(no_beacons_row)
|
||||
|
||||
|
||||
def part2_intervals(
|
||||
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
|
||||
) -> tuple[int, int, int]:
|
||||
from tqdm import trange
|
||||
|
||||
for y in trange(xy_max + 1):
|
||||
its: list[tuple[int, int]] = []
|
||||
for (sx, sy), (bx, by) in sensor_to_beacon.items():
|
||||
d = abs(sx - bx) + abs(sy - by)
|
||||
dx = d - abs(sy - y)
|
||||
|
||||
if dx >= 0:
|
||||
its.append((max(0, sx - dx), min(sx + dx, xy_max)))
|
||||
|
||||
its = sorted(its)
|
||||
_, e = its[0]
|
||||
|
||||
for si, ei in its[1:]:
|
||||
if si > e + 1:
|
||||
return si - 1, y, 4_000_000 * (si - 1) + y
|
||||
if ei > e:
|
||||
e = ei
|
||||
|
||||
return (0, 0, 0)
|
||||
|
||||
|
||||
def part2_cplex(
|
||||
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
|
||||
) -> tuple[int, int, int]:
|
||||
from docplex.mp.model import Model
|
||||
|
||||
m = Model()
|
||||
|
||||
x, y = m.continuous_var_list(2, ub=xy_max, name=["x", "y"])
|
||||
|
||||
for (sx, sy), (bx, by) in sensor_to_beacon.items():
|
||||
d = abs(sx - bx) + abs(sy - by)
|
||||
m.add_constraint(m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}")
|
||||
|
||||
m.set_objective("min", x + y)
|
||||
|
||||
s = m.solve()
|
||||
|
||||
vx = int(s.get_value(x))
|
||||
vy = int(s.get_value(y))
|
||||
return vx, vy, 4_000_000 * vx + vy
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]] = {}
|
||||
|
||||
for line in lines:
|
||||
r = parse.parse(
|
||||
"Sensor at x={sx}, y={sy}: closest beacon is at x={bx}, y={by}", line
|
||||
)
|
||||
sensor_to_beacon[int(r["sx"]), int(r["sy"])] = (int(r["bx"]), int(r["by"]))
|
||||
|
||||
xy_max = 4_000_000 if max(sensor_to_beacon) > (1_000, 0) else 20
|
||||
row = 2_000_000 if max(sensor_to_beacon) > (1_000, 0) else 10
|
||||
|
||||
print(f"answer 1 is {part1(sensor_to_beacon, row)}")
|
||||
|
||||
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
|
||||
x, y, a2 = part2_intervals(sensor_to_beacon, xy_max)
|
||||
print(f"answer 2 is {a2} (x={x}, y={y})")
|
158
src/holt59/aoc/2022/day16.py
Normal file
158
src/holt59/aoc/2022/day16.py
Normal file
@@ -0,0 +1,158 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import heapq
|
||||
import itertools
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from typing import FrozenSet, NamedTuple
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
class Pipe(NamedTuple):
|
||||
name: str
|
||||
flow: int
|
||||
tunnels: list[str]
|
||||
|
||||
def __lt__(self, other: object) -> bool:
|
||||
return isinstance(other, Pipe) and other.name < self.name
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, Pipe) and other.name == self.name
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.name)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
||||
def breadth_first_search(pipes: dict[str, Pipe], pipe: Pipe) -> dict[Pipe, int]:
|
||||
"""
|
||||
Runs a BFS from the given pipe and return the shortest distance (in term of hops)
|
||||
to all other pipes.
|
||||
"""
|
||||
queue = [(0, pipe_1)]
|
||||
visited = set()
|
||||
distances: dict[Pipe, int] = {}
|
||||
|
||||
while len(distances) < len(pipes):
|
||||
distance, current = heapq.heappop(queue)
|
||||
|
||||
if current in visited:
|
||||
continue
|
||||
|
||||
visited.add(current)
|
||||
distances[current] = distance
|
||||
|
||||
for tunnel in current.tunnels:
|
||||
heapq.heappush(queue, (distance + 1, pipes[tunnel]))
|
||||
|
||||
return distances
|
||||
|
||||
|
||||
def update_with_better(
|
||||
node_at_times: dict[FrozenSet[Pipe], int], flow: int, flowing: FrozenSet[Pipe]
|
||||
) -> None:
|
||||
node_at_times[flowing] = max(node_at_times[flowing], flow)
|
||||
|
||||
|
||||
def part_1(
|
||||
start_pipe: Pipe,
|
||||
max_time: int,
|
||||
distances: dict[tuple[Pipe, Pipe], int],
|
||||
relevant_pipes: FrozenSet[Pipe],
|
||||
):
|
||||
node_at_times: dict[int, dict[Pipe, dict[FrozenSet[Pipe], int]]] = defaultdict(
|
||||
lambda: defaultdict(lambda: defaultdict(lambda: 0))
|
||||
)
|
||||
node_at_times[0] = {start_pipe: {frozenset(): 0}}
|
||||
|
||||
for time in range(max_time):
|
||||
for c_pipe, nodes in node_at_times[time].items():
|
||||
for flowing, flow in nodes.items():
|
||||
for target in relevant_pipes:
|
||||
distance = distances[c_pipe, target] + 1
|
||||
if time + distance >= max_time or target in flowing:
|
||||
continue
|
||||
|
||||
update_with_better(
|
||||
node_at_times[time + distance][target],
|
||||
flow + sum(pipe.flow for pipe in flowing) * distance,
|
||||
flowing | {target},
|
||||
)
|
||||
|
||||
update_with_better(
|
||||
node_at_times[max_time][c_pipe],
|
||||
flow + sum(pipe.flow for pipe in flowing) * (max_time - time),
|
||||
flowing,
|
||||
)
|
||||
|
||||
return max(
|
||||
flow
|
||||
for nodes_of_pipe in node_at_times[max_time].values()
|
||||
for flow in nodes_of_pipe.values()
|
||||
)
|
||||
|
||||
|
||||
def part_2(
|
||||
start_pipe: Pipe,
|
||||
max_time: int,
|
||||
distances: dict[tuple[Pipe, Pipe], int],
|
||||
relevant_pipes: FrozenSet[Pipe],
|
||||
):
|
||||
def compute(pipes_for_me: FrozenSet[Pipe]) -> int:
|
||||
return part_1(start_pipe, max_time, distances, pipes_for_me) + part_1(
|
||||
start_pipe, max_time, distances, relevant_pipes - pipes_for_me
|
||||
)
|
||||
|
||||
combs = [
|
||||
frozenset(relevant_pipes_1)
|
||||
for r in range(2, len(relevant_pipes) // 2 + 1)
|
||||
for relevant_pipes_1 in itertools.combinations(relevant_pipes, r)
|
||||
]
|
||||
|
||||
return max(compute(comb) for comb in tqdm(combs))
|
||||
|
||||
|
||||
# === MAIN ===
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
|
||||
pipes: dict[str, Pipe] = {}
|
||||
for line in lines:
|
||||
r = re.match(
|
||||
R"Valve ([A-Z]+) has flow rate=([0-9]+); tunnels? leads? to valves? (.+)",
|
||||
line,
|
||||
)
|
||||
assert r
|
||||
|
||||
g = r.groups()
|
||||
|
||||
pipes[g[0]] = Pipe(g[0], int(g[1]), g[2].split(", "))
|
||||
|
||||
# compute distances from one valve to any other
|
||||
distances: dict[tuple[Pipe, Pipe], int] = {}
|
||||
for pipe_1 in pipes.values():
|
||||
distances.update(
|
||||
{
|
||||
(pipe_1, pipe_2): distance
|
||||
for pipe_2, distance in breadth_first_search(pipes, pipe_1).items()
|
||||
}
|
||||
)
|
||||
|
||||
# valves with flow
|
||||
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
|
||||
|
||||
|
||||
# 1651, 1653
|
||||
print(part_1(pipes["AA"], 30, distances, relevant_pipes))
|
||||
|
||||
# 1707, 2223
|
||||
print(part_2(pipes["AA"], 26, distances, relevant_pipes))
|
120
src/holt59/aoc/2022/day17.py
Normal file
120
src/holt59/aoc/2022/day17.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import sys
|
||||
from typing import Sequence, TypeVar
|
||||
|
||||
import numpy as np
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def print_tower(tower: np.ndarray, out: str = "#"):
|
||||
print("-" * (tower.shape[1] + 2))
|
||||
non_empty = False
|
||||
for row in reversed(range(1, tower.shape[0])):
|
||||
if not non_empty and not tower[row, :].any():
|
||||
continue
|
||||
non_empty = True
|
||||
print("|" + "".join(out if c else "." for c in tower[row, :]) + "|")
|
||||
print("+" + "-" * tower.shape[1] + "+")
|
||||
|
||||
|
||||
def tower_height(tower: np.ndarray) -> int:
|
||||
return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1)
|
||||
|
||||
|
||||
def next_cycle(sequence: Sequence[T], index: int) -> tuple[T, int]:
|
||||
t = sequence[index]
|
||||
index = (index + 1) % len(sequence)
|
||||
return t, index
|
||||
|
||||
|
||||
ROCKS = [
|
||||
np.array([(0, 0), (0, 1), (0, 2), (0, 3)]),
|
||||
np.array([(0, 1), (1, 0), (1, 1), (1, 2), (2, 1)]),
|
||||
np.array([(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)]),
|
||||
np.array([(0, 0), (1, 0), (2, 0), (3, 0)]),
|
||||
np.array([(0, 0), (0, 1), (1, 0), (1, 1)]),
|
||||
]
|
||||
|
||||
WIDTH = 7
|
||||
START_X = 2
|
||||
|
||||
EMPTY_BLOCKS = np.zeros((10, WIDTH), dtype=bool)
|
||||
|
||||
|
||||
def build_tower(
|
||||
n_rocks: int,
|
||||
jets: str,
|
||||
early_stop: bool = False,
|
||||
init: np.ndarray = np.ones(WIDTH, dtype=bool),
|
||||
) -> tuple[np.ndarray, int, int, dict[int, int]]:
|
||||
tower = EMPTY_BLOCKS.copy()
|
||||
tower[0, :] = init
|
||||
|
||||
done_at: dict[tuple[int, int], int] = {}
|
||||
heights: dict[int, int] = {}
|
||||
i_jet, i_rock = 0, 0
|
||||
rock_count = 0
|
||||
|
||||
for rock_count in range(n_rocks):
|
||||
if early_stop:
|
||||
if i_rock == 0 and (i_rock, i_jet) in done_at:
|
||||
break
|
||||
done_at[i_rock, i_jet] = rock_count
|
||||
|
||||
y_start = tower.shape[0] - tower[::-1, :].argmax(axis=0).min() + 3
|
||||
rock, i_rock = next_cycle(ROCKS, i_rock)
|
||||
|
||||
rock_y = rock[:, 0] + y_start
|
||||
rock_x = rock[:, 1] + START_X
|
||||
|
||||
if rock_y.max() >= tower.shape[0]:
|
||||
tower = np.concatenate([tower, EMPTY_BLOCKS], axis=0)
|
||||
|
||||
while True:
|
||||
jet, i_jet = next_cycle(jets, i_jet)
|
||||
|
||||
dx = 0
|
||||
if jet == ">" and rock_x.max() < WIDTH - 1:
|
||||
dx = 1
|
||||
elif jet == "<" and rock_x.min() > 0:
|
||||
dx = -1
|
||||
|
||||
if dx != 0 and not tower[rock_y, rock_x + dx].any():
|
||||
rock_x = rock_x + dx
|
||||
|
||||
# move down
|
||||
rock_y -= 1
|
||||
|
||||
if tower[rock_y, rock_x].any():
|
||||
rock_y += 1
|
||||
break
|
||||
|
||||
heights[rock_count] = tower_height(tower)
|
||||
tower[rock_y, rock_x] = True
|
||||
|
||||
return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights
|
||||
|
||||
|
||||
line = sys.stdin.read().strip()
|
||||
|
||||
tower, *_ = build_tower(2022, line)
|
||||
answer_1 = tower_height(tower)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
TOTAL_ROCKS = 1_000_000_000_000
|
||||
tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, line, True)
|
||||
assert prev_1 > 0
|
||||
|
||||
# 2767 1513
|
||||
remaining_rocks = TOTAL_ROCKS - n_rocks_1
|
||||
n_repeat_rocks = n_rocks_1 - prev_1
|
||||
n_repeat_towers = remaining_rocks // n_repeat_rocks
|
||||
|
||||
base_height = heights_1[prev_1]
|
||||
repeat_height = heights_1[prev_1 + n_repeat_rocks - 1] - heights_1[prev_1]
|
||||
remaining_height = (
|
||||
heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1]
|
||||
)
|
||||
|
||||
answer_2 = base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
|
||||
print(f"answer 2 is {answer_2}")
|
51
src/holt59/aoc/2022/day18.py
Normal file
51
src/holt59/aoc/2022/day18.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import sys
|
||||
from typing import FrozenSet
|
||||
|
||||
import numpy as np
|
||||
|
||||
xyz = np.asarray(
|
||||
[
|
||||
tuple(int(x) for x in row.split(",")) # type: ignore
|
||||
for row in sys.stdin.read().splitlines()
|
||||
]
|
||||
)
|
||||
|
||||
xyz = xyz - xyz.min(axis=0) + 1
|
||||
|
||||
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
|
||||
cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
|
||||
|
||||
n_dims = len(cubes.shape)
|
||||
|
||||
faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
|
||||
|
||||
answer_1 = sum(
|
||||
1 for x, y, z in xyz for dx, dy, dz in faces if not cubes[x + dx, y + dy, z + dz]
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
visited = np.zeros_like(cubes, dtype=bool)
|
||||
queue = [(0, 0, 0)]
|
||||
|
||||
n_faces = 0
|
||||
while queue:
|
||||
x, y, z = queue.pop(0)
|
||||
|
||||
if visited[x, y, z]:
|
||||
continue
|
||||
|
||||
visited[x, y, z] = True
|
||||
|
||||
for dx, dy, dz in faces:
|
||||
nx, ny, nz = x + dx, y + dy, z + dz
|
||||
if not all(n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))):
|
||||
continue
|
||||
|
||||
if visited[nx, ny, nz]:
|
||||
continue
|
||||
|
||||
if cubes[nx, ny, nz]:
|
||||
n_faces += 1
|
||||
else:
|
||||
queue.append((nx, ny, nz))
|
||||
print(f"answer 2 is {n_faces}")
|
182
src/holt59/aoc/2022/day19.py
Normal file
182
src/holt59/aoc/2022/day19.py
Normal file
@@ -0,0 +1,182 @@
|
||||
import sys
|
||||
from typing import Literal
|
||||
|
||||
import numpy as np
|
||||
import parse
|
||||
from tqdm import tqdm
|
||||
|
||||
Reagent = Literal["ore", "clay", "obsidian", "geode"]
|
||||
REAGENTS: tuple[Reagent, ...] = (
|
||||
"ore",
|
||||
"clay",
|
||||
"obsidian",
|
||||
"geode",
|
||||
)
|
||||
|
||||
IntOfReagent = dict[Reagent, int]
|
||||
|
||||
|
||||
class State:
|
||||
robots: IntOfReagent
|
||||
reagents: IntOfReagent
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
robots: IntOfReagent | None = None,
|
||||
reagents: IntOfReagent | None = None,
|
||||
):
|
||||
if robots is None:
|
||||
assert reagents is None
|
||||
self.reagents = {reagent: 0 for reagent in REAGENTS}
|
||||
self.robots = {reagent: 0 for reagent in REAGENTS}
|
||||
self.robots["ore"] = 1
|
||||
else:
|
||||
assert robots is not None and reagents is not None
|
||||
self.robots = robots
|
||||
self.reagents = reagents
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return (
|
||||
isinstance(other, State)
|
||||
and self.robots == other.robots
|
||||
and self.reagents == other.reagents
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(tuple((self.robots[r], self.reagents[r]) for r in REAGENTS))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "State({}, {})".format(
|
||||
"/".join(str(self.robots[k]) for k in REAGENTS),
|
||||
"/".join(str(self.reagents[k]) for k in REAGENTS),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self)
|
||||
|
||||
|
||||
def dominates(lhs: State, rhs: State):
|
||||
return all(
|
||||
lhs.robots[r] >= rhs.robots[r] and lhs.reagents[r] >= rhs.reagents[r]
|
||||
for r in REAGENTS
|
||||
)
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
blueprints: list[dict[Reagent, IntOfReagent]] = []
|
||||
for line in lines:
|
||||
r = parse.parse(
|
||||
"Blueprint {}: "
|
||||
"Each ore robot costs {:d} ore. "
|
||||
"Each clay robot costs {:d} ore. "
|
||||
"Each obsidian robot costs {:d} ore and {:d} clay. "
|
||||
"Each geode robot costs {:d} ore and {:d} obsidian.",
|
||||
line,
|
||||
)
|
||||
|
||||
blueprints.append(
|
||||
{
|
||||
"ore": {"ore": r[1]},
|
||||
"clay": {"ore": r[2]},
|
||||
"obsidian": {"ore": r[3], "clay": r[4]},
|
||||
"geode": {"ore": r[5], "obsidian": r[6]},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
|
||||
# since we can only build one robot per time, we do not need more than X robots
|
||||
# of type K where X is the maximum number of K required among all robots, e.g.,
|
||||
# in the first toy blueprint, we need at most 4 ore robots, 14 clay ones and 7
|
||||
# obsidian ones
|
||||
maximums = {
|
||||
name: max(blueprint[r].get(name, 0) for r in REAGENTS) for name in REAGENTS
|
||||
}
|
||||
|
||||
state_after_t: dict[int, set[State]] = {0: [State()]}
|
||||
|
||||
for t in range(1, max_time + 1):
|
||||
# list of new states at the end of step t that we are going to prune later
|
||||
states_for_t: set[State] = set()
|
||||
|
||||
for state in state_after_t[t - 1]:
|
||||
robots_that_can_be_built = [
|
||||
robot
|
||||
for robot in REAGENTS
|
||||
if all(
|
||||
state.reagents[reagent] >= blueprint[robot].get(reagent, 0)
|
||||
for reagent in REAGENTS
|
||||
)
|
||||
]
|
||||
|
||||
states_for_t.add(
|
||||
State(
|
||||
robots=state.robots,
|
||||
reagents={
|
||||
reagent: state.reagents[reagent] + state.robots[reagent]
|
||||
for reagent in REAGENTS
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
if "geode" in robots_that_can_be_built:
|
||||
robots_that_can_be_built = ["geode"]
|
||||
else:
|
||||
robots_that_can_be_built = [
|
||||
robot
|
||||
for robot in robots_that_can_be_built
|
||||
if state.robots[robot] < maximums[robot]
|
||||
]
|
||||
|
||||
for robot in robots_that_can_be_built:
|
||||
robots = state.robots.copy()
|
||||
robots[robot] += 1
|
||||
reagents = {
|
||||
reagent: state.reagents[reagent]
|
||||
+ state.robots[reagent]
|
||||
- blueprint[robot].get(reagent, 0)
|
||||
for reagent in REAGENTS
|
||||
}
|
||||
states_for_t.add(State(robots=robots, reagents=reagents))
|
||||
|
||||
# use numpy to switch computation of dominated states -> store each state
|
||||
# as a 8 array and use numpy broadcasting to find dominated states
|
||||
states_after = np.asarray(list(states_for_t))
|
||||
np_states = np.array(
|
||||
[
|
||||
[state.robots[r] for r in REAGENTS]
|
||||
+ [state.reagents[r] for r in REAGENTS]
|
||||
for state in states_after
|
||||
]
|
||||
)
|
||||
|
||||
to_keep = []
|
||||
while len(np_states) > 0:
|
||||
first_dom = (np_states[1:] >= np_states[0]).all(axis=1).any()
|
||||
|
||||
if first_dom:
|
||||
np_states = np_states[1:]
|
||||
else:
|
||||
to_keep.append(np_states[0])
|
||||
np_states = np_states[1:][~(np_states[1:] <= np_states[0]).all(axis=1)]
|
||||
|
||||
state_after_t[t] = {
|
||||
State(
|
||||
robots=dict(zip(REAGENTS, row[:4])),
|
||||
reagents=dict(zip(REAGENTS, row[4:])),
|
||||
)
|
||||
for row in to_keep
|
||||
}
|
||||
|
||||
return max(state.reagents["geode"] for state in state_after_t[max_time])
|
||||
|
||||
|
||||
answer_1 = sum(
|
||||
(i_blueprint + 1) * run(blueprint, 24)
|
||||
for i_blueprint, blueprint in enumerate(blueprints)
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
answer_2 = run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32)
|
||||
print(f"answer 2 is {answer_2}")
|
53
src/holt59/aoc/2022/day2.py
Normal file
53
src/holt59/aoc/2022/day2.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import sys
|
||||
|
||||
|
||||
def score_1(ux: int, vx: int) -> int:
|
||||
# here ux and vx are both moves: 0 = rock, 1 = paper, 2 = scissor
|
||||
#
|
||||
|
||||
# 1. to get the score of the move/shape, we simply add 1 -> vx + 1
|
||||
# 2. to get the score of the outcome (loss/draw/win), we use the fact that the
|
||||
# winning hand is always the opponent hand (ux) + 1 in modulo-3 arithmetic:
|
||||
# - (ux - vx) % 3 gives us 0 for a draw, 1 for a loss and 2 for a win
|
||||
# - 1 - ((ux - vx) % 3) gives us -1 for a win, 0 for a loss and 1 for a draw
|
||||
# - (1 - ((ux - vx) % 3)) gives us 0 / 1 / 2 for loss / draw / win
|
||||
# - the above can be rewritten as ((1 - (ux - vx)) % 3)
|
||||
# we can then simply multiply this by 3 to get the outcome score
|
||||
#
|
||||
return (vx + 1) + ((1 - (ux - vx)) % 3) * 3
|
||||
|
||||
|
||||
def score_2(ux: int, vx: int) -> int:
|
||||
# here ux is the opponent move (0 = rock, 1 = paper, 2 = scissor) and vx is the
|
||||
# outcome (0 = loss, 1 = draw, 2 = win)
|
||||
#
|
||||
|
||||
# 1. to get the score to the move/shape, we need to find it (as 0, 1 or 2) and then
|
||||
# add 1 to it
|
||||
# - (vx - 1) gives the offset from the opponent shape (-1 for a loss, 0 for a
|
||||
# draw and 1 for a win)
|
||||
# - from the offset, we can retrieve the shape by adding the opponent shape and
|
||||
# using modulo-3 arithmetic -> (ux + vx - 1) % 3
|
||||
# - we then add 1 to get the final shape score
|
||||
# 2. to get the score of the outcome, we can simply multiply vx by 3 -> vx * 3
|
||||
return (ux + vx - 1) % 3 + 1 + vx * 3
|
||||
|
||||
|
||||
lines = sys.stdin.readlines()
|
||||
|
||||
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
|
||||
# modulo-3 arithmetic
|
||||
#
|
||||
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
|
||||
# if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
|
||||
#
|
||||
|
||||
# we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
|
||||
# easier manipulation
|
||||
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
|
||||
|
||||
# part 1 - 13526
|
||||
print(f"answer 1 is {sum(score_1(*v) for v in values)}")
|
||||
|
||||
# part 2 - 14204
|
||||
print(f"answer 2 is {sum(score_2(*v) for v in values)}")
|
74
src/holt59/aoc/2022/day20.py
Normal file
74
src/holt59/aoc/2022/day20.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
class Number:
|
||||
current: int
|
||||
value: int
|
||||
|
||||
def __init__(self, value: int):
|
||||
self.current = 0
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
def decrypt(numbers: list[Number], key: int, rounds: int) -> int:
|
||||
numbers = numbers.copy()
|
||||
original = numbers.copy()
|
||||
|
||||
for index, number in enumerate(numbers):
|
||||
number.current = index
|
||||
|
||||
for _ in range(rounds):
|
||||
for number in original:
|
||||
index = number.current
|
||||
offset = (number.value * key) % (len(numbers) - 1)
|
||||
target = index + offset
|
||||
|
||||
# need to wrap
|
||||
if target >= len(numbers):
|
||||
target = offset - (len(numbers) - index) + 1
|
||||
|
||||
for number_2 in numbers[target:index]:
|
||||
number_2.current += 1
|
||||
|
||||
numbers = (
|
||||
numbers[:target]
|
||||
+ [number]
|
||||
+ numbers[target:index]
|
||||
+ numbers[index + 1 :]
|
||||
)
|
||||
else:
|
||||
for number_2 in numbers[index : target + 1]:
|
||||
number_2.current -= 1
|
||||
|
||||
numbers = (
|
||||
numbers[:index]
|
||||
+ numbers[index + 1 : target + 1]
|
||||
+ [number]
|
||||
+ numbers[target + 1 :]
|
||||
)
|
||||
number.current = target
|
||||
|
||||
index_of_0 = next(
|
||||
filter(lambda index: numbers[index].value == 0, range(len(numbers)))
|
||||
)
|
||||
return sum(
|
||||
numbers[(index_of_0 + offset) % len(numbers)].value * key
|
||||
for offset in (1000, 2000, 3000)
|
||||
)
|
||||
|
||||
|
||||
numbers = [Number(int(x)) for i, x in enumerate(sys.stdin.readlines())]
|
||||
|
||||
answer_1 = decrypt(numbers, 1, 1)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
answer_2 = decrypt(numbers, 811589153, 10)
|
||||
print(f"answer 2 is {answer_2}")
|
107
src/holt59/aoc/2022/day21.py
Normal file
107
src/holt59/aoc/2022/day21.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import operator
|
||||
import sys
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int:
|
||||
value = monkeys[monkey]
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
else:
|
||||
op: dict[str, Callable[[int, int], int]] = {
|
||||
"+": operator.add,
|
||||
"-": operator.sub,
|
||||
"*": operator.mul,
|
||||
"/": operator.floordiv,
|
||||
}
|
||||
value = op[value[1]](compute(monkeys, value[0]), compute(monkeys, value[2]))
|
||||
monkeys[monkey] = value
|
||||
return value
|
||||
|
||||
|
||||
def invert(
|
||||
monkeys: dict[str, int | tuple[str, str, str]], monkey: str, target: int
|
||||
) -> dict[str, int | tuple[str, str, str]]:
|
||||
"""
|
||||
Revert the given mapping from monkey name to value or operation such that
|
||||
the value from 'monkey' is computable by inverting operation until the root is
|
||||
found.
|
||||
|
||||
Args:
|
||||
monkeys: Dictionary of monkeys, that will be updated and returned.
|
||||
monkey: Name of the monkey to start from.
|
||||
target: Target value to set for the monkey that depends on root.
|
||||
|
||||
Returns:
|
||||
The given dictionary of monkeys.
|
||||
"""
|
||||
|
||||
monkeys = monkeys.copy()
|
||||
|
||||
depends: dict[str, str] = {}
|
||||
for m, v in monkeys.items():
|
||||
if isinstance(v, int):
|
||||
continue
|
||||
|
||||
op1, _, op2 = v
|
||||
|
||||
assert op1 not in depends
|
||||
assert op2 not in depends
|
||||
depends[op1] = m
|
||||
depends[op2] = m
|
||||
|
||||
invert_op = {"+": "-", "-": "+", "*": "/", "/": "*"}
|
||||
|
||||
current = monkey
|
||||
while True:
|
||||
dep = depends[current]
|
||||
|
||||
if dep == "root":
|
||||
monkeys[current] = target
|
||||
break
|
||||
|
||||
val = monkeys[dep]
|
||||
assert not isinstance(val, int)
|
||||
|
||||
op1, ope, op2 = val
|
||||
|
||||
if op1 == current:
|
||||
monkeys[current] = (dep, invert_op[ope], op2)
|
||||
elif ope in ("+", "*"):
|
||||
monkeys[current] = (dep, invert_op[ope], op1)
|
||||
else:
|
||||
monkeys[current] = (op1, ope, dep)
|
||||
|
||||
current = dep
|
||||
|
||||
return monkeys
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
monkeys: dict[str, int | tuple[str, str, str]] = {}
|
||||
|
||||
op_monkeys: set[str] = set()
|
||||
|
||||
for line in lines:
|
||||
parts = line.split(":")
|
||||
name = parts[0].strip()
|
||||
|
||||
try:
|
||||
value = int(parts[1].strip())
|
||||
monkeys[name] = value
|
||||
except ValueError:
|
||||
op1, ope, op2 = parts[1].strip().split()
|
||||
monkeys[name] = (op1, ope, op2)
|
||||
|
||||
op_monkeys.add(name)
|
||||
|
||||
|
||||
answer_1 = compute(monkeys.copy(), "root")
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# assume the second operand of 'root' can be computed, and the first one depends on
|
||||
# humn, which is the case is my input and the test input
|
||||
p1, _, p2 = monkeys["root"] # type: ignore
|
||||
answer_2 = compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
|
||||
print(f"answer 2 is {answer_2}")
|
223
src/holt59/aoc/2022/day22.py
Normal file
223
src/holt59/aoc/2022/day22.py
Normal file
@@ -0,0 +1,223 @@
|
||||
import re
|
||||
import sys
|
||||
from typing import Callable
|
||||
|
||||
import numpy as np
|
||||
|
||||
VOID, EMPTY, WALL = 0, 1, 2
|
||||
TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL}
|
||||
|
||||
SCORES = {"E": 0, "S": 1, "W": 2, "N": 3}
|
||||
|
||||
|
||||
board_map_s, direction_s = sys.stdin.read().split("\n\n")
|
||||
|
||||
# board
|
||||
board_lines = board_map_s.splitlines()
|
||||
max_line = max(len(line) for line in board_lines)
|
||||
board = np.array(
|
||||
[
|
||||
[TILE_FROM_CHAR[c] for c in row] + [VOID] * (max_line - len(row))
|
||||
for row in board_map_s.splitlines()
|
||||
]
|
||||
)
|
||||
|
||||
directions = [
|
||||
int(p1) if p2 else p1 for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
|
||||
]
|
||||
|
||||
|
||||
# find on each row and column the first and last non-void
|
||||
row_first_non_void = np.argmax(board != VOID, axis=1)
|
||||
row_last_non_void = board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1
|
||||
col_first_non_void = np.argmax(board != VOID, axis=0)
|
||||
col_last_non_void = board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1
|
||||
|
||||
|
||||
faces = np.zeros_like(board)
|
||||
size = np.gcd(board.shape[0], board.shape[1])
|
||||
for row in range(0, board.shape[0], size):
|
||||
for col in range(row_first_non_void[row], row_last_non_void[row], size):
|
||||
faces[row : row + size, col : col + size] = faces.max() + 1
|
||||
|
||||
SIZE = np.gcd(*board.shape)
|
||||
|
||||
# TODO: deduce this from the actual cube...
|
||||
faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]]
|
||||
|
||||
if board.shape == (12, 16): # example
|
||||
faces_wrap = {
|
||||
1: {
|
||||
"W": lambda y, x: (4, 4 + y, "S"), # 3N
|
||||
"N": lambda y, x: (4, 11 - x, "S"), # 2N
|
||||
"E": lambda y, x: (11 - y, 15, "W"), # 6E
|
||||
},
|
||||
2: {
|
||||
"W": lambda y, x: (11, 19 - y, "N"), # 6S
|
||||
"N": lambda y, x: (0, 11 - y, "S"), # 1N
|
||||
"S": lambda y, x: (11, 11 - x, "N"), # 5S
|
||||
},
|
||||
3: {
|
||||
"N": lambda y, x: (x - 4, 8, "E"), # 1W
|
||||
"S": lambda y, x: (15 - x, 8, "E"), # 5W
|
||||
},
|
||||
4: {"E": lambda y, x: (8, 19 - y, "S")}, # 6N
|
||||
5: {
|
||||
"W": lambda y, x: (7, 15 - y, "N"), # 3S
|
||||
"S": lambda y, x: (7, 11 - x, "N"), # 2S
|
||||
},
|
||||
6: {
|
||||
"N": lambda y, x: (19 - x, 11, "W"), # 4E
|
||||
"E": lambda y, x: (11 - y, 11, "W"), # 1E
|
||||
"S": lambda y, x: (19 - x, 0, "E"), # 2W
|
||||
},
|
||||
}
|
||||
|
||||
else:
|
||||
faces_wrap = {
|
||||
1: {
|
||||
"W": lambda y, x: (3 * SIZE - y - 1, 0, "E"), # 4W
|
||||
"N": lambda y, x: (2 * SIZE + x, 0, "E"), # 6W
|
||||
},
|
||||
2: {
|
||||
"N": lambda y, x: (4 * SIZE - 1, x - 2 * SIZE, "N"), # 6S
|
||||
"E": lambda y, x: (3 * SIZE - y - 1, 2 * SIZE - 1, "W"), # 5E
|
||||
"S": lambda y, x: (x - SIZE, 2 * SIZE - 1, "W"), # 3E
|
||||
},
|
||||
3: {
|
||||
"W": lambda y, x: (2 * SIZE, y - SIZE, "S"), # 4N
|
||||
"E": lambda y, x: (SIZE - 1, SIZE + y, "N"), # 2S
|
||||
},
|
||||
4: {
|
||||
"W": lambda y, x: (3 * SIZE - y - 1, SIZE, "E"), # 1W
|
||||
"N": lambda y, x: (SIZE + x, SIZE, "E"), # 3W
|
||||
},
|
||||
5: {
|
||||
"E": lambda y, x: (3 * SIZE - y - 1, 3 * SIZE - 1, "W"), # 2E
|
||||
"S": lambda y, x: (2 * SIZE + x, SIZE - 1, "W"), # 6E
|
||||
},
|
||||
6: {
|
||||
"W": lambda y, x: (0, y - 2 * SIZE, "S"), # 1N
|
||||
"E": lambda y, x: (3 * SIZE - 1, y - 2 * SIZE, "N"), # 5S
|
||||
"S": lambda y, x: (0, x + 2 * SIZE, "S"), # 2N
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
|
||||
if r0 == "E":
|
||||
return y0, row_first_non_void[y0], r0
|
||||
elif r0 == "S":
|
||||
return col_first_non_void[x0], x0, r0
|
||||
elif r0 == "W":
|
||||
return y0, row_last_non_void[y0], r0
|
||||
elif r0 == "N":
|
||||
return col_last_non_void[x0], x0, r0
|
||||
|
||||
assert False
|
||||
|
||||
|
||||
def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
|
||||
cube = faces[y0, x0]
|
||||
assert r0 in faces_wrap[cube]
|
||||
return faces_wrap[cube][r0](y0, x0)
|
||||
|
||||
|
||||
def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int, str]:
|
||||
y0 = 0
|
||||
x0 = np.where(board[0] == EMPTY)[0][0]
|
||||
r0 = "E"
|
||||
|
||||
for direction in directions:
|
||||
if isinstance(direction, int):
|
||||
while direction > 0:
|
||||
if r0 == "E":
|
||||
xi = np.where(board[y0, x0 + 1 : x0 + direction + 1] == WALL)[0]
|
||||
if len(xi):
|
||||
x0 = x0 + xi[0]
|
||||
direction = 0
|
||||
elif (
|
||||
x0 + direction < board.shape[1]
|
||||
and board[y0, x0 + direction] == EMPTY
|
||||
):
|
||||
x0 = x0 + direction
|
||||
direction = 0
|
||||
else:
|
||||
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
||||
if board[y0_t, x0_t] == WALL:
|
||||
x0 = row_last_non_void[y0]
|
||||
direction = 0
|
||||
else:
|
||||
direction = direction - (row_last_non_void[y0] - x0) - 1
|
||||
y0, x0, r0 = y0_t, x0_t, r0_t
|
||||
elif r0 == "S":
|
||||
yi = np.where(board[y0 + 1 : y0 + direction + 1, x0] == WALL)[0]
|
||||
if len(yi):
|
||||
y0 = y0 + yi[0]
|
||||
direction = 0
|
||||
elif (
|
||||
y0 + direction < board.shape[0]
|
||||
and board[y0 + direction, x0] == EMPTY
|
||||
):
|
||||
y0 = y0 + direction
|
||||
direction = 0
|
||||
else:
|
||||
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
||||
if board[y0_t, x0_t] == WALL:
|
||||
y0 = col_last_non_void[x0]
|
||||
direction = 0
|
||||
else:
|
||||
direction = direction - (col_last_non_void[x0] - y0) - 1
|
||||
y0, x0, r0 = y0_t, x0_t, r0_t
|
||||
elif r0 == "W":
|
||||
left = max(x0 - direction - 1, 0)
|
||||
xi = np.where(board[y0, left:x0] == WALL)[0]
|
||||
if len(xi):
|
||||
x0 = left + xi[-1] + 1
|
||||
direction = 0
|
||||
elif x0 - direction >= 0 and board[y0, x0 - direction] == EMPTY:
|
||||
x0 = x0 - direction
|
||||
direction = 0
|
||||
else:
|
||||
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
||||
if board[y0_t, x0_t] == WALL:
|
||||
x0 = row_first_non_void[y0]
|
||||
direction = 0
|
||||
else:
|
||||
direction = direction - (x0 - row_first_non_void[y0]) - 1
|
||||
y0, x0, r0 = y0_t, x0_t, r0_t
|
||||
elif r0 == "N":
|
||||
top = max(y0 - direction - 1, 0)
|
||||
yi = np.where(board[top:y0, x0] == WALL)[0]
|
||||
if len(yi):
|
||||
y0 = top + yi[-1] + 1
|
||||
direction = 0
|
||||
elif y0 - direction >= 0 and board[y0 - direction, x0] == EMPTY:
|
||||
y0 = y0 - direction
|
||||
direction = 0
|
||||
else:
|
||||
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
||||
if board[y0_t, x0_t] == WALL:
|
||||
y0 = col_first_non_void[x0]
|
||||
direction = 0
|
||||
else:
|
||||
direction = direction - (y0 - col_first_non_void[x0]) - 1
|
||||
y0, x0, r0 = y0_t, x0_t, r0_t
|
||||
else:
|
||||
r0 = {
|
||||
"E": {"L": "N", "R": "S"},
|
||||
"N": {"L": "W", "R": "E"},
|
||||
"W": {"L": "S", "R": "N"},
|
||||
"S": {"L": "E", "R": "W"},
|
||||
}[r0][direction]
|
||||
|
||||
return y0, x0, r0
|
||||
|
||||
|
||||
y1, x1, r1 = run(wrap_part_1)
|
||||
answer_1 = 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
y2, x2, r2 = run(wrap_part_2)
|
||||
answer_2 = 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]
|
||||
print(f"answer 2 is {answer_2}")
|
103
src/holt59/aoc/2022/day23.py
Normal file
103
src/holt59/aoc/2022/day23.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import itertools
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
Directions = list[
|
||||
tuple[
|
||||
str, tuple[int, int], tuple[tuple[int, int], tuple[int, int], tuple[int, int]]
|
||||
]
|
||||
]
|
||||
|
||||
# (Y, X)
|
||||
DIRECTIONS: Directions = [
|
||||
("N", (-1, 0), ((-1, -1), (-1, 0), (-1, 1))),
|
||||
("S", (1, 0), ((1, -1), (1, 0), (1, 1))),
|
||||
("W", (0, -1), ((-1, -1), (0, -1), (1, -1))),
|
||||
("E", (0, 1), ((-1, 1), (0, 1), (1, 1))),
|
||||
]
|
||||
|
||||
|
||||
def min_max_yx(positions: set[tuple[int, int]]) -> tuple[int, int, int, int]:
|
||||
ys, xs = {y for y, x in positions}, {x for y, x in positions}
|
||||
return min(ys), min(xs), max(ys), max(xs)
|
||||
|
||||
|
||||
def print_positions(positions: set[tuple[int, int]]):
|
||||
min_y, min_x, max_y, max_x = min_max_yx(positions)
|
||||
print(
|
||||
"\n".join(
|
||||
"".join(
|
||||
"#" if (y, x) in positions else "." for x in range(min_x - 1, max_x + 2)
|
||||
)
|
||||
for y in range(min_y - 1, max_y + 2)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def round(
|
||||
positions: set[tuple[int, int]],
|
||||
directions: Directions,
|
||||
):
|
||||
to_move: dict[tuple[int, int], list[tuple[int, int]]] = defaultdict(lambda: [])
|
||||
for y, x in positions:
|
||||
elves = {
|
||||
(dy, dx): (y + dy, x + dx) in positions
|
||||
for dy, dx in itertools.product((-1, 0, 1), (-1, 0, 1))
|
||||
if (dy, dx) != (0, 0)
|
||||
}
|
||||
|
||||
if not any(elves.values()):
|
||||
to_move[y, x].append((y, x))
|
||||
continue
|
||||
|
||||
found: str | None = None
|
||||
for d, (dy, dx), d_yx_check in directions:
|
||||
if not any(elves[dy, dx] for dy, dx in d_yx_check):
|
||||
found = d
|
||||
to_move[y + dy, x + dx].append((y, x))
|
||||
break
|
||||
if found is None:
|
||||
to_move[y, x].append((y, x))
|
||||
|
||||
positions.clear()
|
||||
for ty, tx in to_move:
|
||||
if len(to_move[ty, tx]) > 1:
|
||||
positions.update(to_move[ty, tx])
|
||||
else:
|
||||
positions.add((ty, tx))
|
||||
|
||||
directions.append(directions.pop(0))
|
||||
|
||||
|
||||
POSITIONS = {
|
||||
(i, j)
|
||||
for i, row in enumerate(sys.stdin.read().splitlines())
|
||||
for j, col in enumerate(row)
|
||||
if col == "#"
|
||||
}
|
||||
|
||||
# === part 1 ===
|
||||
|
||||
p1, d1 = POSITIONS.copy(), DIRECTIONS.copy()
|
||||
for r in range(10):
|
||||
round(p1, d1)
|
||||
|
||||
min_y, min_x, max_y, max_x = min_max_yx(p1)
|
||||
answer_1 = sum(
|
||||
(y, x) not in p1 for y in range(min_y, max_y + 1) for x in range(min_x, max_x + 1)
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# === part 2 ===
|
||||
|
||||
p2, d2 = POSITIONS.copy(), DIRECTIONS.copy()
|
||||
answer_2 = 0
|
||||
while True:
|
||||
answer_2 += 1
|
||||
backup = p2.copy()
|
||||
round(p2, d2)
|
||||
|
||||
if backup == p2:
|
||||
break
|
||||
|
||||
print(f"answer 2 is {answer_2}")
|
98
src/holt59/aoc/2022/day24.py
Normal file
98
src/holt59/aoc/2022/day24.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import heapq
|
||||
import math
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
winds = {
|
||||
(i - 1, j - 1, lines[i][j])
|
||||
for i in range(1, len(lines) - 1)
|
||||
for j in range(1, len(lines[i]) - 1)
|
||||
if lines[i][j] != "."
|
||||
}
|
||||
|
||||
n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2
|
||||
CYCLE = math.lcm(n_rows, n_cols)
|
||||
|
||||
east_winds = [{j for j in range(n_cols) if (i, j, ">") in winds} for i in range(n_rows)]
|
||||
west_winds = [{j for j in range(n_cols) if (i, j, "<") in winds} for i in range(n_rows)]
|
||||
north_winds = [
|
||||
{i for i in range(n_rows) if (i, j, "^") in winds} for j in range(n_cols)
|
||||
]
|
||||
south_winds = [
|
||||
{i for i in range(n_rows) if (i, j, "v") in winds} for j in range(n_cols)
|
||||
]
|
||||
|
||||
|
||||
def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
|
||||
def heuristic(y: int, x: int) -> int:
|
||||
return abs(end[0] - y) + abs(end[1] - x)
|
||||
|
||||
# (distance + heuristic, distance, (start_pos, cycle))
|
||||
queue = [(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))]
|
||||
visited: set[tuple[tuple[int, int], int]] = set()
|
||||
distances: dict[tuple[int, int], dict[int, int]] = defaultdict(lambda: {})
|
||||
|
||||
while queue:
|
||||
_, distance, ((y, x), cycle) = heapq.heappop(queue)
|
||||
|
||||
if ((y, x), cycle) in visited:
|
||||
continue
|
||||
|
||||
distances[y, x][cycle] = distance
|
||||
|
||||
visited.add(((y, x), cycle))
|
||||
|
||||
if (y, x) == (end[0], end[1]):
|
||||
break
|
||||
|
||||
for dy, dx in (0, 0), (-1, 0), (1, 0), (0, -1), (0, 1):
|
||||
ty = y + dy
|
||||
tx = x + dx
|
||||
|
||||
n_cycle = (cycle + 1) % CYCLE
|
||||
|
||||
if (ty, tx) == end:
|
||||
heapq.heappush(queue, (distance + 1, distance + 1, ((ty, tx), n_cycle)))
|
||||
break
|
||||
|
||||
if ((ty, tx), n_cycle) in visited:
|
||||
continue
|
||||
|
||||
if (ty, tx) != start and (ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols):
|
||||
continue
|
||||
|
||||
if (ty, tx) != start:
|
||||
if (ty - n_cycle) % n_rows in south_winds[tx]:
|
||||
continue
|
||||
if (ty + n_cycle) % n_rows in north_winds[tx]:
|
||||
continue
|
||||
if (tx + n_cycle) % n_cols in west_winds[ty]:
|
||||
continue
|
||||
if (tx - n_cycle) % n_cols in east_winds[ty]:
|
||||
continue
|
||||
|
||||
heapq.heappush(
|
||||
queue,
|
||||
((heuristic(ty, tx) + distance + 1, distance + 1, ((ty, tx), n_cycle))),
|
||||
)
|
||||
|
||||
return distances, next(iter(distances[end].values()))
|
||||
|
||||
|
||||
start = (
|
||||
-1,
|
||||
next(j for j in range(1, len(lines[0]) - 1) if lines[0][j] == ".") - 1,
|
||||
)
|
||||
end = (
|
||||
n_rows,
|
||||
next(j for j in range(1, len(lines[-1]) - 1) if lines[-1][j] == ".") - 1,
|
||||
)
|
||||
|
||||
distances_1, forward_1 = run(start, 0, end)
|
||||
print(f"answer 1 is {forward_1}")
|
||||
|
||||
distances_2, return_1 = run(end, next(iter(distances_1[end].keys())), start)
|
||||
distances_3, forward_2 = run(start, next(iter(distances_2[start].keys())), end)
|
||||
print(f"answer 2 is {forward_1 + return_1 + forward_2}")
|
27
src/holt59/aoc/2022/day25.py
Normal file
27
src/holt59/aoc/2022/day25.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import sys
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
|
||||
|
||||
|
||||
def snafu2number(number: str) -> int:
|
||||
value = 0
|
||||
for c in number:
|
||||
value *= 5
|
||||
value += coeffs[c]
|
||||
return value
|
||||
|
||||
|
||||
def number2snafu(number: int) -> str:
|
||||
values = ["0", "1", "2", "=", "-"]
|
||||
res = ""
|
||||
while number > 0:
|
||||
mod = number % 5
|
||||
res = res + values[mod]
|
||||
number = number // 5 + int(mod >= 3)
|
||||
return "".join(reversed(res))
|
||||
|
||||
|
||||
answer_1 = number2snafu(sum(map(snafu2number, lines)))
|
||||
print(f"answer 1 is {answer_1}")
|
23
src/holt59/aoc/2022/day3.py
Normal file
23
src/holt59/aoc/2022/day3.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import string
|
||||
import sys
|
||||
|
||||
lines = [line.strip() for line in sys.stdin.readlines()]
|
||||
|
||||
# extract content of each part
|
||||
parts = [(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines]
|
||||
|
||||
# priorities
|
||||
priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)}
|
||||
|
||||
# part 1
|
||||
part1 = sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
|
||||
print(f"answer 1 is {part1}")
|
||||
|
||||
# part 2
|
||||
n_per_group = 3
|
||||
part2 = sum(
|
||||
priorities[c]
|
||||
for i in range(0, len(lines), n_per_group)
|
||||
for c in set(lines[i]).intersection(*lines[i + 1 : i + n_per_group])
|
||||
)
|
||||
print(f"answer 2 is {part2}")
|
17
src/holt59/aoc/2022/day4.py
Normal file
17
src/holt59/aoc/2022/day4.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import sys
|
||||
|
||||
lines = [line.strip() for line in sys.stdin.readlines()]
|
||||
|
||||
|
||||
def make_range(value: str) -> set[int]:
|
||||
parts = value.split("-")
|
||||
return set(range(int(parts[0]), int(parts[1]) + 1))
|
||||
|
||||
|
||||
sections = [tuple(make_range(part) for part in line.split(",")) for line in lines]
|
||||
|
||||
answer_1 = sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
answer_2 = sum(bool(s1.intersection(s2)) for s1, s2 in sections)
|
||||
print(f"answer 1 is {answer_2}")
|
41
src/holt59/aoc/2022/day5.py
Normal file
41
src/holt59/aoc/2022/day5.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import copy
|
||||
import sys
|
||||
|
||||
blocks_s, moves_s = (part.splitlines() for part in sys.stdin.read().split("\n\n"))
|
||||
|
||||
blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()}
|
||||
|
||||
# this codes assumes that the lines are regular, i.e., 4 characters per "crate" in the
|
||||
# form of '[X] ' (including the trailing space)
|
||||
#
|
||||
for block in blocks_s[-2::-1]:
|
||||
for stack, index in zip(blocks, range(0, len(block), 4)):
|
||||
crate = block[index + 1 : index + 2].strip()
|
||||
|
||||
if crate:
|
||||
blocks[stack].append(crate)
|
||||
|
||||
# part 1 - deep copy for part 2
|
||||
blocks_1 = copy.deepcopy(blocks)
|
||||
|
||||
for move in moves_s:
|
||||
_, count_s, _, from_, _, to_ = move.strip().split()
|
||||
|
||||
for _i in range(int(count_s)):
|
||||
blocks_1[to_].append(blocks_1[from_].pop())
|
||||
|
||||
# part 2
|
||||
blocks_2 = copy.deepcopy(blocks)
|
||||
|
||||
for move in moves_s:
|
||||
_, count_s, _, from_, _, to_ = move.strip().split()
|
||||
count = int(count_s)
|
||||
|
||||
blocks_2[to_].extend(blocks_2[from_][-count:])
|
||||
del blocks_2[from_][-count:]
|
||||
|
||||
answer_1 = "".join(s[-1] for s in blocks_1.values())
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
answer_2 = "".join(s[-1] for s in blocks_2.values())
|
||||
print(f"answer 2 is {answer_2}")
|
15
src/holt59/aoc/2022/day6.py
Normal file
15
src/holt59/aoc/2022/day6.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import sys
|
||||
|
||||
|
||||
def index_of_first_n_differents(data: str, n: int) -> int:
|
||||
for i in range(len(data)):
|
||||
if len(set(data[i : i + n])) == n:
|
||||
return i + n
|
||||
return -1
|
||||
|
||||
|
||||
data = sys.stdin.read().strip()
|
||||
|
||||
|
||||
print(f"answer 1 is {index_of_first_n_differents(data, 4)}")
|
||||
print(f"answer 2 is {index_of_first_n_differents(data, 14)}")
|
80
src/holt59/aoc/2022/day7.py
Normal file
80
src/holt59/aoc/2022/day7.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# we are going to use Path to create path and go up/down in the file tree since it
|
||||
# implements everything we need
|
||||
#
|
||||
# we can use .resolve() to get normalized path, although this will add C:\ to all paths
|
||||
# on Windows but that is not an issue since only the sizes matter
|
||||
#
|
||||
|
||||
# mapping from path to list of files or directories
|
||||
trees: dict[Path, list[Path]] = {}
|
||||
|
||||
# mapping from paths to either size (for file) or -1 for directory
|
||||
sizes: dict[Path, int] = {}
|
||||
|
||||
# first line must be a cd otherwise we have no idea where we are
|
||||
assert lines[0].startswith("$ cd")
|
||||
base_path = Path(lines[0].strip("$").split()[1]).resolve()
|
||||
cur_path = base_path
|
||||
|
||||
trees[cur_path] = []
|
||||
sizes[cur_path] = -1
|
||||
|
||||
for line in lines[1:]:
|
||||
# command
|
||||
if line.startswith("$"):
|
||||
parts = line.strip("$").strip().split()
|
||||
command = parts[0]
|
||||
|
||||
if command == "cd":
|
||||
cur_path = cur_path.joinpath(parts[1]).resolve()
|
||||
|
||||
# just initialize the lis of files if not already done
|
||||
if cur_path not in trees:
|
||||
trees[cur_path] = []
|
||||
else:
|
||||
# nothing to do here
|
||||
pass
|
||||
|
||||
# fill the current path
|
||||
else:
|
||||
parts = line.split()
|
||||
name: str = parts[1]
|
||||
if line.startswith("dir"):
|
||||
size = -1
|
||||
else:
|
||||
size = int(parts[0])
|
||||
|
||||
path = cur_path.joinpath(name)
|
||||
trees[cur_path].append(path)
|
||||
sizes[path] = size
|
||||
|
||||
|
||||
def compute_size(path: Path) -> int:
|
||||
size = sizes[path]
|
||||
|
||||
if size >= 0:
|
||||
return size
|
||||
|
||||
return sum(compute_size(sub) for sub in trees[path])
|
||||
|
||||
|
||||
acc_sizes = {path: compute_size(path) for path in trees}
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
total_space = 70_000_000
|
||||
update_space = 30_000_000
|
||||
free_space = total_space - acc_sizes[base_path]
|
||||
|
||||
to_free_space = update_space - free_space
|
||||
|
||||
answer_2 = min(size for size in acc_sizes.values() if size >= to_free_space)
|
||||
print(f"answer 2 is {answer_2}")
|
53
src/holt59/aoc/2022/day8.py
Normal file
53
src/holt59/aoc/2022/day8.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
trees = np.array([[int(x) for x in row] for row in lines])
|
||||
|
||||
# answer 1
|
||||
highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1
|
||||
highest_trees[1:-1, 1:-1] = [
|
||||
[
|
||||
[
|
||||
trees[:i, j].max(),
|
||||
trees[i + 1 :, j].max(),
|
||||
trees[i, :j].max(),
|
||||
trees[i, j + 1 :].max(),
|
||||
]
|
||||
for j in range(1, trees.shape[1] - 1)
|
||||
]
|
||||
for i in range(1, trees.shape[0] - 1)
|
||||
]
|
||||
|
||||
answer_1 = (highest_trees.min(axis=2) < trees).sum()
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
|
||||
def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int:
|
||||
w = np.where(row_of_trees >= value)[0]
|
||||
|
||||
if not w.size:
|
||||
return len(row_of_trees)
|
||||
|
||||
return w[0] + 1
|
||||
|
||||
|
||||
# answer 2
|
||||
v_distances = np.zeros(trees.shape + (4,), dtype=int)
|
||||
v_distances[1:-1, 1:-1, :] = [
|
||||
[
|
||||
[
|
||||
viewing_distance(trees[i - 1 :: -1, j], trees[i, j]),
|
||||
viewing_distance(trees[i, j - 1 :: -1], trees[i, j]),
|
||||
viewing_distance(trees[i, j + 1 :], trees[i, j]),
|
||||
viewing_distance(trees[i + 1 :, j], trees[i, j]),
|
||||
]
|
||||
for j in range(1, trees.shape[1] - 1)
|
||||
]
|
||||
for i in range(1, trees.shape[0] - 1)
|
||||
]
|
||||
answer_2 = np.prod(v_distances, axis=2).max()
|
||||
print(f"answer 2 is {answer_2}")
|
59
src/holt59/aoc/2022/day9.py
Normal file
59
src/holt59/aoc/2022/day9.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def move(head: tuple[int, int], command: str) -> tuple[int, int]:
|
||||
h_col, h_row = head
|
||||
|
||||
if command == "L":
|
||||
head = (h_col - 1, h_row)
|
||||
elif command == "R":
|
||||
head = (h_col + 1, h_row)
|
||||
elif command == "U":
|
||||
head = (h_col, h_row + 1)
|
||||
elif command == "D":
|
||||
head = (h_col, h_row - 1)
|
||||
|
||||
return head
|
||||
|
||||
|
||||
def follow(head: tuple[int, int], tail: tuple[int, int]) -> tuple[int, int]:
|
||||
h_col, h_row = head
|
||||
t_col, t_row = tail
|
||||
|
||||
if abs(t_col - h_col) <= 1 and abs(t_row - h_row) <= 1:
|
||||
return tail
|
||||
|
||||
return t_col + np.sign(h_col - t_col), t_row + np.sign(h_row - t_row)
|
||||
|
||||
|
||||
def run(commands: list[str], n_blocks: int) -> list[tuple[int, int]]:
|
||||
blocks: list[tuple[int, int]] = [(0, 0) for _ in range(n_blocks)]
|
||||
visited = [blocks[-1]]
|
||||
|
||||
for command in commands:
|
||||
blocks[0] = move(blocks[0], command)
|
||||
|
||||
for i in range(0, n_blocks - 1):
|
||||
blocks[i + 1] = follow(blocks[i], blocks[i + 1])
|
||||
|
||||
visited.append(blocks[-1])
|
||||
|
||||
return visited
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# flatten the commands
|
||||
commands: list[str] = []
|
||||
for line in lines:
|
||||
d, c = line.split()
|
||||
commands.extend(d * int(c))
|
||||
|
||||
|
||||
visited_1 = run(commands, n_blocks=2)
|
||||
print(f"answer 1 is {len(set(visited_1))}")
|
||||
|
||||
visited_2 = run(commands, n_blocks=10)
|
||||
print(f"answer 2 is {len(set(visited_2))}")
|
0
src/holt59/aoc/2023/__init__.py
Normal file
0
src/holt59/aoc/2023/__init__.py
Normal file
45
src/holt59/aoc/2023/day1.py
Normal file
45
src/holt59/aoc/2023/day1.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import sys
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
lookups_1 = {str(d): d for d in range(1, 10)}
|
||||
lookups_2 = lookups_1 | {
|
||||
d: i + 1
|
||||
for i, d in enumerate(
|
||||
(
|
||||
"one",
|
||||
"two",
|
||||
"three",
|
||||
"four",
|
||||
"five",
|
||||
"six",
|
||||
"seven",
|
||||
"eight",
|
||||
"nine",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def find_values(lookups: dict[str, int]) -> list[int]:
|
||||
values: list[int] = []
|
||||
|
||||
for line in filter(bool, lines):
|
||||
first_digit = min(
|
||||
lookups,
|
||||
key=lambda lookup: index
|
||||
if (index := line.find(lookup)) >= 0
|
||||
else len(line),
|
||||
)
|
||||
last_digit = max(
|
||||
lookups,
|
||||
key=lambda lookup: index if (index := line.rfind(lookup)) >= 0 else -1,
|
||||
)
|
||||
|
||||
values.append(10 * lookups[first_digit] + lookups[last_digit])
|
||||
|
||||
return values
|
||||
|
||||
|
||||
print(f"answer 1 is {sum(find_values(lookups_1))}")
|
||||
print(f"answer 2 is {sum(find_values(lookups_2))}")
|
100
src/holt59/aoc/2023/day10.py
Normal file
100
src/holt59/aoc/2023/day10.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import os
|
||||
import sys
|
||||
from typing import Literal, cast
|
||||
|
||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
||||
|
||||
Symbol = Literal["|", "-", "L", "J", "7", "F", ".", "S"]
|
||||
|
||||
lines: list[list[Symbol]] = [
|
||||
[cast(Symbol, symbol) for symbol in line] for line in sys.stdin.read().splitlines()
|
||||
]
|
||||
|
||||
# find starting point
|
||||
si, sj = next(
|
||||
(i, j)
|
||||
for i in range(len(lines))
|
||||
for j in range(len(lines[0]))
|
||||
if lines[i][j] == "S"
|
||||
)
|
||||
|
||||
# find one of the two outputs
|
||||
ni, nj = si, sj
|
||||
for ni, nj, chars in (
|
||||
(si - 1, sj, "|7F"),
|
||||
(si + 1, sj, "|LJ"),
|
||||
(si, sj - 1, "-LF"),
|
||||
(si, sj + 1, "-J7"),
|
||||
):
|
||||
if lines[ni][nj] in chars:
|
||||
break
|
||||
|
||||
# part 1 - find the loop (re-used in part 2)
|
||||
loop = [(si, sj), (ni, nj)]
|
||||
while True:
|
||||
pi, pj = loop[-2]
|
||||
i, j = loop[-1]
|
||||
|
||||
sym = lines[i][j]
|
||||
|
||||
if sym == "|" and pi > i or sym in "JL" and pi == i:
|
||||
i -= 1
|
||||
elif sym == "|" and pi < i or sym in "7F" and pi == i:
|
||||
i += 1
|
||||
elif sym == "-" and pj > j or sym in "J7" and pj == j:
|
||||
j -= 1
|
||||
elif sym == "-" and pj < j or sym in "LF" and pj == j:
|
||||
j += 1
|
||||
|
||||
if (i, j) == (si, sj):
|
||||
break
|
||||
|
||||
loop.append((i, j))
|
||||
|
||||
answer_1 = len(loop) // 2
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
|
||||
# replace S by an appropriate character for the loop below
|
||||
di1, dj1 = loop[1][0] - loop[0][0], loop[1][1] - loop[0][1]
|
||||
di2, dj2 = loop[0][0] - loop[-1][0], loop[0][1] - loop[-1][1]
|
||||
mapping: dict[tuple[int, int], dict[tuple[int, int], Symbol]] = {
|
||||
(0, 1): {(0, 1): "-", (-1, 0): "F", (1, 0): "L"},
|
||||
(0, -1): {(0, -1): "-", (-1, 0): "7", (1, 0): "J"},
|
||||
(1, 0): {(1, 0): "|", (0, 1): "7", (0, -1): "F"},
|
||||
(-1, 0): {(-1, 0): "|", (0, -1): "L", (0, 1): "J"},
|
||||
}
|
||||
lines[si][sj] = mapping[di1, dj1][di2, dj2]
|
||||
|
||||
# find the points inside the loop using an adaptation of ray casting for a discrete
|
||||
# grid (https://stackoverflow.com/a/218081/2666289)
|
||||
#
|
||||
# use a set for faster '... in loop' check
|
||||
#
|
||||
loop_s = set(loop)
|
||||
inside: set[tuple[int, int]] = set()
|
||||
for i in range(len(lines)):
|
||||
cnt = 0
|
||||
for j in range(len(lines[0])):
|
||||
if (i, j) not in loop_s and cnt % 2 == 1:
|
||||
inside.add((i, j))
|
||||
|
||||
if (i, j) in loop_s and lines[i][j] in "|LJ":
|
||||
cnt += 1
|
||||
|
||||
if VERBOSE:
|
||||
for i in range(len(lines)):
|
||||
for j in range(len(lines[0])):
|
||||
if (i, j) == (si, sj):
|
||||
print("\033[91mS\033[0m", end="")
|
||||
elif (i, j) in loop:
|
||||
print(lines[i][j], end="")
|
||||
elif (i, j) in inside:
|
||||
print("\033[92mI\033[0m", end="")
|
||||
else:
|
||||
print(".", end="")
|
||||
print()
|
||||
|
||||
answer_2 = len(inside)
|
||||
print(f"answer 2 is {answer_2}")
|
41
src/holt59/aoc/2023/day11.py
Normal file
41
src/holt59/aoc/2023/day11.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
data = np.array([[c == "#" for c in line] for line in lines])
|
||||
|
||||
rows = {c for c in range(data.shape[0]) if not data[c, :].any()}
|
||||
columns = {c for c in range(data.shape[1]) if not data[:, c].any()}
|
||||
|
||||
galaxies_y, galaxies_x = np.where(data) # type: ignore
|
||||
|
||||
|
||||
def compute_total_distance(expansion: int) -> int:
|
||||
distances: list[int] = []
|
||||
for g1 in range(len(galaxies_y)):
|
||||
x1, y1 = int(galaxies_x[g1]), int(galaxies_y[g1])
|
||||
for g2 in range(g1 + 1, len(galaxies_y)):
|
||||
x2, y2 = int(galaxies_x[g2]), int(galaxies_y[g2])
|
||||
|
||||
dx = sum(
|
||||
1 + (expansion - 1) * (x in columns)
|
||||
for x in range(min(x1, x2), max(x1, x2))
|
||||
)
|
||||
dy = sum(
|
||||
1 + (expansion - 1) * (y in rows)
|
||||
for y in range(min(y1, y2), max(y1, y2))
|
||||
)
|
||||
|
||||
distances.append(dx + dy)
|
||||
return sum(distances)
|
||||
|
||||
|
||||
# part 1
|
||||
answer_1 = compute_total_distance(2)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = compute_total_distance(1000000)
|
||||
print(f"answer 2 is {answer_2}")
|
107
src/holt59/aoc/2023/day12.py
Normal file
107
src/holt59/aoc/2023/day12.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import os
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from typing import Iterable
|
||||
|
||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
||||
|
||||
|
||||
@lru_cache
|
||||
def compute_fitting_arrangements(pattern: str, counts: tuple[int, ...]) -> int:
|
||||
"""
|
||||
fn3p tries to fit ALL values in counts() inside the pattern.
|
||||
"""
|
||||
# no pattern -> ok if nothing to fit, otherwise ko
|
||||
if not pattern:
|
||||
count = 1 if not counts else 0
|
||||
|
||||
# no count -> ok if pattern has no mandatory entry, else ko
|
||||
elif not counts:
|
||||
count = 1 if pattern.find("#") == -1 else 0
|
||||
|
||||
# cannot fit all values -> ko
|
||||
elif len(pattern) < sum(counts) + len(counts) - 1:
|
||||
count = 0
|
||||
|
||||
elif len(pattern) < counts[0]:
|
||||
count = 0
|
||||
|
||||
else:
|
||||
count = 0
|
||||
|
||||
if pattern[0] == "?":
|
||||
count += compute_fitting_arrangements(pattern[1:], counts)
|
||||
|
||||
if len(pattern) == counts[0]:
|
||||
count += 1
|
||||
|
||||
elif pattern[counts[0]] != "#":
|
||||
count += compute_fitting_arrangements(pattern[counts[0] + 1 :], counts[1:])
|
||||
|
||||
return count
|
||||
|
||||
|
||||
@lru_cache
|
||||
def compute_possible_arrangements(
|
||||
patterns: tuple[str, ...], counts: tuple[int, ...]
|
||||
) -> int:
|
||||
if not patterns:
|
||||
return 1 if not counts else 0
|
||||
|
||||
with_hash = sum(1 for p in patterns[1:] if p.find("#") >= 0)
|
||||
|
||||
if with_hash > len(counts):
|
||||
return 0
|
||||
|
||||
to_fit = counts if with_hash == 0 else counts[:-with_hash]
|
||||
remaining = () if with_hash == 0 else counts[-with_hash:]
|
||||
|
||||
if not to_fit:
|
||||
if patterns[0].find("#") != -1:
|
||||
return 0
|
||||
return compute_possible_arrangements(patterns[1:], remaining)
|
||||
|
||||
elif patterns[0].find("#") != -1 and len(patterns[0]) < to_fit[0]:
|
||||
return 0
|
||||
|
||||
elif patterns[0].find("?") == -1:
|
||||
if len(patterns[0]) != to_fit[0]:
|
||||
return 0
|
||||
return compute_possible_arrangements(patterns[1:], counts[1:])
|
||||
|
||||
else:
|
||||
return sum(
|
||||
fp * compute_possible_arrangements(patterns[1:], to_fit[i:] + remaining)
|
||||
for i in range(len(to_fit) + 1)
|
||||
if (fp := compute_fitting_arrangements(patterns[0], to_fit[:i])) > 0
|
||||
)
|
||||
|
||||
|
||||
def compute_all_possible_arrangements(lines: Iterable[str], repeat: int) -> int:
|
||||
count = 0
|
||||
|
||||
if VERBOSE:
|
||||
from tqdm import tqdm
|
||||
|
||||
lines = tqdm(lines)
|
||||
|
||||
for line in lines:
|
||||
parts = line.split(" ")
|
||||
count += compute_possible_arrangements(
|
||||
tuple(filter(len, "?".join(parts[0] for _ in range(repeat)).split("."))),
|
||||
tuple(int(c) for c in parts[1].split(",")) * repeat,
|
||||
)
|
||||
|
||||
return count
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
|
||||
# part 1
|
||||
answer_1 = compute_all_possible_arrangements(lines, 1)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = compute_all_possible_arrangements(lines, 5)
|
||||
print(f"answer 2 is {answer_2}")
|
43
src/holt59/aoc/2023/day13.py
Normal file
43
src/holt59/aoc/2023/day13.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import sys
|
||||
from typing import Callable, Literal
|
||||
|
||||
|
||||
def split(block: list[str], axis: Literal[0, 1], count: int) -> int:
|
||||
n_iter = len(block) if axis == 0 else len(block[0])
|
||||
n_check = len(block) if axis == 1 else len(block[0])
|
||||
|
||||
at: Callable[[int, int], str] = (
|
||||
(lambda i, j: block[i][j]) if axis == 0 else (lambda i, j: block[j][i])
|
||||
)
|
||||
|
||||
for i in range(n_iter - 1):
|
||||
size = min(i + 1, n_iter - i - 1)
|
||||
if (
|
||||
sum(
|
||||
at(i - s, j) != at(i + 1 + s, j)
|
||||
for s in range(0, size)
|
||||
for j in range(n_check)
|
||||
)
|
||||
== count
|
||||
):
|
||||
return i + 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
blocks = [block.splitlines() for block in sys.stdin.read().split("\n\n")]
|
||||
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(
|
||||
split(block, axis=1, count=0) + 100 * split(block, axis=0, count=0)
|
||||
for block in blocks
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = sum(
|
||||
split(block, axis=1, count=1) + 100 * split(block, axis=0, count=1)
|
||||
for block in blocks
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
68
src/holt59/aoc/2023/day14.py
Normal file
68
src/holt59/aoc/2023/day14.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import sys
|
||||
from typing import TypeAlias
|
||||
|
||||
RockGrid: TypeAlias = list[list[str]]
|
||||
|
||||
rocks0 = [list(line) for line in sys.stdin.read().splitlines()]
|
||||
|
||||
|
||||
def slide_rocks_top(rocks: RockGrid) -> RockGrid:
|
||||
top = [0 if c == "." else 1 for c in rocks[0]]
|
||||
for row in range(1, len(rocks)):
|
||||
for col in range(len(rocks[0])):
|
||||
match rocks[row][col]:
|
||||
case "O":
|
||||
if top[col] != row:
|
||||
rocks[top[col]][col] = "O"
|
||||
rocks[row][col] = "."
|
||||
top[col] = top[col] + 1
|
||||
case "#":
|
||||
top[col] = row + 1
|
||||
case _:
|
||||
pass
|
||||
return rocks
|
||||
|
||||
|
||||
def cycle(rocks: RockGrid) -> RockGrid:
|
||||
for _ in range(4):
|
||||
rocks = slide_rocks_top(rocks)
|
||||
rocks = [
|
||||
[rocks[len(rocks) - j - 1][i] for j in range(len(rocks))]
|
||||
for i in range(len(rocks[0]))
|
||||
]
|
||||
|
||||
return rocks
|
||||
|
||||
|
||||
rocks = slide_rocks_top([[c for c in r] for r in rocks0])
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(
|
||||
(len(rocks) - i) * sum(1 for c in row if c == "O") for i, row in enumerate(rocks)
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
rocks = rocks0
|
||||
|
||||
N = 1000000000
|
||||
cycles: list[RockGrid] = []
|
||||
i_cycle: int = -1
|
||||
for i_cycle in range(N):
|
||||
rocks = cycle(rocks)
|
||||
|
||||
if any(rocks == c for c in cycles):
|
||||
break
|
||||
|
||||
cycles.append([[c for c in r] for r in rocks])
|
||||
|
||||
cycle_start = next(i for i in range(len(cycles)) if (rocks == cycles[i]))
|
||||
cycle_length = i_cycle - cycle_start
|
||||
|
||||
ci = cycle_start + (N - cycle_start) % cycle_length - 1
|
||||
|
||||
answer_2 = sum(
|
||||
(len(rocks) - i) * sum(1 for c in row if c == "O")
|
||||
for i, row in enumerate(cycles[ci])
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
31
src/holt59/aoc/2023/day15.py
Normal file
31
src/holt59/aoc/2023/day15.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import sys
|
||||
from functools import reduce
|
||||
|
||||
steps = sys.stdin.read().strip().split(",")
|
||||
|
||||
|
||||
def _hash(s: str) -> int:
|
||||
return reduce(lambda v, u: ((v + ord(u)) * 17) % 256, s, 0)
|
||||
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(map(_hash, steps))
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
boxes: list[dict[str, int]] = [{} for _ in range(256)]
|
||||
|
||||
for step in steps:
|
||||
if (i := step.find("=")) >= 0:
|
||||
label, length = step[:i], int(step[i + 1 :])
|
||||
boxes[_hash(label)][label] = length
|
||||
else:
|
||||
label = step[:-1]
|
||||
boxes[_hash(label)].pop(label, None)
|
||||
|
||||
answer_2 = sum(
|
||||
i_box * i_lens * length
|
||||
for i_box, box in enumerate(boxes, start=1)
|
||||
for i_lens, length in enumerate(box.values(), start=1)
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
110
src/holt59/aoc/2023/day16.py
Normal file
110
src/holt59/aoc/2023/day16.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import os
|
||||
import sys
|
||||
from typing import Literal, TypeAlias, cast
|
||||
|
||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
||||
|
||||
CellType: TypeAlias = Literal[".", "|", "-", "\\", "/"]
|
||||
Direction: TypeAlias = Literal["R", "L", "U", "D"]
|
||||
|
||||
Mappings: dict[
|
||||
CellType,
|
||||
dict[
|
||||
Direction,
|
||||
tuple[tuple[tuple[int, int, Direction], ...], tuple[Direction, ...]],
|
||||
],
|
||||
] = {
|
||||
".": {
|
||||
"R": (((0, +1, "R"),), ("R", "L")),
|
||||
"L": (((0, -1, "L"),), ("R", "L")),
|
||||
"U": (((-1, 0, "U"),), ("U", "D")),
|
||||
"D": (((+1, 0, "D"),), ("U", "D")),
|
||||
},
|
||||
"-": {
|
||||
"R": (((0, +1, "R"),), ("R", "L")),
|
||||
"L": (((0, -1, "L"),), ("R", "L")),
|
||||
"U": (((0, +1, "R"), (0, -1, "L")), ("U", "D")),
|
||||
"D": (((0, +1, "R"), (0, -1, "L")), ("U", "D")),
|
||||
},
|
||||
"|": {
|
||||
"U": (((-1, 0, "U"),), ("U", "D")),
|
||||
"D": (((+1, 0, "D"),), ("U", "D")),
|
||||
"R": (((-1, 0, "U"), (+1, 0, "D")), ("R", "L")),
|
||||
"L": (((-1, 0, "U"), (+1, 0, "D")), ("R", "L")),
|
||||
},
|
||||
"/": {
|
||||
"R": (((-1, 0, "U"),), ("R", "D")),
|
||||
"L": (((+1, 0, "D"),), ("L", "U")),
|
||||
"U": (((0, +1, "R"),), ("U", "L")),
|
||||
"D": (((0, -1, "L"),), ("R", "D")),
|
||||
},
|
||||
"\\": {
|
||||
"R": (((+1, 0, "D"),), ("R", "U")),
|
||||
"L": (((-1, 0, "U"),), ("L", "D")),
|
||||
"U": (((0, -1, "L"),), ("U", "R")),
|
||||
"D": (((0, +1, "R"),), ("L", "D")),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def propagate(
|
||||
layout: list[list[CellType]], start: tuple[int, int], direction: Direction
|
||||
) -> list[list[tuple[Direction, ...]]]:
|
||||
n_rows, n_cols = len(layout), len(layout[0])
|
||||
|
||||
beams: list[list[tuple[Direction, ...]]] = [
|
||||
[() for _ in range(len(layout[0]))] for _ in range(len(layout))
|
||||
]
|
||||
|
||||
queue = [(start, direction)]
|
||||
|
||||
while queue:
|
||||
(row, col), direction = queue.pop()
|
||||
|
||||
if (
|
||||
row not in range(0, n_rows)
|
||||
or col not in range(0, n_cols)
|
||||
or direction in beams[row][col]
|
||||
):
|
||||
continue
|
||||
|
||||
moves, update = Mappings[layout[row][col]][direction]
|
||||
|
||||
beams[row][col] += update
|
||||
|
||||
for move in moves:
|
||||
queue.append(((row + move[0], col + move[1]), move[2]))
|
||||
|
||||
return beams
|
||||
|
||||
|
||||
layout: list[list[CellType]] = [
|
||||
[cast(CellType, col) for col in row] for row in sys.stdin.read().splitlines()
|
||||
]
|
||||
|
||||
|
||||
beams = propagate(layout, (0, 0), "R")
|
||||
|
||||
if VERBOSE:
|
||||
print("\n".join(["".join("#" if col else "." for col in row) for row in beams]))
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(sum(map(bool, row)) for row in beams)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
n_rows, n_cols = len(layout), len(layout[0])
|
||||
cases: list[tuple[tuple[int, int], Direction]] = []
|
||||
|
||||
for row in range(n_rows):
|
||||
cases.append(((row, 0), "R"))
|
||||
cases.append(((row, n_cols - 1), "L"))
|
||||
for col in range(n_cols):
|
||||
cases.append(((0, col), "D"))
|
||||
cases.append(((n_rows - 1, col), "U"))
|
||||
|
||||
answer_2 = max(
|
||||
sum(sum(map(bool, row)) for row in propagate(layout, start, direction))
|
||||
for start, direction in cases
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
233
src/holt59/aoc/2023/day17.py
Normal file
233
src/holt59/aoc/2023/day17.py
Normal file
@@ -0,0 +1,233 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import heapq
|
||||
import os
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, TypeAlias
|
||||
|
||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
||||
|
||||
Direction: TypeAlias = Literal[">", "<", "^", "v"]
|
||||
|
||||
|
||||
@dataclass(frozen=True, order=True)
|
||||
class Label:
|
||||
row: int
|
||||
col: int
|
||||
|
||||
direction: Direction
|
||||
count: int
|
||||
|
||||
parent: Label | None = None
|
||||
|
||||
|
||||
# mappings from direction to row shift / col shift / opposite direction
|
||||
MAPPINGS: dict[Direction, tuple[int, int, Direction]] = {
|
||||
">": (0, +1, "<"),
|
||||
"<": (0, -1, ">"),
|
||||
"v": (+1, 0, "^"),
|
||||
"^": (-1, 0, "v"),
|
||||
}
|
||||
|
||||
|
||||
def print_shortest_path(
|
||||
grid: list[list[int]],
|
||||
target: tuple[int, int],
|
||||
per_cell: dict[tuple[int, int], list[tuple[Label, int]]],
|
||||
):
|
||||
assert len(per_cell[target]) == 1
|
||||
label = per_cell[target][0][0]
|
||||
|
||||
path: list[Label] = []
|
||||
while True:
|
||||
path.insert(0, label)
|
||||
if label.parent is None:
|
||||
break
|
||||
label = label.parent
|
||||
|
||||
p_grid = [[str(c) for c in r] for r in grid]
|
||||
|
||||
for i in range(len(grid)):
|
||||
for j in range(len(grid[0])):
|
||||
if per_cell[i, j]:
|
||||
p_grid[i][j] = f"\033[94m{grid[i][j]}\033[0m"
|
||||
|
||||
prev_label = path[0]
|
||||
for label in path[1:]:
|
||||
for r in range(
|
||||
min(prev_label.row, label.row), max(prev_label.row, label.row) + 1
|
||||
):
|
||||
for c in range(
|
||||
min(prev_label.col, label.col),
|
||||
max(prev_label.col, label.col) + 1,
|
||||
):
|
||||
if (r, c) != (prev_label.row, prev_label.col):
|
||||
p_grid[r][c] = f"\033[93m{grid[r][c]}\033[0m"
|
||||
|
||||
p_grid[label.row][label.col] = f"\033[91m{grid[label.row][label.col]}\033[0m"
|
||||
|
||||
prev_label = label
|
||||
|
||||
p_grid[0][0] = f"\033[92m{grid[0][0]}\033[0m"
|
||||
|
||||
print("\n".join("".join(row) for row in p_grid))
|
||||
|
||||
|
||||
def shortest_many_paths(grid: list[list[int]]) -> dict[tuple[int, int], int]:
|
||||
n_rows, n_cols = len(grid), len(grid[0])
|
||||
|
||||
visited: dict[tuple[int, int], tuple[Label, int]] = {}
|
||||
|
||||
queue: list[tuple[int, Label]] = [
|
||||
(0, Label(row=n_rows - 1, col=n_cols - 1, direction="^", count=0))
|
||||
]
|
||||
|
||||
while queue and len(visited) != n_rows * n_cols:
|
||||
distance, label = heapq.heappop(queue)
|
||||
|
||||
if (label.row, label.col) in visited:
|
||||
continue
|
||||
|
||||
visited[label.row, label.col] = (label, distance)
|
||||
|
||||
for direction, (c_row, c_col, i_direction) in MAPPINGS.items():
|
||||
if label.direction == i_direction:
|
||||
continue
|
||||
else:
|
||||
row, col = (label.row + c_row, label.col + c_col)
|
||||
|
||||
# exclude labels outside the grid or with too many moves in the same
|
||||
# direction
|
||||
if row not in range(0, n_rows) or col not in range(0, n_cols):
|
||||
continue
|
||||
|
||||
heapq.heappush(
|
||||
queue,
|
||||
(
|
||||
distance
|
||||
+ sum(
|
||||
grid[r][c]
|
||||
for r in range(min(row, label.row), max(row, label.row) + 1)
|
||||
for c in range(min(col, label.col), max(col, label.col) + 1)
|
||||
)
|
||||
- grid[row][col],
|
||||
Label(
|
||||
row=row,
|
||||
col=col,
|
||||
direction=direction,
|
||||
count=0,
|
||||
parent=label,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
return {(r, c): visited[r, c][1] for r in range(n_rows) for c in range(n_cols)}
|
||||
|
||||
|
||||
def shortest_path(
|
||||
grid: list[list[int]],
|
||||
min_straight: int,
|
||||
max_straight: int,
|
||||
lower_bounds: dict[tuple[int, int], int],
|
||||
) -> int:
|
||||
n_rows, n_cols = len(grid), len(grid[0])
|
||||
|
||||
target = (len(grid) - 1, len(grid[0]) - 1)
|
||||
|
||||
# for each tuple (row, col, direction, count), the associated label when visited
|
||||
visited: dict[tuple[int, int, str, int], Label] = {}
|
||||
|
||||
# list of all visited labels for a cell (with associated distance)
|
||||
per_cell: dict[tuple[int, int], list[tuple[Label, int]]] = defaultdict(list)
|
||||
|
||||
# need to add two start labels, otherwise one of the two possible direction will
|
||||
# not be possible
|
||||
queue: list[tuple[int, int, Label]] = [
|
||||
(lower_bounds[0, 0], 0, Label(row=0, col=0, direction="^", count=0)),
|
||||
(lower_bounds[0, 0], 0, Label(row=0, col=0, direction="<", count=0)),
|
||||
]
|
||||
|
||||
while queue:
|
||||
_, distance, label = heapq.heappop(queue)
|
||||
|
||||
if (label.row, label.col, label.direction, label.count) in visited:
|
||||
continue
|
||||
|
||||
visited[label.row, label.col, label.direction, label.count] = label
|
||||
per_cell[label.row, label.col].append((label, distance))
|
||||
|
||||
if (label.row, label.col) == target:
|
||||
break
|
||||
|
||||
for direction, (c_row, c_col, i_direction) in MAPPINGS.items():
|
||||
# cannot move in the opposite direction
|
||||
if label.direction == i_direction:
|
||||
continue
|
||||
|
||||
# other direction, move 'min_straight' in the new direction
|
||||
elif label.direction != direction:
|
||||
row, col, count = (
|
||||
label.row + min_straight * c_row,
|
||||
label.col + min_straight * c_col,
|
||||
min_straight,
|
||||
)
|
||||
|
||||
# same direction, too many count
|
||||
elif label.count == max_straight:
|
||||
continue
|
||||
|
||||
# same direction, keep going and increment count
|
||||
else:
|
||||
row, col, count = (
|
||||
label.row + c_row,
|
||||
label.col + c_col,
|
||||
label.count + 1,
|
||||
)
|
||||
# exclude labels outside the grid or with too many moves in the same
|
||||
# direction
|
||||
if row not in range(0, n_rows) or col not in range(0, n_cols):
|
||||
continue
|
||||
|
||||
distance_to = (
|
||||
distance
|
||||
+ sum(
|
||||
grid[r][c]
|
||||
for r in range(min(row, label.row), max(row, label.row) + 1)
|
||||
for c in range(min(col, label.col), max(col, label.col) + 1)
|
||||
)
|
||||
- grid[label.row][label.col]
|
||||
)
|
||||
|
||||
heapq.heappush(
|
||||
queue,
|
||||
(
|
||||
distance_to + lower_bounds[row, col],
|
||||
distance_to,
|
||||
Label(
|
||||
row=row,
|
||||
col=col,
|
||||
direction=direction,
|
||||
count=count,
|
||||
parent=label,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
if VERBOSE:
|
||||
print_shortest_path(grid, target, per_cell)
|
||||
|
||||
return per_cell[target][0][1]
|
||||
|
||||
|
||||
data = [[int(c) for c in r] for r in sys.stdin.read().splitlines()]
|
||||
estimates = shortest_many_paths(data)
|
||||
|
||||
# part 1
|
||||
answer_1 = shortest_path(data, 1, 3, lower_bounds=estimates)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = shortest_path(data, 4, 10, lower_bounds=estimates)
|
||||
print(f"answer 2 is {answer_2}")
|
54
src/holt59/aoc/2023/day18.py
Normal file
54
src/holt59/aoc/2023/day18.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import sys
|
||||
from typing import Literal, TypeAlias, cast
|
||||
|
||||
Direction: TypeAlias = Literal["R", "L", "U", "D"]
|
||||
|
||||
DIRECTIONS: list[Direction] = ["R", "D", "L", "U"]
|
||||
MOVES: dict[Direction, tuple[int, int]] = {
|
||||
"R": (0, +1),
|
||||
"L": (0, -1),
|
||||
"U": (-1, 0),
|
||||
"D": (+1, 0),
|
||||
}
|
||||
|
||||
|
||||
def area(corners: list[tuple[int, int]], perimeter: int) -> int:
|
||||
area = abs(
|
||||
sum(c0[0] * c1[1] - c0[1] * c1[0] for c0, c1 in zip(corners, corners[1::])) // 2
|
||||
)
|
||||
return 1 + area - perimeter // 2 + perimeter
|
||||
|
||||
|
||||
def polygon(values: list[tuple[Direction, int]]) -> tuple[list[tuple[int, int]], int]:
|
||||
perimeter = 0
|
||||
corners: list[tuple[int, int]] = [(0, 0)]
|
||||
for direction, amount in values:
|
||||
perimeter += amount
|
||||
corners.append(
|
||||
(
|
||||
corners[-1][0] + amount * MOVES[direction][0],
|
||||
corners[-1][1] + amount * MOVES[direction][1],
|
||||
)
|
||||
)
|
||||
return corners, perimeter
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
|
||||
# part 1
|
||||
answer_1 = area(
|
||||
*polygon([(cast(Direction, (p := line.split())[0]), int(p[1])) for line in lines])
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = area(
|
||||
*polygon(
|
||||
[
|
||||
(DIRECTIONS[int((h := line.split()[-1])[-2])], int(h[2:-2], 16))
|
||||
for line in lines
|
||||
]
|
||||
)
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
132
src/holt59/aoc/2023/day19.py
Normal file
132
src/holt59/aoc/2023/day19.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import logging
|
||||
import operator
|
||||
import os
|
||||
import sys
|
||||
from math import prod
|
||||
from typing import Literal, TypeAlias, cast
|
||||
|
||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
||||
|
||||
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
|
||||
|
||||
Category: TypeAlias = Literal["x", "m", "a", "s"]
|
||||
Part: TypeAlias = dict[Category, int]
|
||||
PartWithBounds: TypeAlias = dict[Category, tuple[int, int]]
|
||||
|
||||
OPERATORS = {"<": operator.lt, ">": operator.gt}
|
||||
|
||||
# None if there is no check (last entry), otherwise (category, sense, value)
|
||||
Check: TypeAlias = tuple[Category, Literal["<", ">"], int] | None
|
||||
|
||||
# workflow as a list of check, in specified order, with target
|
||||
Workflow: TypeAlias = list[tuple[Check, str]]
|
||||
|
||||
|
||||
def accept(workflows: dict[str, Workflow], part: Part) -> bool:
|
||||
workflow = "in"
|
||||
decision: bool | None = None
|
||||
|
||||
while decision is None:
|
||||
for check, target in workflows[workflow]:
|
||||
ok = check is None
|
||||
if check is not None:
|
||||
category, sense, value = check
|
||||
ok = OPERATORS[sense](part[category], value)
|
||||
|
||||
if ok:
|
||||
if target in workflows:
|
||||
workflow = target
|
||||
else:
|
||||
decision = target == "A"
|
||||
break
|
||||
|
||||
return decision
|
||||
|
||||
|
||||
def propagate(workflows: dict[str, Workflow], start: PartWithBounds) -> int:
|
||||
def transfer_or_accept(
|
||||
target: str, meta: PartWithBounds, queue: list[tuple[PartWithBounds, str]]
|
||||
) -> int:
|
||||
if target in workflows:
|
||||
logging.info(f" transfer to {target}")
|
||||
queue.append((meta, target))
|
||||
return 0
|
||||
elif target == "A":
|
||||
logging.info(" accepted")
|
||||
return prod((high - low + 1) for low, high in meta.values())
|
||||
else:
|
||||
logging.info(" rejected")
|
||||
return 0
|
||||
|
||||
accepted = 0
|
||||
queue: list[tuple[PartWithBounds, str]] = [(start, "in")]
|
||||
|
||||
while queue:
|
||||
meta, workflow = queue.pop()
|
||||
logging.info(f"{workflow}: {meta}")
|
||||
for check, target in workflows[workflow]:
|
||||
if check is None:
|
||||
accepted += transfer_or_accept(target, meta, queue)
|
||||
continue
|
||||
|
||||
category, sense, value = check
|
||||
bounds, op = meta[category], OPERATORS[sense]
|
||||
|
||||
logging.info(f" splitting {meta} into {category} {sense} {value}")
|
||||
|
||||
if not op(bounds[0], value) and not op(bounds[1], value):
|
||||
logging.info(" reject, always false")
|
||||
continue
|
||||
|
||||
if op(meta[category][0], value) and op(meta[category][1], value):
|
||||
logging.info(" accept, always true")
|
||||
accepted += transfer_or_accept(target, meta, queue)
|
||||
break
|
||||
|
||||
meta2 = meta.copy()
|
||||
if sense == "<":
|
||||
meta2[category] = (meta[category][0], value - 1)
|
||||
meta[category] = (value, meta[category][1])
|
||||
else:
|
||||
meta2[category] = (value + 1, meta[category][1])
|
||||
meta[category] = (meta[category][0], value)
|
||||
logging.info(f" split {meta2} ({target}), {meta}")
|
||||
|
||||
accepted += transfer_or_accept(target, meta2, queue)
|
||||
|
||||
return accepted
|
||||
|
||||
|
||||
workflows_s, parts_s = sys.stdin.read().strip().split("\n\n")
|
||||
|
||||
workflows: dict[str, Workflow] = {}
|
||||
for workflow_s in workflows_s.split("\n"):
|
||||
name, block_s = workflow_s.split("{")
|
||||
workflows[name] = []
|
||||
|
||||
for block in block_s[:-1].split(","):
|
||||
check: Check
|
||||
if (i := block.find(":")) >= 0:
|
||||
check, target = (
|
||||
cast(Category, block[0]),
|
||||
cast(Literal["<", ">"], block[1]),
|
||||
int(block[2:i]),
|
||||
), block[i + 1 :]
|
||||
else:
|
||||
check, target = None, block
|
||||
workflows[name].append((check, target))
|
||||
|
||||
# part 1
|
||||
parts: list[Part] = [
|
||||
{cast(Category, s[0]): int(s[2:]) for s in part_s[1:-1].split(",")}
|
||||
for part_s in parts_s.split("\n")
|
||||
]
|
||||
answer_1 = sum(sum(part.values()) for part in parts if accept(workflows, part))
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
|
||||
# part 2
|
||||
answer_2 = propagate(
|
||||
workflows, {cast(Category, c): (1, 4000) for c in ["x", "m", "a", "s"]}
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
43
src/holt59/aoc/2023/day2.py
Normal file
43
src/holt59/aoc/2023/day2.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import math
|
||||
import sys
|
||||
from typing import Literal, TypeAlias, cast
|
||||
|
||||
CubeType: TypeAlias = Literal["red", "blue", "green"]
|
||||
|
||||
MAX_CUBES: dict[CubeType, int] = {"red": 12, "green": 13, "blue": 14}
|
||||
|
||||
# parse games
|
||||
lines = sys.stdin.read().splitlines()
|
||||
games: dict[int, list[dict[CubeType, int]]] = {}
|
||||
for line in filter(bool, lines):
|
||||
id_part, sets_part = line.split(":")
|
||||
|
||||
games[int(id_part.split(" ")[-1])] = [
|
||||
{
|
||||
cast(CubeType, s[1]): int(s[0])
|
||||
for cube_draw in cube_set_s.strip().split(", ")
|
||||
if (s := cube_draw.split(" "))
|
||||
}
|
||||
for cube_set_s in sets_part.strip().split(";")
|
||||
]
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(
|
||||
id
|
||||
for id, set_of_cubes in games.items()
|
||||
if all(
|
||||
n_cubes <= MAX_CUBES[cube]
|
||||
for cube_set in set_of_cubes
|
||||
for cube, n_cubes in cube_set.items()
|
||||
)
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = sum(
|
||||
math.prod(
|
||||
max(cube_set.get(cube, 0) for cube_set in set_of_cubes) for cube in MAX_CUBES
|
||||
)
|
||||
for set_of_cubes in games.values()
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2023/day20.py
Normal file
13
src/holt59/aoc/2023/day20.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2023/day21.py
Normal file
13
src/holt59/aoc/2023/day21.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2023/day22.py
Normal file
13
src/holt59/aoc/2023/day22.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2023/day23.py
Normal file
13
src/holt59/aoc/2023/day23.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2023/day24.py
Normal file
13
src/holt59/aoc/2023/day24.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
13
src/holt59/aoc/2023/day25.py
Normal file
13
src/holt59/aoc/2023/day25.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
53
src/holt59/aoc/2023/day3.py
Normal file
53
src/holt59/aoc/2023/day3.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import string
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
NOT_A_SYMBOL = "." + string.digits
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
values: list[int] = []
|
||||
gears: dict[tuple[int, int], list[int]] = defaultdict(list)
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
j = 0
|
||||
while j < len(line):
|
||||
# skip everything until a digit is found (start of a number)
|
||||
if line[j] not in string.digits:
|
||||
j += 1
|
||||
continue
|
||||
|
||||
# extract the range of the number and its value
|
||||
k = j + 1
|
||||
while k < len(line) and line[k] in string.digits:
|
||||
k += 1
|
||||
|
||||
value = int(line[j:k])
|
||||
|
||||
# lookup around the number if there is a symbol - we go through the number
|
||||
# itself but that should not matter since it only contains digits
|
||||
found = False
|
||||
for i2 in range(max(0, i - 1), min(i + 1, len(lines) - 1) + 1):
|
||||
for j2 in range(max(0, j - 1), min(k, len(line) - 1) + 1):
|
||||
assert i2 >= 0 and i2 < len(lines)
|
||||
assert j2 >= 0 and j2 < len(line)
|
||||
|
||||
if lines[i2][j2] not in NOT_A_SYMBOL:
|
||||
found = True
|
||||
|
||||
if lines[i2][j2] == "*":
|
||||
gears[i2, j2].append(value)
|
||||
|
||||
if found:
|
||||
values.append(value)
|
||||
|
||||
# continue starting from the end of the number
|
||||
j = k
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(values)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = sum(v1 * v2 for v1, v2 in filter(lambda vs: len(vs) == 2, gears.values()))
|
||||
print(f"answer 2 is {answer_2}")
|
41
src/holt59/aoc/2023/day4.py
Normal file
41
src/holt59/aoc/2023/day4.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Card:
|
||||
id: int
|
||||
numbers: list[int]
|
||||
values: list[int]
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
cards: list[Card] = []
|
||||
for line in lines:
|
||||
id_part, e_part = line.split(":")
|
||||
numbers_s, values_s = e_part.split("|")
|
||||
cards.append(
|
||||
Card(
|
||||
id=int(id_part.split()[1]),
|
||||
numbers=[int(v.strip()) for v in numbers_s.strip().split()],
|
||||
values=[int(v.strip()) for v in values_s.strip().split()],
|
||||
)
|
||||
)
|
||||
|
||||
winnings = [sum(1 for n in card.values if n in card.numbers) for card in cards]
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(2 ** (winning - 1) for winning in winnings if winning > 0)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)}
|
||||
card2values = {i: 0 for i in range(len(cards))}
|
||||
|
||||
for i in range(len(cards)):
|
||||
card2values[i] += 1
|
||||
for j in card2cards[i]:
|
||||
card2values[j] += card2values[i]
|
||||
|
||||
print(f"answer 2 is {sum(card2values.values())}")
|
129
src/holt59/aoc/2023/day5.py
Normal file
129
src/holt59/aoc/2023/day5.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import sys
|
||||
from typing import Sequence
|
||||
|
||||
MAP_ORDER = [
|
||||
"seed",
|
||||
"soil",
|
||||
"fertilizer",
|
||||
"water",
|
||||
"light",
|
||||
"temperature",
|
||||
"humidity",
|
||||
"location",
|
||||
]
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# mappings from one category to another, each list contains
|
||||
# ranges stored as (source, target, length), ordered by start and
|
||||
# completed to have no "hole"
|
||||
maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {}
|
||||
|
||||
# parsing
|
||||
index = 2
|
||||
while index < len(lines):
|
||||
p1, _, p2 = lines[index].split()[0].split("-")
|
||||
|
||||
# extract the existing ranges from the file - we store as (source, target, length)
|
||||
# whereas the file is in order (target, source, length)
|
||||
index += 1
|
||||
values: list[tuple[int, int, int]] = []
|
||||
while index < len(lines) and lines[index]:
|
||||
n1, n2, n3 = lines[index].split()
|
||||
values.append((int(n2), int(n1), int(n3)))
|
||||
index += 1
|
||||
|
||||
# sort by source value
|
||||
values.sort()
|
||||
|
||||
# add a 'fake' interval starting at 0 if missing
|
||||
if values[0][0] != 0:
|
||||
values.insert(0, (0, 0, values[0][0]))
|
||||
|
||||
# fill gaps between intervals
|
||||
for i in range(len(values) - 1):
|
||||
next_start = values[i + 1][0]
|
||||
end = values[i][0] + values[i][2]
|
||||
if next_start != end:
|
||||
values.insert(
|
||||
i + 1,
|
||||
(end, end, next_start - end),
|
||||
)
|
||||
|
||||
# add an interval covering values up to at least 2**32 at the end
|
||||
last_start, _, last_length = values[-1]
|
||||
values.append((last_start + last_length, last_start + last_length, 2**32))
|
||||
|
||||
assert all(v1[0] + v1[2] == v2[0] for v1, v2 in zip(values[:-1], values[1:]))
|
||||
assert values[0][0] == 0
|
||||
assert values[-1][0] + values[-1][-1] >= 2**32
|
||||
|
||||
maps[p1, p2] = values
|
||||
index += 1
|
||||
|
||||
|
||||
def find_range(
|
||||
values: tuple[int, int], map: list[tuple[int, int, int]]
|
||||
) -> list[tuple[int, int]]:
|
||||
"""
|
||||
Given an input range, use the given mapping to find the corresponding list of
|
||||
ranges in the target domain.
|
||||
"""
|
||||
r_start, r_length = values
|
||||
ranges: list[tuple[int, int]] = []
|
||||
|
||||
# find index of the first and last intervals in map that overlaps the input
|
||||
# interval
|
||||
index_start, index_end = -1, -1
|
||||
|
||||
for index_start, (start, _, length) in enumerate(map):
|
||||
if start <= r_start and start + length > r_start:
|
||||
break
|
||||
|
||||
for index_end, (start, _, length) in enumerate(
|
||||
map[index_start:], start=index_start
|
||||
):
|
||||
if r_start + r_length >= start and r_start + r_length < start + length:
|
||||
break
|
||||
|
||||
assert index_start >= 0 and index_end >= 0
|
||||
|
||||
# special case if one interval contains everything
|
||||
if index_start == index_end:
|
||||
start, target, length = map[index_start]
|
||||
ranges.append((target + r_start - start, r_length))
|
||||
else:
|
||||
# add the start interval part
|
||||
start, target, length = map[index_start]
|
||||
ranges.append((target + r_start - start, start + length - r_start))
|
||||
|
||||
# add all intervals between the first and last (excluding both)
|
||||
index = index_start + 1
|
||||
while index < index_end:
|
||||
start, target, length = map[index]
|
||||
ranges.append((target, length))
|
||||
index += 1
|
||||
|
||||
# add the last interval
|
||||
start, target, length = map[index_end]
|
||||
ranges.append((target, r_start + r_length - start))
|
||||
|
||||
return ranges
|
||||
|
||||
|
||||
def find_location_ranges(seeds: Sequence[tuple[int, int]]) -> Sequence[tuple[int, int]]:
|
||||
for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]):
|
||||
seeds = [s2 for s1 in seeds for s2 in find_range(s1, maps[map1, map2])]
|
||||
return seeds
|
||||
|
||||
|
||||
# part 1 - use find_range() with range of length 1
|
||||
seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()]
|
||||
answer_1 = min(start for start, _ in find_location_ranges(seeds_p1))
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# # part 2
|
||||
parts = lines[0].split(":")[1].strip().split()
|
||||
seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])]
|
||||
answer_2 = min(start for start, _ in find_location_ranges(seeds_p2))
|
||||
print(f"answer 2 is {answer_2}")
|
47
src/holt59/aoc/2023/day6.py
Normal file
47
src/holt59/aoc/2023/day6.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import math
|
||||
import sys
|
||||
|
||||
|
||||
def extreme_times_to_beat(time: int, distance: int) -> tuple[int, int]:
|
||||
# formula (T = total time, D = target distance)
|
||||
# - speed(t) = t,
|
||||
# - distance(t) = (T - t) * speed(t)
|
||||
# - distance(t) > D
|
||||
# <=> (T - t) * t > D
|
||||
# <=> -t^2 + T * t - D >= 0
|
||||
|
||||
a, b, c = -1, time, -distance
|
||||
d = b * b - 4 * a * c
|
||||
|
||||
t1 = math.ceil(-(b - math.sqrt(d)) / (2 * a))
|
||||
t2 = math.floor(-(b + math.sqrt(d)) / (2 * a))
|
||||
|
||||
if (time - t1) * t1 == distance:
|
||||
t1 += 1
|
||||
|
||||
if (time - t2) * t2 == distance:
|
||||
t2 -= 1
|
||||
|
||||
return t1, t2
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
times = list(map(int, lines[0].split()[1:]))
|
||||
distances = list(map(int, lines[1].split()[1:]))
|
||||
answer_1 = math.prod(
|
||||
t2 - t1 + 1
|
||||
for t1, t2 in (
|
||||
extreme_times_to_beat(time, distance)
|
||||
for time, distance in zip(times, distances)
|
||||
)
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
time = int(lines[0].split(":")[1].strip().replace(" ", ""))
|
||||
distance = int(lines[1].split(":")[1].strip().replace(" ", ""))
|
||||
t1, t2 = extreme_times_to_beat(time, distance)
|
||||
answer_2 = t2 - t1 + 1
|
||||
print(f"answer 2 is {answer_2}")
|
49
src/holt59/aoc/2023/day7.py
Normal file
49
src/holt59/aoc/2023/day7.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import sys
|
||||
from collections import Counter, defaultdict
|
||||
|
||||
|
||||
class HandTypes:
|
||||
HIGH_CARD = 0
|
||||
ONE_PAIR = 1
|
||||
TWO_PAIR = 2
|
||||
THREE_OF_A_KIND = 3
|
||||
FULL_HOUSE = 4
|
||||
FOUR_OF_A_KIND = 5
|
||||
FIVE_OF_A_KIND = 6
|
||||
|
||||
|
||||
# mapping from number of different cards + highest count of card to the hand type
|
||||
LEN_LAST_TO_TYPE: dict[int, dict[int, int]] = {
|
||||
1: defaultdict(lambda: HandTypes.FIVE_OF_A_KIND),
|
||||
2: defaultdict(lambda: HandTypes.FULL_HOUSE, {4: HandTypes.FOUR_OF_A_KIND}),
|
||||
3: defaultdict(lambda: HandTypes.TWO_PAIR, {3: HandTypes.THREE_OF_A_KIND}),
|
||||
4: defaultdict(lambda: HandTypes.ONE_PAIR),
|
||||
5: defaultdict(lambda: HandTypes.HIGH_CARD),
|
||||
}
|
||||
|
||||
|
||||
def extract_key(hand: str, values: dict[str, int], joker: str = "0") -> tuple[int, ...]:
|
||||
# get count of each cards, in increasing count - the 'or' part handles the JJJJJ
|
||||
# case when joker is True
|
||||
cnt = sorted(Counter(hand.replace(joker, "")).values()) or [0]
|
||||
|
||||
return (LEN_LAST_TO_TYPE[len(cnt)][cnt[-1] + hand.count(joker)],) + tuple(
|
||||
values[c] for c in hand
|
||||
)
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
cards = [(t[0], int(t[1])) for line in lines if (t := line.split())]
|
||||
|
||||
|
||||
# part 1
|
||||
values = {card: value for value, card in enumerate("23456789TJQKA")}
|
||||
cards.sort(key=lambda cv: extract_key(cv[0], values=values))
|
||||
answer_1 = sum(rank * value for rank, (_, value) in enumerate(cards, start=1))
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
values = {card: value for value, card in enumerate("J23456789TQKA")}
|
||||
cards.sort(key=lambda cv: extract_key(cv[0], values=values, joker="J"))
|
||||
answer_2 = sum(rank * value for rank, (_, value) in enumerate(cards, start=1))
|
||||
print(f"answer 2 is {answer_2}")
|
29
src/holt59/aoc/2023/day8.py
Normal file
29
src/holt59/aoc/2023/day8.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import itertools
|
||||
import math
|
||||
import sys
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
sequence = lines[0]
|
||||
nodes = {
|
||||
p[0]: {d: n for d, n in zip("LR", p[1].strip("()").split(", "))}
|
||||
for line in lines[2:]
|
||||
if (p := line.split(" = "))
|
||||
}
|
||||
|
||||
|
||||
def path(start: str):
|
||||
path = [start]
|
||||
it_seq = iter(itertools.cycle(sequence))
|
||||
while not path[-1].endswith("Z"):
|
||||
path.append(nodes[path[-1]][next(it_seq)])
|
||||
return path
|
||||
|
||||
|
||||
# part 1
|
||||
answer_1 = len(path(next(node for node in nodes if node.endswith("A")))) - 1
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = math.lcm(*(len(path(node)) - 1 for node in nodes if node.endswith("A")))
|
||||
print(f"answer 2 is {answer_2}")
|
29
src/holt59/aoc/2023/day9.py
Normal file
29
src/holt59/aoc/2023/day9.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import sys
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
data = [[int(c) for c in line.split()] for line in lines]
|
||||
|
||||
right_values: list[int] = []
|
||||
left_values: list[int] = []
|
||||
for values in data:
|
||||
diffs = [values]
|
||||
while any(d != 0 for d in diffs[-1]):
|
||||
diffs.append([rhs - lhs for lhs, rhs in zip(diffs[-1][:-1], diffs[-1][1:])])
|
||||
|
||||
rhs: list[int] = [0]
|
||||
lhs: list[int] = [0]
|
||||
for cx in range(len(diffs) - 1):
|
||||
rhs.append(diffs[-cx - 2][-1] + rhs[cx])
|
||||
lhs.append(diffs[-cx - 2][0] - lhs[cx])
|
||||
|
||||
right_values.append(rhs[-1])
|
||||
left_values.append(lhs[-1])
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(right_values)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = sum(left_values)
|
||||
print(f"answer 2 is {answer_2}")
|
49
src/holt59/aoc/__main__.py
Normal file
49
src/holt59/aoc/__main__.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import argparse
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser("Holt59 Advent-Of-Code Runner")
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="verbose mode")
|
||||
parser.add_argument("-t", "--test", action="store_true", help="test mode")
|
||||
parser.add_argument(
|
||||
"-u", "--user", type=str, default="holt59", help="user input to use"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--input",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="input to use (override user and test)",
|
||||
)
|
||||
parser.add_argument("-y", "--year", type=int, help="year to run", default=2023)
|
||||
parser.add_argument("day", type=int, help="day to run")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
verbose: bool = args.verbose
|
||||
test: bool = args.test
|
||||
user: str = args.user
|
||||
input_path: Path | None = args.input
|
||||
|
||||
year: int = args.year
|
||||
day: int = args.day
|
||||
|
||||
# TODO: change this
|
||||
if verbose:
|
||||
os.environ["AOC_VERBOSE"] = "True"
|
||||
|
||||
if input_path is None:
|
||||
input_path = Path(__file__).parent.joinpath(
|
||||
"inputs", "tests" if test else user, str(year), f"day{day}.txt"
|
||||
)
|
||||
assert input_path.exists(), f"{input_path} missing"
|
||||
|
||||
with open(input_path) as fp:
|
||||
sys.stdin = fp
|
||||
importlib.import_module(f".{year}.day{day}", __package__)
|
||||
|
||||
sys.stdin = sys.__stdin__
|
2000
src/holt59/aoc/inputs/holt59/2021/day1.txt
Normal file
2000
src/holt59/aoc/inputs/holt59/2021/day1.txt
Normal file
File diff suppressed because it is too large
Load Diff
0
src/holt59/aoc/inputs/holt59/2021/day10.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day10.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day11.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day11.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day12.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day12.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day13.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day13.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day14.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day14.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day15.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day15.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day16.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day16.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day17.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day17.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day18.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day18.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day19.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day19.txt
Normal file
1000
src/holt59/aoc/inputs/holt59/2021/day2.txt
Normal file
1000
src/holt59/aoc/inputs/holt59/2021/day2.txt
Normal file
File diff suppressed because it is too large
Load Diff
0
src/holt59/aoc/inputs/holt59/2021/day20.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day20.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day21.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day21.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day22.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day22.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day23.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day23.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day24.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day24.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day25.txt
Normal file
0
src/holt59/aoc/inputs/holt59/2021/day25.txt
Normal file
1000
src/holt59/aoc/inputs/holt59/2021/day3.txt
Normal file
1000
src/holt59/aoc/inputs/holt59/2021/day3.txt
Normal file
File diff suppressed because it is too large
Load Diff
601
src/holt59/aoc/inputs/holt59/2021/day4.txt
Normal file
601
src/holt59/aoc/inputs/holt59/2021/day4.txt
Normal file
@@ -0,0 +1,601 @@
|
||||
46,12,57,37,14,78,31,71,87,52,64,97,10,35,54,36,27,84,80,94,99,22,0,11,30,44,86,59,66,7,90,21,51,53,92,8,76,41,39,77,42,88,29,24,60,17,68,13,79,67,50,82,25,61,20,16,6,3,81,19,85,9,28,56,75,96,2,26,1,62,33,63,32,73,18,48,43,65,98,5,91,69,47,4,38,23,49,34,55,83,93,45,72,95,40,15,58,74,70,89
|
||||
|
||||
37 72 60 35 89
|
||||
32 49 4 77 82
|
||||
30 26 27 63 88
|
||||
29 43 16 34 58
|
||||
48 33 96 79 94
|
||||
|
||||
41 94 77 43 87
|
||||
2 17 82 96 25
|
||||
95 49 32 12 9
|
||||
59 33 67 71 64
|
||||
88 54 93 85 30
|
||||
|
||||
78 84 73 64 81
|
||||
6 66 54 21 15
|
||||
72 88 69 5 93
|
||||
11 96 38 95 44
|
||||
13 41 94 55 48
|
||||
|
||||
5 14 2 82 33
|
||||
56 26 0 84 92
|
||||
8 95 24 54 25
|
||||
68 67 15 85 47
|
||||
20 91 36 13 88
|
||||
|
||||
39 26 33 65 32
|
||||
78 72 80 51 0
|
||||
35 64 60 18 31
|
||||
93 59 83 54 74
|
||||
86 5 9 98 69
|
||||
|
||||
0 8 20 18 70
|
||||
5 29 65 21 57
|
||||
68 61 83 63 51
|
||||
91 73 77 75 80
|
||||
35 62 16 32 10
|
||||
|
||||
51 78 58 67 93
|
||||
50 14 99 5 31
|
||||
6 21 48 30 83
|
||||
22 33 23 1 34
|
||||
2 72 57 54 42
|
||||
|
||||
15 68 4 24 49
|
||||
12 9 74 88 51
|
||||
91 19 50 76 75
|
||||
80 84 23 17 53
|
||||
67 42 22 85 36
|
||||
|
||||
41 78 11 69 9
|
||||
90 25 98 65 77
|
||||
97 53 37 84 89
|
||||
58 63 5 55 1
|
||||
24 10 74 20 82
|
||||
|
||||
42 19 95 89 49
|
||||
61 31 50 76 3
|
||||
34 47 32 69 86
|
||||
78 68 99 11 91
|
||||
55 12 73 45 23
|
||||
|
||||
24 53 95 64 14
|
||||
40 29 71 57 97
|
||||
62 70 25 22 2
|
||||
88 68 33 82 59
|
||||
72 38 76 78 43
|
||||
|
||||
73 36 84 90 40
|
||||
16 4 57 9 29
|
||||
38 97 46 51 83
|
||||
86 88 99 44 32
|
||||
54 49 37 43 62
|
||||
|
||||
18 66 17 49 27
|
||||
24 93 91 87 72
|
||||
54 37 77 43 10
|
||||
88 80 60 15 79
|
||||
47 68 12 2 69
|
||||
|
||||
9 23 13 57 68
|
||||
38 97 63 88 98
|
||||
96 62 65 82 58
|
||||
61 83 29 47 40
|
||||
21 86 20 16 56
|
||||
|
||||
27 90 37 97 52
|
||||
14 96 76 21 79
|
||||
0 43 63 81 56
|
||||
42 62 23 55 74
|
||||
45 72 77 44 47
|
||||
|
||||
8 78 63 24 87
|
||||
9 23 12 17 68
|
||||
36 83 45 61 50
|
||||
84 77 18 86 37
|
||||
31 26 19 49 94
|
||||
|
||||
72 84 59 48 40
|
||||
92 98 35 1 80
|
||||
83 15 85 63 39
|
||||
2 64 58 13 20
|
||||
29 88 60 12 74
|
||||
|
||||
21 94 52 6 4
|
||||
89 70 39 23 64
|
||||
96 87 31 54 14
|
||||
88 35 83 13 56
|
||||
84 10 98 48 68
|
||||
|
||||
70 33 48 21 37
|
||||
91 95 65 38 77
|
||||
92 14 26 96 60
|
||||
12 6 73 13 81
|
||||
54 55 2 45 80
|
||||
|
||||
60 11 67 95 28
|
||||
5 32 0 71 12
|
||||
47 78 13 54 43
|
||||
49 89 82 66 77
|
||||
26 53 19 79 3
|
||||
|
||||
81 9 53 72 29
|
||||
56 35 60 44 45
|
||||
42 94 96 88 64
|
||||
15 92 4 6 14
|
||||
97 11 17 61 63
|
||||
|
||||
24 43 33 9 34
|
||||
36 28 69 35 7
|
||||
47 4 14 82 38
|
||||
11 1 52 0 49
|
||||
93 87 98 41 5
|
||||
|
||||
37 79 99 34 77
|
||||
38 26 25 95 70
|
||||
28 78 40 33 86
|
||||
41 57 96 10 24
|
||||
9 74 72 50 81
|
||||
|
||||
18 96 52 29 61
|
||||
38 90 1 48 51
|
||||
78 11 27 55 97
|
||||
33 21 87 93 67
|
||||
79 46 94 45 2
|
||||
|
||||
27 63 6 90 10
|
||||
3 60 24 5 89
|
||||
78 72 76 54 8
|
||||
33 22 87 51 58
|
||||
4 37 64 91 43
|
||||
|
||||
63 73 87 80 89
|
||||
29 14 95 48 3
|
||||
71 55 69 9 67
|
||||
30 99 19 2 86
|
||||
26 72 88 85 37
|
||||
|
||||
12 57 81 78 40
|
||||
35 4 55 15 39
|
||||
33 45 25 60 70
|
||||
86 79 88 52 3
|
||||
90 20 28 59 85
|
||||
|
||||
92 51 98 47 99
|
||||
41 78 65 4 46
|
||||
19 87 39 89 17
|
||||
12 23 36 29 44
|
||||
6 82 71 16 37
|
||||
|
||||
8 34 81 67 80
|
||||
83 92 13 11 41
|
||||
39 89 93 49 43
|
||||
20 69 3 74 76
|
||||
44 72 68 70 45
|
||||
|
||||
66 39 94 98 28
|
||||
72 4 25 77 76
|
||||
56 41 84 59 40
|
||||
36 87 18 44 73
|
||||
29 45 79 55 95
|
||||
|
||||
45 91 2 92 16
|
||||
21 47 86 81 56
|
||||
31 11 62 5 95
|
||||
39 1 30 65 33
|
||||
42 60 17 18 83
|
||||
|
||||
86 11 77 30 43
|
||||
51 88 73 98 94
|
||||
72 63 38 56 10
|
||||
57 92 49 7 41
|
||||
79 75 34 23 54
|
||||
|
||||
56 95 3 43 65
|
||||
39 62 93 19 27
|
||||
61 41 99 96 52
|
||||
4 92 77 98 70
|
||||
16 54 11 17 57
|
||||
|
||||
6 63 10 71 58
|
||||
64 70 50 92 0
|
||||
7 14 99 45 26
|
||||
78 17 44 46 73
|
||||
77 38 62 53 37
|
||||
|
||||
31 82 67 55 27
|
||||
57 58 84 6 15
|
||||
14 41 49 8 85
|
||||
12 32 91 42 19
|
||||
23 1 87 54 29
|
||||
|
||||
54 60 43 26 4
|
||||
78 17 28 67 5
|
||||
87 93 90 71 22
|
||||
13 30 16 21 85
|
||||
55 74 52 1 29
|
||||
|
||||
50 16 70 32 33
|
||||
6 94 52 66 22
|
||||
97 64 98 72 39
|
||||
27 69 99 34 26
|
||||
36 91 37 21 14
|
||||
|
||||
7 97 64 28 18
|
||||
85 80 14 37 34
|
||||
72 1 22 58 73
|
||||
53 3 68 17 0
|
||||
29 44 56 95 32
|
||||
|
||||
30 66 93 24 92
|
||||
48 80 79 86 27
|
||||
89 13 62 94 81
|
||||
70 65 61 8 54
|
||||
96 97 20 90 34
|
||||
|
||||
87 76 4 7 43
|
||||
92 55 80 25 62
|
||||
79 6 88 35 30
|
||||
10 32 5 45 17
|
||||
36 27 33 68 63
|
||||
|
||||
72 69 27 88 41
|
||||
34 53 42 84 3
|
||||
58 18 22 66 65
|
||||
9 47 85 12 62
|
||||
73 90 91 57 33
|
||||
|
||||
67 16 50 58 52
|
||||
68 70 84 98 69
|
||||
4 72 9 64 0
|
||||
93 97 39 26 5
|
||||
3 37 79 7 82
|
||||
|
||||
61 57 88 54 70
|
||||
77 8 94 81 63
|
||||
39 48 18 13 10
|
||||
55 23 27 4 73
|
||||
3 5 64 0 96
|
||||
|
||||
62 27 0 52 19
|
||||
28 57 83 25 41
|
||||
5 59 24 33 80
|
||||
37 85 2 86 43
|
||||
22 94 50 8 20
|
||||
|
||||
54 32 34 47 87
|
||||
71 22 43 85 24
|
||||
11 68 58 36 46
|
||||
35 56 61 67 18
|
||||
70 23 72 5 59
|
||||
|
||||
3 96 41 45 32
|
||||
68 2 56 28 24
|
||||
87 38 40 75 26
|
||||
53 64 73 80 81
|
||||
54 88 20 6 18
|
||||
|
||||
64 55 51 96 47
|
||||
59 35 49 67 71
|
||||
36 91 61 76 68
|
||||
6 94 20 8 27
|
||||
60 88 45 7 82
|
||||
|
||||
87 94 51 91 1
|
||||
96 60 28 97 37
|
||||
26 27 74 53 35
|
||||
88 89 11 77 8
|
||||
73 47 18 59 6
|
||||
|
||||
46 50 19 36 83
|
||||
69 28 4 44 70
|
||||
45 20 63 27 1
|
||||
53 38 9 47 67
|
||||
91 31 79 73 86
|
||||
|
||||
45 3 98 91 60
|
||||
40 7 78 34 83
|
||||
52 73 59 13 4
|
||||
38 15 82 86 79
|
||||
42 11 17 20 62
|
||||
|
||||
65 86 38 20 72
|
||||
78 45 73 74 25
|
||||
62 42 24 75 3
|
||||
81 8 35 50 51
|
||||
44 11 94 85 57
|
||||
|
||||
13 86 55 65 96
|
||||
53 18 43 76 20
|
||||
41 14 32 52 38
|
||||
90 59 80 68 7
|
||||
2 23 92 39 50
|
||||
|
||||
96 62 85 24 14
|
||||
37 5 11 91 45
|
||||
61 28 23 34 77
|
||||
43 48 20 0 21
|
||||
10 35 2 26 97
|
||||
|
||||
89 5 40 34 84
|
||||
90 6 72 68 10
|
||||
13 64 71 31 76
|
||||
53 60 9 92 62
|
||||
69 98 8 50 3
|
||||
|
||||
17 86 10 75 79
|
||||
67 94 78 40 56
|
||||
11 85 82 50 46
|
||||
53 39 22 9 61
|
||||
59 73 72 33 45
|
||||
|
||||
65 22 18 96 95
|
||||
55 86 67 52 69
|
||||
10 2 60 83 98
|
||||
43 61 87 88 66
|
||||
41 24 8 84 33
|
||||
|
||||
31 53 98 70 91
|
||||
33 34 48 83 9
|
||||
40 39 29 71 65
|
||||
69 10 62 30 4
|
||||
52 21 11 93 75
|
||||
|
||||
8 94 53 85 89
|
||||
13 84 58 59 29
|
||||
97 7 21 25 96
|
||||
45 54 34 22 63
|
||||
37 17 49 68 67
|
||||
|
||||
86 87 84 24 10
|
||||
82 32 36 59 50
|
||||
8 62 79 71 43
|
||||
49 23 85 69 58
|
||||
21 66 42 25 56
|
||||
|
||||
65 88 43 25 19
|
||||
26 36 63 5 6
|
||||
37 54 75 1 38
|
||||
95 46 83 66 28
|
||||
4 90 80 99 85
|
||||
|
||||
78 83 7 77 34
|
||||
27 92 93 96 82
|
||||
40 95 52 32 43
|
||||
17 28 69 41 85
|
||||
21 65 39 58 19
|
||||
|
||||
11 84 28 90 36
|
||||
74 4 62 5 46
|
||||
22 8 45 40 98
|
||||
12 6 30 9 82
|
||||
37 2 53 29 41
|
||||
|
||||
17 65 31 86 57
|
||||
73 16 24 67 53
|
||||
60 93 88 45 26
|
||||
14 80 94 7 44
|
||||
55 78 49 8 82
|
||||
|
||||
95 38 81 25 76
|
||||
29 13 83 47 12
|
||||
17 69 4 43 28
|
||||
63 84 39 52 34
|
||||
1 97 41 88 8
|
||||
|
||||
70 40 16 83 3
|
||||
15 49 20 74 48
|
||||
71 30 21 28 84
|
||||
29 10 97 1 18
|
||||
57 50 63 35 69
|
||||
|
||||
40 13 67 9 41
|
||||
71 76 8 54 24
|
||||
15 97 92 49 96
|
||||
61 34 23 81 31
|
||||
11 38 48 37 86
|
||||
|
||||
77 36 32 75 7
|
||||
38 18 84 26 2
|
||||
19 13 99 83 20
|
||||
35 51 74 6 27
|
||||
71 48 15 66 69
|
||||
|
||||
91 57 41 3 99
|
||||
74 55 81 77 43
|
||||
36 52 47 49 45
|
||||
85 65 5 38 50
|
||||
90 68 70 16 0
|
||||
|
||||
1 90 28 86 27
|
||||
73 36 67 11 14
|
||||
71 31 10 65 55
|
||||
78 21 16 69 12
|
||||
87 24 33 83 68
|
||||
|
||||
90 17 10 84 45
|
||||
5 68 69 27 92
|
||||
6 63 98 3 46
|
||||
94 48 59 34 43
|
||||
39 88 12 33 73
|
||||
|
||||
12 31 33 98 63
|
||||
65 51 94 83 92
|
||||
41 38 84 91 66
|
||||
47 28 76 54 3
|
||||
48 36 11 13 27
|
||||
|
||||
51 84 96 16 8
|
||||
64 26 74 30 48
|
||||
29 41 68 97 87
|
||||
9 38 1 15 39
|
||||
98 3 45 53 14
|
||||
|
||||
53 70 90 95 86
|
||||
35 22 85 45 66
|
||||
93 0 83 30 88
|
||||
64 57 68 36 3
|
||||
5 51 19 20 89
|
||||
|
||||
9 36 69 46 44
|
||||
37 7 99 57 45
|
||||
79 10 86 58 30
|
||||
49 98 52 90 27
|
||||
14 51 88 60 81
|
||||
|
||||
73 97 91 19 48
|
||||
76 43 18 83 67
|
||||
62 9 11 82 55
|
||||
24 17 33 53 22
|
||||
75 8 56 1 21
|
||||
|
||||
27 97 53 0 89
|
||||
30 70 3 80 54
|
||||
56 93 40 64 35
|
||||
46 82 1 44 65
|
||||
6 59 45 32 34
|
||||
|
||||
87 58 73 45 69
|
||||
24 49 89 71 83
|
||||
94 6 53 68 50
|
||||
28 25 88 47 0
|
||||
36 13 31 18 55
|
||||
|
||||
52 63 37 66 9
|
||||
34 77 57 6 55
|
||||
85 80 97 78 74
|
||||
95 75 67 96 29
|
||||
22 73 92 69 47
|
||||
|
||||
79 97 80 36 73
|
||||
38 77 35 32 53
|
||||
2 37 29 6 89
|
||||
78 91 15 47 34
|
||||
11 52 64 84 0
|
||||
|
||||
69 30 21 99 46
|
||||
72 4 15 25 42
|
||||
67 98 81 91 63
|
||||
70 20 57 65 14
|
||||
0 78 19 8 87
|
||||
|
||||
20 4 98 33 85
|
||||
76 17 94 65 35
|
||||
95 69 72 52 71
|
||||
23 25 50 38 27
|
||||
43 49 96 53 99
|
||||
|
||||
16 27 34 65 36
|
||||
10 40 84 60 82
|
||||
80 2 54 67 70
|
||||
52 94 79 17 56
|
||||
5 14 77 91 88
|
||||
|
||||
32 90 50 66 39
|
||||
30 16 14 20 10
|
||||
4 42 88 59 12
|
||||
75 84 54 51 48
|
||||
33 24 13 89 43
|
||||
|
||||
78 42 34 65 51
|
||||
75 72 3 99 61
|
||||
15 50 59 8 89
|
||||
71 18 9 54 53
|
||||
43 39 97 56 19
|
||||
|
||||
50 43 83 4 30
|
||||
89 97 58 35 39
|
||||
11 24 61 41 25
|
||||
87 99 93 15 34
|
||||
31 57 3 45 44
|
||||
|
||||
70 21 63 24 38
|
||||
34 23 88 7 51
|
||||
43 18 76 46 49
|
||||
60 78 47 8 12
|
||||
11 66 98 25 74
|
||||
|
||||
30 17 23 10 92
|
||||
12 85 69 81 91
|
||||
47 80 28 29 58
|
||||
73 44 77 50 32
|
||||
76 54 78 75 60
|
||||
|
||||
71 53 86 48 98
|
||||
90 37 79 8 56
|
||||
99 42 97 36 15
|
||||
31 85 34 10 40
|
||||
43 89 57 72 51
|
||||
|
||||
48 0 65 55 90
|
||||
45 76 69 97 4
|
||||
42 52 46 77 56
|
||||
64 62 68 35 72
|
||||
71 10 27 30 16
|
||||
|
||||
41 69 63 88 57
|
||||
25 56 23 78 80
|
||||
8 92 59 66 97
|
||||
48 61 77 15 14
|
||||
87 47 91 12 71
|
||||
|
||||
51 46 15 2 49
|
||||
48 33 23 16 4
|
||||
80 41 43 59 83
|
||||
62 13 20 63 85
|
||||
99 30 7 87 8
|
||||
|
||||
69 80 96 43 47
|
||||
61 75 45 62 15
|
||||
32 22 91 83 58
|
||||
82 13 50 52 8
|
||||
89 20 63 73 14
|
||||
|
||||
40 2 96 52 73
|
||||
25 27 26 43 34
|
||||
60 38 80 78 5
|
||||
83 63 48 10 66
|
||||
97 46 53 74 86
|
||||
|
||||
46 7 0 69 15
|
||||
79 19 85 27 73
|
||||
63 45 5 49 54
|
||||
93 29 84 28 66
|
||||
72 23 99 8 33
|
||||
|
||||
20 72 85 99 49
|
||||
69 0 10 52 23
|
||||
88 56 28 67 21
|
||||
16 91 83 54 81
|
||||
14 73 32 30 59
|
||||
|
||||
31 52 63 12 3
|
||||
96 20 82 6 89
|
||||
55 38 8 95 40
|
||||
5 60 84 81 75
|
||||
51 14 65 27 61
|
||||
|
||||
46 93 1 47 76
|
||||
8 98 7 16 63
|
||||
44 78 17 14 92
|
||||
42 62 20 12 68
|
||||
56 3 74 6 21
|
||||
|
||||
8 94 11 40 44
|
||||
43 92 78 91 18
|
||||
75 80 12 54 26
|
||||
67 9 45 22 21
|
||||
86 1 90 36 30
|
||||
|
||||
21 19 83 90 8
|
||||
50 28 45 65 75
|
||||
59 88 25 29 70
|
||||
58 23 0 95 49
|
||||
36 68 76 78 66
|
||||
|
||||
77 28 43 56 97
|
||||
73 71 8 72 46
|
||||
23 25 70 69 41
|
||||
90 17 34 67 48
|
||||
32 75 81 63 21
|
500
src/holt59/aoc/inputs/holt59/2021/day5.txt
Normal file
500
src/holt59/aoc/inputs/holt59/2021/day5.txt
Normal file
@@ -0,0 +1,500 @@
|
||||
657,934 -> 657,926
|
||||
130,34 -> 570,474
|
||||
478,716 -> 226,464
|
||||
861,110 -> 861,167
|
||||
448,831 -> 370,831
|
||||
75,738 -> 390,738
|
||||
26,880 -> 864,42
|
||||
965,658 -> 527,220
|
||||
208,381 -> 80,381
|
||||
523,475 -> 807,475
|
||||
219,69 -> 219,434
|
||||
793,538 -> 534,797
|
||||
754,602 -> 754,148
|
||||
443,327 -> 443,611
|
||||
606,395 -> 546,395
|
||||
980,56 -> 51,985
|
||||
619,325 -> 354,325
|
||||
342,123 -> 819,600
|
||||
290,533 -> 374,533
|
||||
598,77 -> 598,75
|
||||
605,302 -> 605,636
|
||||
97,981 -> 692,386
|
||||
278,779 -> 278,800
|
||||
661,377 -> 661,10
|
||||
726,108 -> 518,316
|
||||
271,883 -> 271,50
|
||||
382,271 -> 606,271
|
||||
963,358 -> 891,286
|
||||
496,880 -> 496,855
|
||||
211,142 -> 211,49
|
||||
841,866 -> 260,285
|
||||
841,849 -> 173,181
|
||||
927,326 -> 391,862
|
||||
396,558 -> 459,558
|
||||
753,183 -> 953,183
|
||||
941,698 -> 941,407
|
||||
347,612 -> 347,476
|
||||
18,340 -> 18,612
|
||||
140,299 -> 797,956
|
||||
714,907 -> 714,228
|
||||
966,155 -> 194,927
|
||||
769,674 -> 712,674
|
||||
644,675 -> 948,979
|
||||
703,872 -> 812,763
|
||||
26,629 -> 120,535
|
||||
844,738 -> 844,253
|
||||
798,133 -> 798,795
|
||||
27,318 -> 288,57
|
||||
38,545 -> 872,545
|
||||
827,351 -> 195,983
|
||||
818,45 -> 21,842
|
||||
257,559 -> 626,928
|
||||
145,925 -> 886,184
|
||||
83,618 -> 590,111
|
||||
326,243 -> 53,243
|
||||
489,278 -> 526,278
|
||||
783,693 -> 783,525
|
||||
495,636 -> 495,585
|
||||
374,716 -> 215,557
|
||||
839,536 -> 839,966
|
||||
850,468 -> 955,468
|
||||
55,799 -> 55,447
|
||||
472,722 -> 296,898
|
||||
390,731 -> 120,461
|
||||
405,493 -> 208,296
|
||||
807,42 -> 56,793
|
||||
476,327 -> 655,327
|
||||
24,965 -> 967,22
|
||||
776,211 -> 776,850
|
||||
489,20 -> 822,20
|
||||
630,740 -> 871,499
|
||||
743,493 -> 283,953
|
||||
62,429 -> 62,720
|
||||
806,270 -> 806,332
|
||||
550,154 -> 107,597
|
||||
71,713 -> 533,251
|
||||
620,575 -> 620,156
|
||||
726,829 -> 143,246
|
||||
944,553 -> 468,553
|
||||
185,582 -> 185,468
|
||||
845,266 -> 212,899
|
||||
654,97 -> 265,486
|
||||
726,609 -> 726,147
|
||||
631,76 -> 860,76
|
||||
835,24 -> 928,24
|
||||
712,719 -> 74,81
|
||||
616,478 -> 616,117
|
||||
903,226 -> 903,577
|
||||
440,699 -> 136,395
|
||||
215,705 -> 890,30
|
||||
20,24 -> 981,985
|
||||
102,144 -> 850,892
|
||||
695,967 -> 582,967
|
||||
219,284 -> 219,388
|
||||
359,833 -> 665,833
|
||||
389,55 -> 305,55
|
||||
59,32 -> 957,930
|
||||
815,198 -> 64,949
|
||||
699,540 -> 717,558
|
||||
215,682 -> 182,682
|
||||
805,489 -> 328,489
|
||||
43,546 -> 578,546
|
||||
489,181 -> 489,363
|
||||
266,391 -> 266,582
|
||||
863,368 -> 448,368
|
||||
83,236 -> 83,487
|
||||
874,875 -> 874,413
|
||||
799,90 -> 799,802
|
||||
253,29 -> 253,905
|
||||
136,446 -> 435,745
|
||||
830,534 -> 550,534
|
||||
183,785 -> 107,785
|
||||
81,517 -> 159,517
|
||||
359,941 -> 359,560
|
||||
71,546 -> 948,546
|
||||
596,811 -> 596,791
|
||||
255,960 -> 255,159
|
||||
788,15 -> 788,682
|
||||
240,55 -> 240,244
|
||||
51,423 -> 137,423
|
||||
504,418 -> 809,723
|
||||
131,842 -> 914,59
|
||||
727,790 -> 82,145
|
||||
281,509 -> 841,509
|
||||
797,807 -> 834,807
|
||||
333,499 -> 790,499
|
||||
215,328 -> 215,139
|
||||
500,898 -> 500,862
|
||||
75,217 -> 777,919
|
||||
17,264 -> 17,446
|
||||
852,755 -> 150,755
|
||||
865,186 -> 385,186
|
||||
158,192 -> 158,733
|
||||
196,261 -> 196,128
|
||||
989,960 -> 131,102
|
||||
807,393 -> 807,153
|
||||
507,579 -> 507,764
|
||||
468,76 -> 535,76
|
||||
381,357 -> 659,357
|
||||
794,277 -> 749,277
|
||||
51,152 -> 546,647
|
||||
797,959 -> 458,959
|
||||
82,156 -> 967,156
|
||||
261,624 -> 460,624
|
||||
597,53 -> 197,53
|
||||
153,507 -> 411,765
|
||||
305,717 -> 768,717
|
||||
344,954 -> 344,217
|
||||
194,432 -> 545,432
|
||||
346,46 -> 557,46
|
||||
685,599 -> 685,312
|
||||
49,719 -> 49,631
|
||||
499,668 -> 304,863
|
||||
262,405 -> 554,405
|
||||
87,64 -> 295,64
|
||||
859,675 -> 74,675
|
||||
663,776 -> 99,212
|
||||
232,189 -> 232,904
|
||||
777,276 -> 703,276
|
||||
704,492 -> 86,492
|
||||
142,736 -> 514,364
|
||||
418,611 -> 224,417
|
||||
602,571 -> 602,424
|
||||
152,603 -> 248,603
|
||||
915,673 -> 143,673
|
||||
538,32 -> 128,32
|
||||
975,885 -> 975,344
|
||||
870,511 -> 870,756
|
||||
330,798 -> 46,798
|
||||
440,195 -> 587,195
|
||||
739,237 -> 568,66
|
||||
54,838 -> 196,980
|
||||
370,556 -> 47,556
|
||||
124,575 -> 748,575
|
||||
261,283 -> 880,902
|
||||
784,91 -> 426,449
|
||||
764,670 -> 148,670
|
||||
32,51 -> 967,986
|
||||
807,906 -> 10,906
|
||||
470,488 -> 579,597
|
||||
274,649 -> 285,649
|
||||
221,540 -> 221,94
|
||||
914,957 -> 914,510
|
||||
879,825 -> 145,91
|
||||
438,833 -> 438,775
|
||||
191,844 -> 911,124
|
||||
145,763 -> 595,763
|
||||
504,81 -> 622,199
|
||||
834,206 -> 834,704
|
||||
908,308 -> 815,308
|
||||
929,567 -> 929,322
|
||||
805,50 -> 620,235
|
||||
36,409 -> 133,312
|
||||
345,375 -> 19,701
|
||||
468,948 -> 468,108
|
||||
109,547 -> 446,547
|
||||
929,916 -> 69,56
|
||||
927,857 -> 318,248
|
||||
833,948 -> 833,61
|
||||
559,787 -> 559,982
|
||||
293,825 -> 293,775
|
||||
508,744 -> 545,744
|
||||
827,713 -> 753,639
|
||||
88,775 -> 555,775
|
||||
523,812 -> 684,812
|
||||
307,142 -> 307,265
|
||||
636,40 -> 355,321
|
||||
891,875 -> 891,25
|
||||
301,423 -> 712,12
|
||||
922,187 -> 219,890
|
||||
45,447 -> 230,262
|
||||
114,568 -> 233,687
|
||||
573,398 -> 677,398
|
||||
334,101 -> 324,101
|
||||
957,277 -> 957,652
|
||||
943,834 -> 610,834
|
||||
523,632 -> 523,379
|
||||
958,361 -> 90,361
|
||||
408,824 -> 380,824
|
||||
647,314 -> 647,449
|
||||
747,83 -> 59,83
|
||||
776,104 -> 937,104
|
||||
16,984 -> 989,11
|
||||
362,581 -> 362,226
|
||||
72,962 -> 940,94
|
||||
319,877 -> 319,122
|
||||
310,206 -> 986,882
|
||||
794,877 -> 267,877
|
||||
855,58 -> 976,58
|
||||
699,971 -> 598,971
|
||||
162,556 -> 162,440
|
||||
494,859 -> 494,255
|
||||
794,210 -> 142,862
|
||||
275,510 -> 548,510
|
||||
739,592 -> 739,793
|
||||
376,985 -> 376,990
|
||||
755,264 -> 280,739
|
||||
187,34 -> 187,688
|
||||
770,827 -> 770,548
|
||||
10,68 -> 913,971
|
||||
571,427 -> 571,944
|
||||
153,211 -> 153,560
|
||||
976,972 -> 55,51
|
||||
103,611 -> 674,40
|
||||
95,972 -> 924,143
|
||||
929,94 -> 38,985
|
||||
777,330 -> 60,330
|
||||
312,430 -> 312,326
|
||||
549,433 -> 269,433
|
||||
477,267 -> 477,403
|
||||
598,375 -> 19,375
|
||||
512,799 -> 512,831
|
||||
348,700 -> 348,43
|
||||
165,97 -> 63,199
|
||||
38,835 -> 38,828
|
||||
282,334 -> 282,909
|
||||
14,891 -> 390,515
|
||||
930,657 -> 334,61
|
||||
630,341 -> 630,85
|
||||
671,464 -> 319,112
|
||||
949,340 -> 894,285
|
||||
663,916 -> 245,916
|
||||
114,395 -> 286,223
|
||||
335,804 -> 529,804
|
||||
567,338 -> 14,891
|
||||
623,705 -> 379,949
|
||||
82,864 -> 545,401
|
||||
932,128 -> 932,134
|
||||
291,294 -> 291,101
|
||||
739,765 -> 739,757
|
||||
460,94 -> 892,94
|
||||
375,673 -> 367,681
|
||||
81,831 -> 90,831
|
||||
890,402 -> 890,138
|
||||
775,547 -> 790,547
|
||||
49,927 -> 966,10
|
||||
23,116 -> 257,116
|
||||
923,75 -> 18,980
|
||||
63,986 -> 687,362
|
||||
369,844 -> 357,844
|
||||
790,188 -> 644,188
|
||||
557,282 -> 557,669
|
||||
861,173 -> 390,644
|
||||
480,529 -> 893,529
|
||||
32,960 -> 830,162
|
||||
368,725 -> 368,40
|
||||
502,600 -> 701,600
|
||||
63,977 -> 873,167
|
||||
463,518 -> 788,193
|
||||
738,406 -> 324,406
|
||||
162,931 -> 822,931
|
||||
377,487 -> 707,817
|
||||
610,319 -> 901,319
|
||||
586,658 -> 690,658
|
||||
25,288 -> 53,288
|
||||
760,602 -> 760,628
|
||||
294,62 -> 951,62
|
||||
222,773 -> 661,334
|
||||
151,483 -> 646,483
|
||||
272,852 -> 317,852
|
||||
557,906 -> 503,960
|
||||
736,445 -> 736,703
|
||||
241,376 -> 241,692
|
||||
835,41 -> 835,369
|
||||
987,743 -> 987,210
|
||||
42,700 -> 42,244
|
||||
646,136 -> 646,440
|
||||
544,751 -> 404,751
|
||||
295,651 -> 295,805
|
||||
687,878 -> 113,878
|
||||
290,142 -> 604,142
|
||||
579,920 -> 579,807
|
||||
12,985 -> 987,10
|
||||
919,940 -> 919,808
|
||||
770,143 -> 770,832
|
||||
114,76 -> 962,76
|
||||
876,882 -> 428,434
|
||||
861,139 -> 861,320
|
||||
888,59 -> 888,39
|
||||
629,823 -> 707,823
|
||||
296,598 -> 296,305
|
||||
61,54 -> 578,54
|
||||
864,58 -> 253,58
|
||||
71,861 -> 306,861
|
||||
682,181 -> 326,537
|
||||
307,418 -> 307,910
|
||||
810,251 -> 810,431
|
||||
151,836 -> 602,385
|
||||
954,987 -> 243,276
|
||||
724,272 -> 350,646
|
||||
134,295 -> 434,295
|
||||
178,235 -> 802,859
|
||||
832,688 -> 832,573
|
||||
165,334 -> 165,378
|
||||
816,26 -> 114,728
|
||||
668,192 -> 540,192
|
||||
730,341 -> 969,341
|
||||
951,169 -> 286,834
|
||||
647,115 -> 886,115
|
||||
664,288 -> 507,131
|
||||
609,362 -> 609,295
|
||||
747,479 -> 287,19
|
||||
350,967 -> 350,725
|
||||
117,383 -> 311,383
|
||||
871,124 -> 292,124
|
||||
654,271 -> 547,271
|
||||
525,773 -> 345,953
|
||||
401,670 -> 610,670
|
||||
930,196 -> 301,825
|
||||
336,37 -> 961,662
|
||||
714,212 -> 714,667
|
||||
454,848 -> 454,107
|
||||
587,390 -> 587,577
|
||||
530,437 -> 542,437
|
||||
304,229 -> 517,229
|
||||
340,571 -> 766,571
|
||||
727,941 -> 138,352
|
||||
831,325 -> 11,325
|
||||
241,294 -> 403,456
|
||||
788,658 -> 788,126
|
||||
337,360 -> 337,589
|
||||
799,402 -> 342,402
|
||||
530,820 -> 530,319
|
||||
982,27 -> 20,989
|
||||
923,936 -> 923,721
|
||||
581,395 -> 64,912
|
||||
61,509 -> 61,827
|
||||
989,580 -> 610,580
|
||||
477,592 -> 219,592
|
||||
296,775 -> 296,58
|
||||
204,12 -> 204,457
|
||||
190,171 -> 190,673
|
||||
939,200 -> 939,457
|
||||
472,282 -> 472,631
|
||||
983,331 -> 734,331
|
||||
365,609 -> 365,817
|
||||
640,698 -> 145,698
|
||||
103,618 -> 549,618
|
||||
454,319 -> 454,346
|
||||
650,815 -> 381,546
|
||||
624,603 -> 507,603
|
||||
966,445 -> 723,445
|
||||
763,129 -> 763,784
|
||||
695,145 -> 695,511
|
||||
498,84 -> 435,147
|
||||
188,716 -> 967,716
|
||||
810,446 -> 810,924
|
||||
731,483 -> 731,51
|
||||
307,783 -> 307,533
|
||||
15,956 -> 956,15
|
||||
192,210 -> 882,210
|
||||
303,173 -> 38,438
|
||||
769,952 -> 769,863
|
||||
135,781 -> 405,781
|
||||
494,436 -> 494,892
|
||||
705,394 -> 714,394
|
||||
164,37 -> 164,633
|
||||
813,232 -> 813,620
|
||||
227,906 -> 222,906
|
||||
542,432 -> 414,432
|
||||
549,858 -> 88,397
|
||||
200,101 -> 958,859
|
||||
235,565 -> 469,331
|
||||
492,871 -> 503,882
|
||||
704,398 -> 869,563
|
||||
450,736 -> 746,736
|
||||
420,706 -> 420,635
|
||||
717,493 -> 686,524
|
||||
187,554 -> 717,24
|
||||
31,851 -> 315,851
|
||||
800,230 -> 466,230
|
||||
226,324 -> 226,614
|
||||
937,927 -> 937,798
|
||||
143,26 -> 534,417
|
||||
952,344 -> 12,344
|
||||
181,361 -> 782,361
|
||||
925,906 -> 415,396
|
||||
685,944 -> 470,944
|
||||
200,627 -> 290,627
|
||||
728,285 -> 728,326
|
||||
271,864 -> 271,34
|
||||
802,558 -> 207,558
|
||||
963,26 -> 84,905
|
||||
504,60 -> 529,60
|
||||
840,292 -> 180,292
|
||||
914,272 -> 914,330
|
||||
82,107 -> 925,950
|
||||
33,245 -> 33,134
|
||||
463,663 -> 463,82
|
||||
27,305 -> 27,675
|
||||
276,894 -> 891,279
|
||||
746,325 -> 746,948
|
||||
249,657 -> 341,749
|
||||
530,848 -> 28,346
|
||||
798,617 -> 798,609
|
||||
119,767 -> 312,767
|
||||
80,18 -> 674,18
|
||||
723,374 -> 583,374
|
||||
582,985 -> 239,642
|
||||
217,765 -> 217,395
|
||||
811,159 -> 609,159
|
||||
689,896 -> 501,896
|
||||
562,881 -> 562,96
|
||||
244,621 -> 629,621
|
||||
277,379 -> 277,287
|
||||
856,153 -> 20,153
|
||||
518,228 -> 518,898
|
||||
230,789 -> 243,789
|
||||
534,335 -> 534,592
|
||||
240,790 -> 413,617
|
||||
768,615 -> 768,560
|
||||
773,101 -> 912,101
|
||||
252,571 -> 767,56
|
||||
370,595 -> 681,906
|
||||
565,176 -> 565,318
|
||||
750,465 -> 750,724
|
||||
979,130 -> 120,989
|
||||
160,153 -> 160,785
|
||||
610,222 -> 610,191
|
||||
873,124 -> 130,867
|
||||
519,593 -> 519,32
|
||||
525,947 -> 525,562
|
||||
50,292 -> 291,533
|
||||
558,927 -> 960,525
|
||||
536,694 -> 249,981
|
||||
954,896 -> 277,896
|
||||
732,202 -> 732,288
|
||||
447,989 -> 541,895
|
||||
890,754 -> 367,231
|
||||
368,89 -> 564,285
|
||||
588,100 -> 588,156
|
||||
282,313 -> 943,974
|
||||
16,792 -> 495,792
|
||||
111,591 -> 111,493
|
||||
57,713 -> 685,85
|
||||
676,632 -> 676,575
|
||||
560,708 -> 560,602
|
||||
489,288 -> 489,404
|
||||
904,515 -> 443,54
|
||||
70,977 -> 985,62
|
||||
11,119 -> 11,403
|
||||
215,859 -> 937,137
|
||||
78,469 -> 110,437
|
||||
747,605 -> 747,369
|
||||
847,598 -> 847,299
|
||||
742,695 -> 159,112
|
||||
986,370 -> 986,460
|
||||
631,900 -> 771,760
|
||||
228,406 -> 683,861
|
||||
189,639 -> 61,639
|
||||
221,650 -> 820,650
|
||||
558,569 -> 834,845
|
||||
655,533 -> 558,630
|
||||
967,921 -> 967,169
|
||||
230,308 -> 429,308
|
||||
873,762 -> 873,528
|
||||
412,151 -> 412,538
|
||||
881,587 -> 881,21
|
||||
941,45 -> 26,960
|
||||
377,126 -> 700,126
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user