Compare commits

..

81 Commits

Author SHA1 Message Date
Mikaël Capelle
155e0e9521 Final a bit better. 2023-12-12 21:18:57 +01:00
Mikael CAPELLE
13b15aba76 Final. 2023-12-12 19:03:40 +01:00
Mikael CAPELLE
c7d2eb2171 WIP 2. 2023-12-12 18:54:08 +01:00
Mikael CAPELLE
edc50cb9c2 WIP. 2023-12-12 14:49:16 +01:00
Mikaël Capelle
7151499835 2022 day 12. 2023-12-12 07:46:34 +01:00
Mikaël Capelle
1a6ab1cc0e 2023 day 11. 2023-12-11 10:25:56 +01:00
Mikaël Capelle
f5aabbee8f 2021 day 8. 2023-12-10 20:23:22 +01:00
Mikaël Capelle
6c00341ab0 2023 day 10. 2023-12-10 10:09:12 +01:00
Mikaël Capelle
755e0bd4b3 2021 day 7. 2023-12-09 12:52:46 +01:00
Mikaël Capelle
a52d077a40 2021 day 6. 2023-12-09 12:36:10 +01:00
Mikaël Capelle
3fc0f94b1c 2021 day 1-5. 2023-12-09 11:01:28 +01:00
Mikaël Capelle
8a0412f926 Clean 2022. 2023-12-09 09:54:53 +01:00
Mikaël Capelle
855efeb0aa 2023 day 9. 2023-12-09 08:08:46 +01:00
Mikaël Capelle
f2a65e03e5 2023 day 8. 2023-12-08 08:44:03 +01:00
Mikaël Capelle
759f47bfab 2023 day 7. 2023-12-08 08:38:08 +01:00
Mikael CAPELLE
999207b007 Clean 2023. 2023-12-06 08:47:59 +01:00
Mikaël Capelle
d92e4744a4 2023 day 6. 2023-12-06 07:14:26 +01:00
Mikael CAPELLE
8dbf0f101c 2023 day 5. 2023-12-05 13:41:17 +01:00
Mikaël Capelle
b8d8df06d6 2023 day 5 part 1. 2023-12-05 07:22:56 +01:00
Mikaël Capelle
825ebea299 Prepare 2023 days. 2023-12-04 19:32:41 +01:00
Mikaël Capelle
869cd4477f 2023 day 4. 2023-12-04 19:30:44 +01:00
Mikaël Capelle
fd777627d6 2023 day 3. 2023-12-03 09:09:25 +01:00
Mikaël Capelle
98f605f30e 2023 day 2. 2023-12-02 09:47:52 +01:00
Mikaël Capelle
d51fed283c Fix 2023 day 1. 2023-12-01 20:27:42 +01:00
Mikael CAPELLE
e991cd8b04 2023: Day 1. 2023-12-01 10:41:13 +01:00
Mikael CAPELLE
10f67e6bfd Post-christmas cleaning. 2023-01-06 13:48:18 +01:00
Mikaël Capelle
f291b0aa3f Day 25. 2022-12-25 11:34:49 +01:00
Mikaël Capelle
0eb5b5a88f Faster day 24. 2022-12-24 23:00:14 +01:00
Mikaël Capelle
2ec0a3d5f9 Day 24. 2022-12-24 20:50:37 +01:00
Mikael CAPELLE
0327a3f36a Add day 23. 2022-12-23 13:25:22 +01:00
Mikaël Capelle
3732e70ef7 Ugly day 22. 2022-12-22 23:00:36 +01:00
Mikaël Capelle
b0cc6b4a46 Update day 22. 2022-12-22 18:46:59 +01:00
Mikael CAPELLE
8c24b9f9e2 Update day 22. 2022-12-22 17:22:56 +01:00
Mikael CAPELLE
dca6f6a08f Update day 22. 2022-12-22 17:00:09 +01:00
Mikael CAPELLE
8d7a20f575 Day 22 part 1. 2022-12-22 14:10:30 +01:00
Mikael CAPELLE
3934dfd152 Start day 22. 2022-12-22 08:20:09 +01:00
Mikael CAPELLE
b656e8929e Remove tqdm from day 19. 2022-12-21 17:39:43 +01:00
Mikael CAPELLE
c9c69f479b Day 21. 2022-12-21 09:44:05 +01:00
Mikaël Capelle
72ebcfff1f Clean day 20. 2022-12-20 23:39:26 +01:00
Mikaël Capelle
dd72bb3238 Add empty files for following days. 2022-12-20 21:51:38 +01:00
Mikael CAPELLE
c1dd74c57d Faster day 20. 2022-12-20 18:27:09 +01:00
Mikael CAPELLE
1bf2de62c7 Day 20, slow. 2022-12-20 13:39:36 +01:00
Mikael CAPELLE
df808bc98a Start day 20. 2022-12-20 12:35:01 +01:00
Mikaël Capelle
f46e190e98 Add all tests from previous days. 2022-12-19 22:32:15 +01:00
Mikaël Capelle
7f4a34b2d7 Day 19. 2022-12-19 22:09:20 +01:00
Mikael CAPELLE
ddebd26db2 Tmp 2022-12-19 18:46:16 +01:00
Mikael CAPELLE
01300e23b2 Start working on day 19. 2022-12-19 13:51:32 +01:00
Mikaël Capelle
b8e2faa8c9 Clean day 17. 2022-12-18 16:46:00 +01:00
Mikaël Capelle
ea5b757180 Day 18. 2022-12-18 09:57:35 +01:00
Mikaël Capelle
89a71c175f Day 17. 2022-12-17 12:27:05 +01:00
Mikaël Capelle
9ffb332dea Day 17. 2022-12-17 12:25:48 +01:00
Mikaël Capelle
8167ab34c7 Less BFS for day 16. 2022-12-16 22:56:34 +01:00
Mikaël Capelle
100df02a09 Better day 16. 2022-12-16 22:52:03 +01:00
Mikael CAPELLE
15b987a590 Update ugly day 16. 2022-12-16 18:40:21 +01:00
Mikael CAPELLE
b1578f5709 Add ugly day 16, to be improved... 2022-12-16 09:21:54 +01:00
Mikael CAPELLE
d80dbb6c7c Update day 15. 2022-12-15 14:17:04 +01:00
Mikael CAPELLE
b679c1f895 Add day 15. 2022-12-15 09:36:19 +01:00
Mikael CAPELLE
e9d5f9747b Add day 14. 2022-12-14 09:29:56 +01:00
Mikael CAPELLE
fe3aad7ddd Clean day 9. 2022-12-13 11:18:31 +01:00
Mikael CAPELLE
7ac9981ae5 Clean day 13. 2022-12-13 08:59:53 +01:00
Mikael CAPELLE
652756a341 Add day 13. 2022-12-13 08:54:15 +01:00
Mikael CAPELLE
c7ef505f1b Clean day 12. 2022-12-12 17:55:24 +01:00
Mikael CAPELLE
c55f6ac8e1 One to many Dijkstra. 2022-12-12 17:50:48 +01:00
Mikael CAPELLE
726a6aecac Generic Dijkstra for day 12. 2022-12-12 15:59:19 +01:00
Mikael CAPELLE
291b188238 Clean day 12. 2022-12-12 10:48:37 +01:00
Mikael CAPELLE
289e3b7d02 Add day 12. 2022-12-12 09:35:12 +01:00
Mikaël Capelle
9820765e9c Clean monkey code. 2022-12-11 11:50:23 +01:00
Mikaël Capelle
c6522de8a2 Add day 11. 2022-12-11 11:42:47 +01:00
Mikaël Capelle
80465e5e53 Add day 10. 2022-12-10 10:24:23 +01:00
Mikaël Capelle
af1428b5e1 Add day 9. 2022-12-09 10:45:00 +01:00
Mikael CAPELLE
fca283527d Add 2022 day 8. 2022-12-08 08:59:25 +01:00
Mikael CAPELLE
0d37458ec5 Add 2021 day 5. 2022-12-07 18:41:39 +01:00
Mikael CAPELLE
198927e4a3 Add day 7. 2022-12-07 09:28:06 +01:00
Mikael CAPELLE
4192c98bba Cleaning. 2022-12-06 15:28:46 +01:00
Mikael CAPELLE
7cb8317659 Add day 6. 2022-12-06 09:03:22 +01:00
Mikaël Capelle
f46cb51c60 Add day 4. 2022-12-05 19:09:55 +01:00
Mikael CAPELLE
261a396ae7 Add day 5. 2022-12-05 08:58:25 +01:00
Mikaël Capelle
4b3af377ab Day 3. 2022-12-03 11:42:09 +01:00
Mikael CAPELLE
f697415ef2 More comments. 2022-12-02 15:06:37 +01:00
Mikael CAPELLE
ac2806b0fb Comments. 2022-12-02 09:21:38 +01:00
Mikael CAPELLE
c62b8abfd0 Day 2. 2022-12-02 09:12:49 +01:00
502 changed files with 2739 additions and 37823 deletions

