Skip to content

Commit 0f3c629

Browse files
committed
Add solution to 2025-12-10
1 parent c7cb303 commit 0f3c629

File tree

1 file changed

+59
-0
lines changed

1 file changed

+59
-0
lines changed

2025/day10/solutions.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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}. In other
20+
words, to solve part 1, we take our part 2 solution and simply add a handful of useful
21+
auxiliary variables to encode the fact that the matrix equation is now modulo 2.
22+
23+
While we're at it, note that part 1 is actually the well-known “syndrome decoding”
24+
problem, also known as “maximum-likelihood decoding”.
25+
"""
26+
27+
import numpy as np
28+
from scipy.optimize import linprog
29+
30+
31+
with open("input") as f:
32+
ls = f.read().strip().split("\n")
33+
34+
tasks = []
35+
for l in ls:
36+
toggles, *buttons, counters = l.split()
37+
toggles = [x == "#" for x in toggles[1:-1]]
38+
moves = [set(map(int, b[1:-1].split(","))) for b in buttons]
39+
counters = list(map(int, counters[1:-1].split(",")))
40+
tasks.append((toggles, moves, counters))
41+
42+
43+
def solve(goal, moves, part1):
44+
n, m = len(moves), len(goal)
45+
c = [1] * n
46+
A_eq = [[i in move for move in moves] for i in range(m)]
47+
bounds = [(0, None)] * n
48+
if part1:
49+
c += [0] * m
50+
A_eq = np.hstack([A_eq, -2 * np.eye(m)])
51+
bounds += [(None, None)] * m
52+
return linprog(c, A_eq=A_eq, b_eq=goal, bounds=bounds, integrality=True).fun
53+
54+
55+
# Part 1
56+
print(sum(solve(goal, moves, True) for goal, moves, _ in tasks))
57+
58+
# Part 2
59+
print(sum(solve(goal, moves, False) for _, moves, goal in tasks))

0 commit comments

Comments
 (0)