Skip to content

Commit f8cc6d2

Browse files
committed
* Add 2025
1 parent cc238a7 commit f8cc6d2

5 files changed

Lines changed: 395 additions & 0 deletions

File tree

src/2025/08/2025_08.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import math
2+
from itertools import combinations
3+
from operator import itemgetter
4+
from typing import NamedTuple
5+
6+
from scipy._lib._disjoint_set import DisjointSet
7+
8+
from src.utils.data import load_data
9+
from src.utils.submission import submit_or_print
10+
11+
12+
class Box(NamedTuple):
13+
x: int
14+
y: int
15+
z: int
16+
17+
def distance(self, other):
18+
return math.dist([self.x, self.y, self.z], [other.x, other.y, other.z])
19+
20+
21+
def main(debug: bool) -> None:
22+
input_data = load_data(debug)
23+
24+
boxes = [Box(*map(int, line.split(","))) for line in input_data.splitlines()]
25+
print(len(boxes), "boxes")
26+
27+
pair_to_distance = {}
28+
for box1, box2 in combinations(boxes, 2):
29+
pair_to_distance[(box1, box2)] = box1.distance(box2)
30+
circuits = DisjointSet(elements=boxes)
31+
for (box1, box2), distance in sorted(pair_to_distance.items(), key=itemgetter(1))[
32+
:1000
33+
]:
34+
circuits.merge(box1, box2)
35+
result_part1 = math.prod(
36+
sorted([len(s) for s in circuits.subsets()], reverse=True)[:3]
37+
)
38+
39+
circuits = DisjointSet(elements=boxes)
40+
for (box1, box2), distance in sorted(pair_to_distance.items(), key=itemgetter(1)):
41+
circuits.merge(box1, box2)
42+
if circuits.n_subsets == 1:
43+
result_part2 = box1.x * box2.x
44+
break
45+
46+
submit_or_print(result_part1, result_part2, debug)
47+
48+
49+
if __name__ == "__main__":
50+
debug_mode = True
51+
# debug_mode = False
52+
main(debug_mode)

src/2025/09/2025_09.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from collections import namedtuple
2+
from itertools import combinations, product
3+
from matplotlib.path import Path
4+
from src.utils.data import load_data
5+
from src.utils.submission import submit_or_print
6+
7+
Point = namedtuple("Point", ["x", "y"])
8+
Segment = namedtuple("Segment", ["start", "end"])
9+
10+
11+
def area(point1: Point, point2: Point) -> int:
12+
return (abs(point1.x - point2.x) + 1) * (abs(point1.y - point2.y) + 1)
13+
14+
15+
def no_points_inside(point1, point2, points):
16+
x1, x2 = point1.x, point2.x
17+
if x1 > x2:
18+
x1, x2 = x2, x1
19+
y1, y2 = point1.y, point2.y
20+
if y1 > y2:
21+
y1, y2 = y2, y1
22+
23+
for point in points:
24+
if x1 < point.x < x2 and y1 < point.y < y2:
25+
return False
26+
return True
27+
28+
29+
def polygon(points):
30+
return [
31+
Segment(point_from, point_to)
32+
for point_from, point_to in zip(points, points[1:] + points[0:1])
33+
]
34+
35+
36+
def ccw(A, B, C):
37+
return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x)
38+
39+
40+
def intersect(A, B, C, D):
41+
return ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D)
42+
43+
44+
def intersects(lines1, lines2):
45+
print("checking...")
46+
print(lines1)
47+
print(lines2)
48+
49+
for line1, line2 in product(lines1, lines2):
50+
if intersect(line1.start, line1.end, line2.start, line2.end):
51+
print("intersect!")
52+
print(line1, line2)
53+
return True
54+
print("no intersections")
55+
return False
56+
57+
58+
def rectangle(point1, point2):
59+
xs = sorted([point1.x, point2.x])
60+
ys = sorted([point1.y, point2.y])
61+
points = [
62+
Point(xs[0], ys[0]),
63+
Point(xs[0], ys[1]),
64+
Point(xs[1], ys[1]),
65+
Point(xs[1], ys[0]),
66+
]
67+
return polygon(points)
68+
69+
70+
def main(debug: bool) -> None:
71+
input_data = load_data(debug)
72+
73+
points = [Point(*map(int, line.split(","))) for line in input_data.splitlines()]
74+
print(len(points), "points")
75+
76+
result_part1 = max(
77+
area(point1, point2) for point1, point2 in combinations(points, 2)
78+
)
79+
80+
polygon = Path(points)
81+
print(polygon)
82+
result_part2 = max(
83+
area(point1, point2)
84+
for point1, point2 in combinations(points, 2)
85+
if not polygon.intersects_path(Path(rectangle(point1, point2)))
86+
)
87+
88+
print(polygon.contains_point((5, 5)))
89+
90+
result_part2 = None
91+
xs = sorted({p.x for p in points})
92+
ys = sorted({p.y for p in points})
93+
94+
print("Xs:", min(xs), max(xs), "all", xs)
95+
print("Ys:", min(ys), max(ys), "all", ys)
96+
97+
x_map = {x: i for i, x in enumerate(xs)}
98+
y_map = {y: i for i, y in enumerate(ys)}
99+
100+
mapped_points = [Point(x_map[p.x], y_map[p.y]) for p in points]
101+
print(points)
102+
print(mapped_points)
103+
104+
submit_or_print(result_part1, result_part2, debug)
105+
106+
107+
if __name__ == "__main__":
108+
debug_mode = True
109+
# debug_mode = False
110+
main(debug_mode)