View File

@ -1,12 +0,0 @@
---
kind: pipeline
type: docker
name: default
steps:
- name: tests
image: python:3.10-slim
commands:
- pip install poetry
- poetry install
- poetry run poe lint

5
.gitignore vendored
View File

@ -1,6 +1 @@
# python / VS Code
venv venv
__pycache__
.ruff_cache
.vscode
build

14
2021/day1.py Normal file
View 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
2021/day10.py Normal file
View 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
2021/day11.py Normal file
View 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
2021/day12.py Normal file
View 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
2021/day13.py Normal file
View 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
2021/day14.py Normal file
View 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
2021/day15.py Normal file
View 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
2021/day16.py Normal file
View 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
2021/day17.py Normal file
View 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
2021/day18.py Normal file
View 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
2021/day19.py Normal file
View 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
2021/day2.py Normal file
View 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
2021/day20.py Normal file
View 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
2021/day21.py Normal file
View 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
2021/day22.py Normal file
View 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
2021/day23.py Normal file
View 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
2021/day24.py Normal file
View 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
2021/day25.py Normal file
View 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
2021/day3.py Normal file
View 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
2021/day4.py Normal file
View 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
2021/day5.py Normal file
View 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
2021/day6.py Normal file
View 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
2021/day7.py Normal file
View 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
2021/day8.py Normal file
View 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
2021/day9.py Normal file
View 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}")

