Skip to content

Commit 4104a8f

Browse files
committed
Add solution to 2025-12-10
1 parent c7cb303 commit 4104a8f

File tree

1 file changed

+57
-0
lines changed

1 file changed

+57
-0
lines changed

2025/day10/solutions.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
The solution below is really one in which the solution for part 2 has been
3+
retroactively applied to part 1 as well, simply because it was a fun thing to do.
4+
5+
Really, part 1 is likely always best solved by a combinatorial/path based solution like
6+
(bidirectional) BFS or an SAT/SMT solver like Z3, whereas part 2 is very naturally
7+
expressed as an integer linear program.
8+
9+
The solution below takes that part 2 solution, and extends it by also formulating
10+
part 1 as an integer linear program. Here's how that works: In both parts, we want to
11+
solve a linear equation 𝐴𝑥 = 𝑏 such that 𝑥 is minimal in some sense. In part 1, we
12+
are solving over 𝔽₂ = {0, 1} and require that 𝑥 has minimal Hamming weight, and in
13+
part 2, we require that 𝑥 consists of positive integers and has minimal sum. Integer
14+
programming solvers like the HiGHS-based solver in SciPy that we use below like
15+
integers better but can be forced to work over the binaries by adding additional
16+
variables. Concretely, for part 1, instead of solving 𝐴𝑥 = 𝑏 over 𝔽₂, we instead
17+
allow 𝑥 to be an integer, and introduce new integer variables 𝑡 and solve
18+
𝐴𝑥 − 2𝑡 = 𝑏, still just minimizing the sum of 𝑥. This works because 2𝑡 vanishes over
19+
𝔽₂, and because any optimal integer solution will values of 𝑥 in {0, 1}.
20+
21+
While we're at it, note that part 1 is actually the well-known “syndrome decoding”
22+
problem, also known as “maximum-likelihood decoding”.
23+
"""
24+
25+
import numpy as np
26+
from scipy.optimize import linprog
27+
28+
29+
with open("input") as f:
30+
ls = f.read().strip().split("\n")
31+
32+
tasks = []
33+
for l in ls:
34+
toggles, *buttons, counters = l.split()
35+
toggles = [x == "#" for x in toggles[1:-1]]
36+
moves = [set(map(int, b[1:-1].split(","))) for b in buttons]
37+
counters = list(map(int, counters[1:-1].split(",")))
38+
tasks.append((toggles, moves, counters))
39+
40+
41+
def solve(goal, moves, part1):
42+
n, m = len(moves), len(goal)
43+
c = [1] * n
44+
A_eq = [[i in move for move in moves] for i in range(m)]
45+
bounds = [(0, None)] * n
46+
if part1:
47+
c += [0] * m
48+
A_eq = np.hstack([A_eq, -2 * np.eye(m)])
49+
bounds += [(None, None)] * m
50+
return linprog(c, A_eq=A_eq, b_eq=goal, bounds=bounds, integrality=True).fun
51+
52+
53+
# Part 1
54+
print(sum(solve(goal, moves, True) for goal, moves, _ in tasks))
55+
56+
# Part 2
57+
print(sum(solve(goal, moves, False) for _, moves, goal in tasks))

0 commit comments

Comments
 (0)