src/2025/10/2025_10.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import re
2+
from collections import deque
3+
from functools import reduce
4+
from itertools import repeat
5+
from operator import add
6+
from typing import NamedTuple
7+
from z3 import Solver, Int, IntVal
8+
from tqdm import tqdm
9+
10+
from src.utils.data import load_data
11+
from src.utils.submission import submit_or_print
12+
13+
14+
class State(NamedTuple):
15+
status: tuple[int, ...]
16+
buttons: set[set[int]]
17+
18+
19+
class Node(NamedTuple):
20+
state: State
21+
depth: int
22+
23+
24+
def main(debug: bool) -> None:
25+
input_data = load_data(debug)
26+
27+
result_part1 = 0
28+
for line in tqdm(input_data.splitlines()):
29+
m = re.match(r"\[(?P<target>[.#]+)\] (?P<buttons>(\([0-9,]+\) )+)", line)
30+
target = tuple(c == "#" for c in m.group("target"))
31+
buttons = frozenset(
32+
[
33+
frozenset(map(int, s[1:-1].split(",")))
34+
for s in m.group("buttons").strip().split(" ")
35+
]
36+
)
37+
result_part1 += solve(target, buttons)
38+
39+
result_part2 = 0
40+
for line in tqdm(input_data.splitlines()):
41+
m = re.match(
42+
r"\[(?P<target>[.#]+)\] (?P<buttons>(\([0-9,]+\) )+){(?P<joltage>[0-9,]+)}",
43+
line,
44+
)
45+
target = tuple(map(int, m.group("joltage").split(",")))
46+
buttons = list(
47+
[
48+
frozenset(map(int, s[1:-1].split(",")))
49+
for s in m.group("buttons").strip().split(" ")
50+
]
51+
)
52+
result_part2 += solve_part2(target, buttons)
53+
54+
submit_or_print(result_part1, result_part2, debug)
55+
56+
57+
def solve(target: tuple[bool, ...], buttons: set[set[int]]):
58+
queue: deque[Node] = deque(
59+
[
60+
Node(
61+
state=State(status=tuple(repeat(0, len(target))), buttons=buttons),
62+
depth=0,
63+
)
64+
]
65+
)
66+
67+
visited_states = set()
68+
while queue:
69+
node = queue.popleft()
70+
state = node.state
71+
if state in visited_states:
72+
continue
73+
visited_states.add(state)
74+
if state.status == target:
75+
return node.depth
76+
77+
for button in state.buttons:
78+
new_state = State(
79+
status=tuple(
80+
int(not s) if i in button else s for i, s in enumerate(state.status)
81+
),
82+
buttons=frozenset(b for b in state.buttons if b != button),
83+
)
84+
queue.append(
85+
Node(
86+
new_state,
87+
depth=node.depth + 1,
88+
)
89+
)
90+
return -1
91+
92+
93+
def solve_part2(target, buttons):
94+
presses = max(target)
95+
96+
while True:
97+
s = Solver()
98+
button_vars = [Int(i) for i in range(len(buttons))]
99+
for light_nr, target_joltage in enumerate(target):
100+
target_joltage_constant = IntVal(target_joltage)
101+
parts = [
102+
button_vars[button_index]
103+
for button_index, button in enumerate(buttons)
104+
if light_nr in button
105+
]
106+
s.add(target_joltage_constant == reduce(add, parts))
107+
for i in range(len(buttons)):
108+
s.add(button_vars[i] >= 0)
109+
total_button_pushes = IntVal(presses)
110+
s.add(total_button_pushes == reduce(add, button_vars))
111+
if str(s.check()) == "sat":
112+
return presses
113+
else:
114+
presses += 1
115+
116+
117+
if __name__ == "__main__":
118+
debug_mode = True
119+
debug_mode = False
120+
main(debug_mode)