7
2022/day1.py Normal file
View 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
2022/day10.py Normal file
View 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()

View File

@ -1,8 +1,7 @@
import copy import copy
import sys
from functools import reduce from functools import reduce
from typing import Any, Callable, Final, Iterator, Mapping, Sequence from typing import Callable, Final, Mapping, Sequence
from ..base import BaseSolver
class Monkey: class Monkey:
@ -120,14 +119,13 @@ def monkey_business(inspects: dict[Monkey, int]) -> int:
return sorted_levels[-2] * sorted_levels[-1] return sorted_levels[-2] * sorted_levels[-1]
class Solver(BaseSolver): monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")]
def solve(self, input: str) -> Iterator[Any]:
monkeys = [parse_monkey(block.splitlines()) for block in input.split("\n\n")]
# case 1: we simply divide the worry by 3 after applying the monkey worry operation # case 1: we simply divide the worry by 3 after applying the monkey worry operation
yield monkey_business( answer_1 = monkey_business(
run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3) 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 # 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 # use the product of all "divisible by" test so that the test remains valid
@ -138,10 +136,7 @@ class Solver(BaseSolver):
# we use the product of all test value # we use the product of all test value
# #
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1) total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
yield monkey_business( answer_2 = monkey_business(
run( run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value)
copy.deepcopy(monkeys),
10_000,
me_worry_fn=lambda w: w % total_test_value,
)
) )
print(f"answer 2 is {answer_2}")

160
2022/day12.py Normal file
View 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}")

View File

@ -1,8 +1,11 @@
import json import json
import sys
from functools import cmp_to_key from functools import cmp_to_key
from typing import Any, Iterator, TypeAlias, cast from typing import TypeAlias, cast
from ..base import BaseSolver 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"]] Packet: TypeAlias = list[int | list["Packet"]]
@ -25,12 +28,8 @@ def compare(lhs: Packet, rhs: Packet) -> int:
return len(rhs) - len(lhs) return len(rhs) - len(lhs)
class Solver(BaseSolver): answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
def solve(self, input: str) -> Iterator[Any]: print(f"answer_1 is {answer_1}")
blocks = input.split("\n\n")
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
yield sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
dividers = [[[2]], [[6]]] dividers = [[[2]], [[6]]]
@ -39,4 +38,4 @@ class Solver(BaseSolver):
packets = list(reversed(sorted(packets, key=cmp_to_key(compare)))) packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
d_index = [packets.index(d) + 1 for d in dividers] d_index = [packets.index(d) + 1 for d in dividers]
yield d_index[0] * d_index[1] print(f"answer 2 is {d_index[0] * d_index[1]}")

140
2022/day14.py Normal file
View 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
2022/day15.py Normal file
View 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
2022/day16.py Normal file
View 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))

View File

@ -1,16 +1,23 @@
from typing import Any, Iterator, Sequence, TypeAlias, TypeVar import sys
from typing import Sequence, TypeVar
import numpy as np import numpy as np
from numpy.typing import NDArray
from ..base import BaseSolver
T = TypeVar("T") T = TypeVar("T")
Tower: TypeAlias = NDArray[np.bool]
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: Tower) -> int: def tower_height(tower: np.ndarray) -> int:
return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1) return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1)
@ -38,8 +45,8 @@ def build_tower(
n_rocks: int, n_rocks: int,
jets: str, jets: str,
early_stop: bool = False, early_stop: bool = False,
init: Tower = np.ones(WIDTH, dtype=bool), init: np.ndarray = np.ones(WIDTH, dtype=bool),
) -> tuple[Tower, int, int, dict[int, int]]: ) -> tuple[np.ndarray, int, int, dict[int, int]]:
tower = EMPTY_BLOCKS.copy() tower = EMPTY_BLOCKS.copy()
tower[0, :] = init tower[0, :] = init
@ -88,13 +95,14 @@ def build_tower(
return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights
class Solver(BaseSolver): line = sys.stdin.read().strip()
def solve(self, input: str) -> Iterator[Any]:
tower, *_ = build_tower(2022, input) tower, *_ = build_tower(2022, line)
yield tower_height(tower) answer_1 = tower_height(tower)
print(f"answer 1 is {answer_1}")
TOTAL_ROCKS = 1_000_000_000_000 TOTAL_ROCKS = 1_000_000_000_000
_tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, input, True) tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, line, True)
assert prev_1 > 0 assert prev_1 > 0
# 2767 1513 # 2767 1513
@ -108,4 +116,5 @@ class Solver(BaseSolver):
heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1] heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1]
) )
yield base_height + (n_repeat_towers + 1) * repeat_height + remaining_height answer_2 = base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
print(f"answer 2 is {answer_2}")

51
2022/day18.py Normal file
View 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}")

View File