src/2025/11/2025_11.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from collections import deque
2+
3+
import networkx as nx
4+
from tqdm import tqdm
5+
6+
from src.utils.data import load_data
7+
from src.utils.submission import submit_or_print
8+
9+
10+
def main(debug: bool) -> None:
11+
input_data = load_data(debug)
12+
13+
source_to_targets = {}
14+
for line in input_data.splitlines():
15+
s = line.split(":")
16+
source = s[0].strip()
17+
targets = set(s[1].strip().split(" "))
18+
source_to_targets[source] = targets
19+
20+
graph = nx.DiGraph()
21+
for source, targets in source_to_targets.items():
22+
for target in targets:
23+
graph.add_edge(source, target)
24+
25+
# result_part1 = paths_count(graph, "you", "out")
26+
27+
source = "svr"
28+
target = "out"
29+
30+
graphs = deque([graph])
31+
while graphs:
32+
g = graphs.pop()
33+
cut_nodes = nx.minimum_node_cut(g, source, target)
34+
print(g, len(cut_nodes), cut_nodes)
35+
if len(cut_nodes) < 10:
36+
g.remove_nodes_from(cut_nodes)
37+
graphs.extend(
38+
[
39+
g.subgraph(c).copy()
40+
for c in nx.connected_components(g.to_undirected())
41+
]
42+
)
43+
44+
#
45+
# inter_fft = paths_count(graph, "fft", "dac")
46+
# inter_dac = paths_count(graph, "dac", "fft")
47+
# print(inter_fft, inter_dac)
48+
#
49+
# fft = paths_count(graph, "svr", "fft")
50+
# dac = paths_count(graph, "svr", "dac")
51+
# print(fft, dac)
52+
#
53+
# fft_out = paths_count(graph, "fft", "out")
54+
# dac_out = paths_count(graph, "dac", "out")
55+
# print(fft_out, dac_out)
56+
57+
result_part1 = None
58+
result_part2 = None
59+
60+
# nx.draw_networkx(graph)
61+
# plt.show()
62+
63+
submit_or_print(result_part1, result_part2, debug)
64+
65+
66+
def paths_count(graph, start, end) -> int:
67+
return sum(tqdm(1 for _ in nx.all_simple_paths(graph, start, end)))
68+
69+
70+
if __name__ == "__main__":
71+
debug_mode = True
72+
debug_mode = False
73+
main(debug_mode)

src/2025/12/2025_12.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import re
2+
from itertools import chain
3+
4+
5+
from src.utils.data import load_data
6+
from src.utils.submission import submit_or_print
7+
8+
9+
def main(debug: bool) -> None:
10+
input_data = load_data(debug)
11+
12+
shape_nr_to_size = {}
13+
result_part1 = 0
14+
for block in input_data.split("\n\n"):
15+
if m := re.match(r"([0-9]+):", block):
16+
shape_nr = int(m.group(1))
17+
shape = [list(l) for l in block.splitlines()[1:]]
18+
size = sum(1 for c in chain.from_iterable(shape) if c == "#")
19+
shape_nr_to_size[shape_nr] = size
20+
else:
21+
for line in block.splitlines():
22+
m = re.match(r"([0-9]+)x([0-9]+):([0-9 ]+)", line)
23+
area = int(m.group(1)) * int(m.group(2))
24+
counts = tuple(map(int, m.group(3).strip().split(" ")))
25+
required_area = sum(
26+
shape_nr_to_size[i] * c for i, c in enumerate(counts)
27+
)
28+
print(area, required_area, counts)
29+
result_part1 += 1 if required_area <= area else 0
30+
print(shape_nr_to_size)
31+
32+
result_part2 = None
33+
34+
submit_or_print(result_part1, result_part2, debug)
35+
36+
37+
if __name__ == "__main__":
38+
debug_mode = True
39+
debug_mode = False
40+
main(debug_mode)

0 commit comments

Comments
 (0)