@ -1,10 +1,9 @@
from typing import Any, Iterator, Literal import sys
from typing import Literal
import numpy as np import numpy as np
import parse # pyright: ignore[reportMissingTypeStubs] import parse
from numpy.typing import NDArray from tqdm import tqdm
from ..base import BaseSolver
Reagent = Literal["ore", "clay", "obsidian", "geode"] Reagent = Literal["ore", "clay", "obsidian", "geode"]
REAGENTS: tuple[Reagent, ...] = ( REAGENTS: tuple[Reagent, ...] = (
@ -36,7 +35,7 @@ class State:
self.robots = robots self.robots = robots
self.reagents = reagents self.reagents = reagents
def __eq__(self, other: object) -> bool: def __eq__(self, other) -> bool:
return ( return (
isinstance(other, State) isinstance(other, State)
and self.robots == other.robots and self.robots == other.robots
@ -63,6 +62,29 @@ def dominates(lhs: State, rhs: State):
) )
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: 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 # 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., # of type K where X is the maximum number of K required among all robots, e.g.,
@ -72,12 +94,11 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
name: max(blueprint[r].get(name, 0) for r in REAGENTS) for name in REAGENTS name: max(blueprint[r].get(name, 0) for r in REAGENTS) for name in REAGENTS
} }
state_after_t: dict[int, set[State]] = {0: {State()}} state_after_t: dict[int, set[State]] = {0: [State()]}
for t in range(1, max_time + 1): 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 # list of new states at the end of step t that we are going to prune later
states_for_t: set[State] = set() states_for_t: set[State] = set()
robots_that_can_be_built: list[Reagent]
for state in state_after_t[t - 1]: for state in state_after_t[t - 1]:
robots_that_can_be_built = [ robots_that_can_be_built = [
@ -111,7 +132,7 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
for robot in robots_that_can_be_built: for robot in robots_that_can_be_built:
robots = state.robots.copy() robots = state.robots.copy()
robots[robot] += 1 robots[robot] += 1
reagents: IntOfReagent = { reagents = {
reagent: state.reagents[reagent] reagent: state.reagents[reagent]
+ state.robots[reagent] + state.robots[reagent]
- blueprint[robot].get(reagent, 0) - blueprint[robot].get(reagent, 0)
@ -130,7 +151,7 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
] ]
) )
to_keep: list[NDArray[np.integer[Any]]] = [] to_keep = []
while len(np_states) > 0: while len(np_states) > 0:
first_dom = (np_states[1:] >= np_states[0]).all(axis=1).any() first_dom = (np_states[1:] >= np_states[0]).all(axis=1).any()
@ -151,31 +172,11 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
return max(state.reagents["geode"] for state in state_after_t[max_time]) return max(state.reagents["geode"] for state in state_after_t[max_time])
class Solver(BaseSolver): answer_1 = sum(
def solve(self, input: str) -> Iterator[Any]:
blueprints: list[dict[Reagent, IntOfReagent]] = []
for line in input.splitlines():
r: list[int] = parse.parse( # type: ignore
"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]},
}
)
yield sum(
(i_blueprint + 1) * run(blueprint, 24) (i_blueprint + 1) * run(blueprint, 24)
for i_blueprint, blueprint in enumerate(blueprints) for i_blueprint, blueprint in enumerate(blueprints)
) )
print(f"answer 1 is {answer_1}")
yield (run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32)) answer_2 = run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32)
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,4 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver
def score_1(ux: int, vx: int) -> int: def score_1(ux: int, vx: int) -> int:
@ -35,9 +33,7 @@ def score_2(ux: int, vx: int) -> int:
return (ux + vx - 1) % 3 + 1 + vx * 3 return (ux + vx - 1) % 3 + 1 + vx * 3
class Solver(BaseSolver): lines = sys.stdin.readlines()
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using # the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
# modulo-3 arithmetic # modulo-3 arithmetic
@ -51,7 +47,7 @@ class Solver(BaseSolver):
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines] values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
# part 1 - 13526 # part 1 - 13526
yield sum(score_1(*v) for v in values) print(f"answer 1 is {sum(score_1(*v) for v in values)}")
# part 2 - 14204 # part 2 - 14204
yield sum(score_2(*v) for v in values) print(f"answer 2 is {sum(score_2(*v) for v in values)}")

View File

@ -1,8 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import Any, Iterator import sys
from ..base import BaseSolver
class Number: class Number:
@ -67,9 +65,10 @@ def decrypt(numbers: list[Number], key: int, rounds: int) -> int:
) )
class Solver(BaseSolver): numbers = [Number(int(x)) for i, x in enumerate(sys.stdin.readlines())]
def solve(self, input: str) -> Iterator[Any]:
numbers = [Number(int(x)) for x in input.splitlines()]
yield decrypt(numbers, 1, 1) answer_1 = decrypt(numbers, 1, 1)
yield decrypt(numbers, 811589153, 10) print(f"answer 1 is {answer_1}")
answer_2 = decrypt(numbers, 811589153, 10)
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,6 @@
import operator import operator
from typing import Any, Callable, Iterator import sys
from typing import Callable
from ..base import BaseSolver
def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int: def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int:
@ -78,9 +77,7 @@ def invert(
return monkeys return monkeys
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
monkeys: dict[str, int | tuple[str, str, str]] = {} monkeys: dict[str, int | tuple[str, str, str]] = {}
@ -99,10 +96,12 @@ class Solver(BaseSolver):
op_monkeys.add(name) op_monkeys.add(name)
yield compute(monkeys.copy(), "root")
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 # 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 # humn, which is the case is my input and the test input
assert isinstance(monkeys["root"], tuple)
p1, _, p2 = monkeys["root"] # type: ignore p1, _, p2 = monkeys["root"] # type: ignore
yield compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn") answer_2 = compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
print(f"answer 2 is {answer_2}")

223
2022/day22.py Normal file
View 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}")

View File

@ -1,8 +1,6 @@
import itertools import itertools
import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
Directions = list[ Directions = list[
tuple[ tuple[
@ -20,10 +18,22 @@ DIRECTIONS: Directions = [
def min_max_yx(positions: set[tuple[int, int]]) -> tuple[int, int, int, int]: 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} ys, xs = {y for y, x in positions}, {x for y, x in positions}
return min(ys), min(xs), max(ys), max(xs) 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( def round(
positions: set[tuple[int, int]], positions: set[tuple[int, int]],
directions: Directions, directions: Directions,
@ -59,11 +69,9 @@ def round(
directions.append(directions.pop(0)) directions.append(directions.pop(0))
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
POSITIONS = { POSITIONS = {
(i, j) (i, j)
for i, row in enumerate(input.splitlines()) for i, row in enumerate(sys.stdin.read().splitlines())
for j, col in enumerate(row) for j, col in enumerate(row)
if col == "#" if col == "#"
} }
@ -71,15 +79,14 @@ class Solver(BaseSolver):
# === part 1 === # === part 1 ===
p1, d1 = POSITIONS.copy(), DIRECTIONS.copy() p1, d1 = POSITIONS.copy(), DIRECTIONS.copy()
for _ in range(10): for r in range(10):
round(p1, d1) round(p1, d1)
min_y, min_x, max_y, max_x = min_max_yx(p1) min_y, min_x, max_y, max_x = min_max_yx(p1)
yield sum( answer_1 = sum(
(y, x) not in p1 (y, x) not in p1 for y in range(min_y, max_y + 1) for x in range(min_x, max_x + 1)
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 === # === part 2 ===
@ -93,4 +100,4 @@ class Solver(BaseSolver):
if backup == p2: if backup == p2:
break break
yield answer_2 print(f"answer 2 is {answer_2}")

98
2022/day24.py Normal file
View 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
2022/day25.py Normal file
View 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
2022/day3.py Normal file
View 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
2022/day4.py Normal file
View 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
2022/day5.py Normal file
View 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
2022/day6.py Normal file
View 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
2022/day7.py Normal file
View 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}")

Some files were not shown because too many files have changed in this diff Show More