diff options
| author | Sebastiano Tronto <sebastiano@tronto.net> | 2025-12-10 18:15:14 +0100 |
|---|---|---|
| committer | Sebastiano Tronto <sebastiano@tronto.net> | 2025-12-10 18:15:14 +0100 |
| commit | b52eef5b9399ab3888fcfe1bc9ca23c7a9f7b026 (patch) | |
| tree | df3c5525c55e5d21ce07b9bab2359468cd9827a3 /2025 | |
| parent | 4dc2097b38697614dba7a58bf2ec53e1e54e52fe (diff) | |
| download | aoc-b52eef5b9399ab3888fcfe1bc9ca23c7a9f7b026.tar.gz aoc-b52eef5b9399ab3888fcfe1bc9ca23c7a9f7b026.zip | |
Day 10 2025
Diffstat (limited to '2025')
| -rw-r--r-- | 2025/10/a.py | 35 | ||||
| -rw-r--r-- | 2025/10/b-debug.py | 133 | ||||
| -rw-r--r-- | 2025/10/b-recursive-slow.py | 66 | ||||
| -rw-r--r-- | 2025/10/b.py | 125 | ||||
| -rw-r--r-- | 2025/README.md | 6 |
5 files changed, 363 insertions, 2 deletions
diff --git a/2025/10/a.py b/2025/10/a.py index 5fdb11b..52fa0b0 100644 --- a/2025/10/a.py +++ b/2025/10/a.py | |||
| @@ -1,6 +1,37 @@ | |||
| 1 | import fileinput | 1 | import fileinput |
| 2 | 2 | ||
| 3 | def readline(line): | ||
| 4 | m = 0 | ||
| 5 | for i in range(1, line.index(']')): | ||
| 6 | if line[i] == '#': | ||
| 7 | m |= 1 << (i-1) | ||
| 8 | |||
| 9 | b = [] | ||
| 10 | j = line.index(']') | ||
| 11 | while '(' in line[j:]: | ||
| 12 | i = line[j:].index('(') + j + 1 | ||
| 13 | j = line[i:].index(')') + i | ||
| 14 | x = 0 | ||
| 15 | for y in line[i:j].split(','): | ||
| 16 | x |= 1 << int(y) | ||
| 17 | b.append(x) | ||
| 18 | |||
| 19 | return m, b | ||
| 20 | |||
| 21 | def works(m, b, s): | ||
| 22 | x = 0 | ||
| 23 | for j in range(len(s)): | ||
| 24 | if s[j] == '1': | ||
| 25 | x ^= b[j] | ||
| 26 | return x == m | ||
| 27 | |||
| 28 | def sol(m, b): | ||
| 29 | s = 99999999 | ||
| 30 | for i in range(2**len(b)): | ||
| 31 | if works(m, b, bin(i)[2:].rjust(len(b), '0')): | ||
| 32 | s = min(s, i.bit_count()) | ||
| 33 | return s | ||
| 34 | |||
| 3 | with fileinput.input() as lines: | 35 | with fileinput.input() as lines: |
| 4 | for line in lines: | 36 | print(sum(sol(*readline(line)) for line in lines)) |
| 5 | ... | ||
| 6 | 37 | ||
diff --git a/2025/10/b-debug.py b/2025/10/b-debug.py new file mode 100644 index 0000000..e6f9961 --- /dev/null +++ b/2025/10/b-debug.py | |||
| @@ -0,0 +1,133 @@ | |||
| 1 | import fileinput | ||
| 2 | from math import gcd | ||
| 3 | |||
| 4 | inf = 999999999 | ||
| 5 | |||
| 6 | def readline(l): | ||
| 7 | b = [int(x) for x in l[l.index('{')+1:l.index('}')].split(',')] | ||
| 8 | |||
| 9 | A = [[] for j in b] | ||
| 10 | c = [] | ||
| 11 | |||
| 12 | end = l.index(']') | ||
| 13 | while '(' in l[end:]: | ||
| 14 | begin = l[end:].index('(') + end + 1 | ||
| 15 | end = l[begin:].index(')') + begin | ||
| 16 | bu = [int(x) for x in l[begin:end].split(',')] | ||
| 17 | c.append(min(b[j] for j in bu)) | ||
| 18 | for i in range(len(b)): | ||
| 19 | A[i].append(1 if i in bu else 0) | ||
| 20 | |||
| 21 | return A, b, c | ||
| 22 | |||
| 23 | def printabc(A, b, c): | ||
| 24 | print("--") | ||
| 25 | for i in range(len(b)): | ||
| 26 | print(A[i], [b[i]]) | ||
| 27 | print(f"Parameter bounds: {c}") | ||
| 28 | print("--") | ||
| 29 | |||
| 30 | def swaprow(A, b, i, j): | ||
| 31 | if i != j: | ||
| 32 | A[i], A[j] = A[j], A[i] | ||
| 33 | b[i], b[j] = b[j], b[i] | ||
| 34 | |||
| 35 | def swapcol(A, c, i, j): | ||
| 36 | if i != j: | ||
| 37 | for k in range(len(A)): | ||
| 38 | A[k][i], A[k][j] = A[k][j], A[k][i] | ||
| 39 | c[i], c[j] = c[j], c[i] | ||
| 40 | |||
| 41 | def reducerow(A, b, i, j): | ||
| 42 | if A[i][i] != 0: | ||
| 43 | x = A[i][i] | ||
| 44 | y = -A[j][i] | ||
| 45 | d = gcd(x, y) | ||
| 46 | A[j] = [(y*A[i][k]+x*A[j][k])//d for k in range(len(A[i]))] | ||
| 47 | b[j] = (y*b[i]+x*b[j])//d | ||
| 48 | |||
| 49 | def reduce(A, b, c): | ||
| 50 | for i in range(len(A[0])): | ||
| 51 | # Swap columns until there is one in position i with at least | ||
| 52 | # one non-zero element. | ||
| 53 | I = [] | ||
| 54 | k = i | ||
| 55 | while len(I) == 0 and k < len(A[0]): | ||
| 56 | swapcol(A, c, i, k) | ||
| 57 | I = [j for j in range(i, len(A)) if A[j][i] != 0] | ||
| 58 | k += 1 | ||
| 59 | |||
| 60 | # If no such column is found, we are done | ||
| 61 | if len(I) == 0: | ||
| 62 | break | ||
| 63 | |||
| 64 | # Swap rows so that A[i][i] is non-zero | ||
| 65 | swaprow(A, b, i, I[0]) | ||
| 66 | |||
| 67 | # Reduce all other rows | ||
| 68 | for j in range(i+1, len(A)): | ||
| 69 | reducerow(A, b, i, j) | ||
| 70 | |||
| 71 | # Remove all rows of zero and check if the system is solvable | ||
| 72 | I = [i for i in range(len(A)) if any(a != 0 for a in A[i])] | ||
| 73 | if any(b[i] != 0 for i in range(len(A)) if i not in I): | ||
| 74 | printabc(A, b, c) | ||
| 75 | print("Unsolvable!") | ||
| 76 | exit(1) | ||
| 77 | A = [A[i] for i in I] | ||
| 78 | b = [b[i] for i in I] | ||
| 79 | |||
| 80 | # TODO continue with back substitution? | ||
| 81 | for i in range(len(A)-1, -1, -1): | ||
| 82 | for j in range(i): | ||
| 83 | reducerow(A, b, i, j) | ||
| 84 | |||
| 85 | # Clean all rows to minimize coefficients (unnecessary, but makes | ||
| 86 | # numbers smaller). | ||
| 87 | for i in range(len(A)): | ||
| 88 | d = gcd(*A[i]) * (-1 if A[i][i] < 0 else 1) | ||
| 89 | A[i] = [A[i][k]//d for k in range(len(A[i]))] | ||
| 90 | b[i] = b[i]//d | ||
| 91 | |||
| 92 | return A, b, c | ||
| 93 | |||
| 94 | def paramcomb(nparam, c): | ||
| 95 | if nparam == 0: | ||
| 96 | return [[]] | ||
| 97 | |||
| 98 | ret = [] | ||
| 99 | for i in range(c[-nparam]+1): | ||
| 100 | ret += [[i, *l] for l in paramcomb(nparam-1, c)] | ||
| 101 | return ret | ||
| 102 | |||
| 103 | def solve_system_min_sum(A, b, c): | ||
| 104 | #nparam = len(A[0])-len(A) | ||
| 105 | #print(f"{nparam}: {paramcomb(nparam, c)}") | ||
| 106 | |||
| 107 | k = len(A[0]) - len(A) | ||
| 108 | mins = inf | ||
| 109 | for c in paramcomb(k, c): | ||
| 110 | sol = sum(c) | ||
| 111 | for i in range(len(A)): | ||
| 112 | p = sum(c[j]*A[i][len(A[0])-k+j] for j in range(len(c))) | ||
| 113 | a = (b[i] - p)//A[i][i] | ||
| 114 | if a < 0 or a*A[i][i] != b[i] - p: | ||
| 115 | sol = inf | ||
| 116 | break | ||
| 117 | sol += a | ||
| 118 | mins = min(mins, sol) | ||
| 119 | return mins | ||
| 120 | |||
| 121 | with fileinput.input() as lines: | ||
| 122 | sols = [] | ||
| 123 | k = 1 | ||
| 124 | for line in lines: | ||
| 125 | print(f"doing line {k}: {line[:-1]}") | ||
| 126 | A, b, c = readline(line) | ||
| 127 | #printabc(A, b, c) | ||
| 128 | A, b, c = reduce(A, b, c) | ||
| 129 | #printabc(A, b, c) | ||
| 130 | sols.append(solve_system_min_sum(A, b, c)) | ||
| 131 | k += 1 | ||
| 132 | print(sols) | ||
| 133 | print(sum(sols)) | ||
diff --git a/2025/10/b-recursive-slow.py b/2025/10/b-recursive-slow.py new file mode 100644 index 0000000..a52b50e --- /dev/null +++ b/2025/10/b-recursive-slow.py | |||
| @@ -0,0 +1,66 @@ | |||
| 1 | import fileinput | ||
| 2 | from functools import cache | ||
| 3 | |||
| 4 | def readline(line): | ||
| 5 | b = [] | ||
| 6 | j = line.index(']') | ||
| 7 | while '(' in line[j:]: | ||
| 8 | i = line[j:].index('(') + j + 1 | ||
| 9 | j = line[i:].index(')') + i | ||
| 10 | x = 0 | ||
| 11 | for y in line[i:j].split(','): | ||
| 12 | x |= 1 << int(y) | ||
| 13 | b.append(x) | ||
| 14 | |||
| 15 | begin = line.index('{')+1 | ||
| 16 | end = line.index('}') | ||
| 17 | j = tuple(int(x) for x in line[begin:end].split(',')) | ||
| 18 | |||
| 19 | return tuple(b), j | ||
| 20 | |||
| 21 | def newj(j, b, n): | ||
| 22 | return tuple(j[i]-n if b & (1<<i) else j[i] for i in range(len(j))) | ||
| 23 | |||
| 24 | def can(b, i): | ||
| 25 | return b & (1 << i) != 0 | ||
| 26 | |||
| 27 | inf = 99999999999 | ||
| 28 | @cache | ||
| 29 | def sol(b, j, best): | ||
| 30 | print(f"{b} {j} {best}") | ||
| 31 | if all(x == 0 for x in j): | ||
| 32 | return 0 | ||
| 33 | if any(x < 0 for x in j): | ||
| 34 | return inf | ||
| 35 | if len(b) == 0: | ||
| 36 | return inf | ||
| 37 | for i in range(len(j)): | ||
| 38 | if j[i] > 0 and not any(can(bb, i) for bb in b): | ||
| 39 | return inf | ||
| 40 | |||
| 41 | # Optimization: if any of the buttons is the last one available that | ||
| 42 | # toggles a certain jolt, we press it as much as needed. | ||
| 43 | for i in range(len(j)): | ||
| 44 | for k in range(len(b)): | ||
| 45 | bb = b[:k] + b[k+1:] | ||
| 46 | if can(b[k], i) and not any(can(x, i) for x in bb): | ||
| 47 | return j[i] + sol(bb, newj(j, b[k], j[i]), best) | ||
| 48 | |||
| 49 | c = 0 | ||
| 50 | while all(x >= 0 for x in j) and c <= best: | ||
| 51 | best = min(best, c + sol(b[1:], j, best)) | ||
| 52 | j = newj(j, b[0], 1) | ||
| 53 | c += 1 | ||
| 54 | |||
| 55 | return best | ||
| 56 | |||
| 57 | with fileinput.input() as lines: | ||
| 58 | sols = [] | ||
| 59 | c = 1 | ||
| 60 | for line in lines: | ||
| 61 | print(f"doing line {c}: {line}") | ||
| 62 | c += 1 | ||
| 63 | sols.append(sol(*readline(line), inf)) | ||
| 64 | print(sols) | ||
| 65 | print(sum(sols)) | ||
| 66 | |||
diff --git a/2025/10/b.py b/2025/10/b.py new file mode 100644 index 0000000..57fff46 --- /dev/null +++ b/2025/10/b.py | |||
| @@ -0,0 +1,125 @@ | |||
| 1 | import fileinput | ||
| 2 | from math import gcd | ||
| 3 | |||
| 4 | inf = 999999999 | ||
| 5 | |||
| 6 | # Read an input line and return a triple A, b, c where: | ||
| 7 | # A is the matrix of coefficient (A[i][j] is 1 if pressing the j-th button | ||
| 8 | # increases the i-th counter, 0 otherwise). | ||
| 9 | # b is the list of required final values of each counter. | ||
| 10 | # c is a list of bounds for the number of times each button can be pressed: | ||
| 11 | # c[i] is the minimum required value of a counter increased by button i. | ||
| 12 | def readline(l): | ||
| 13 | b = [int(x) for x in l[l.index('{')+1:l.index('}')].split(',')] | ||
| 14 | |||
| 15 | A = [[] for j in b] | ||
| 16 | c = [] | ||
| 17 | |||
| 18 | end = l.index(']') | ||
| 19 | while '(' in l[end:]: | ||
| 20 | begin = l[end:].index('(') + end + 1 | ||
| 21 | end = l[begin:].index(')') + begin | ||
| 22 | bu = [int(x) for x in l[begin:end].split(',')] | ||
| 23 | c.append(min(b[j] for j in bu)) | ||
| 24 | for i in range(len(b)): | ||
| 25 | A[i].append(1 if i in bu else 0) | ||
| 26 | |||
| 27 | return A, b, c | ||
| 28 | |||
| 29 | # swaprow, swapcol and reducerow are utility functions for the matrix | ||
| 30 | # row reduction procedure. | ||
| 31 | def swaprow(A, b, i, j): | ||
| 32 | if i != j: | ||
| 33 | A[i], A[j] = A[j], A[i] | ||
| 34 | b[i], b[j] = b[j], b[i] | ||
| 35 | |||
| 36 | def swapcol(A, c, i, j): | ||
| 37 | if i != j: | ||
| 38 | for k in range(len(A)): | ||
| 39 | A[k][i], A[k][j] = A[k][j], A[k][i] | ||
| 40 | c[i], c[j] = c[j], c[i] | ||
| 41 | |||
| 42 | def reducerow(A, b, i, j): | ||
| 43 | if A[i][i] != 0: | ||
| 44 | x = A[i][i] | ||
| 45 | y = -A[j][i] | ||
| 46 | d = gcd(x, y) | ||
| 47 | A[j] = [(y*A[i][k]+x*A[j][k])//d for k in range(len(A[i]))] | ||
| 48 | b[j] = (y*b[i]+x*b[j])//d | ||
| 49 | |||
| 50 | # Reduce the matrix A by row, including back subsitution. Returns a triple | ||
| 51 | # A, b, c where: | ||
| 52 | # A is the reduced matrix in the form [D|p] with D diagonal and p a matrix | ||
| 53 | # whose number of columns is the number of free parameters for Ax = b. | ||
| 54 | # b and c are the adjusted versions of the input paramters with the same | ||
| 55 | # names. In particular, b is adjusted every time a row is reduced and | ||
| 56 | # when rows are swapped, while c (the list of bounds) is adjusted when | ||
| 57 | # two columns are swapped (which is equivalent to changing the order of | ||
| 58 | # two buttons). | ||
| 59 | def reduce(A, b, c): | ||
| 60 | for i in range(len(A[0])): | ||
| 61 | # Swap columns until there is one in position i with at least | ||
| 62 | # one non-zero element. | ||
| 63 | I = [] | ||
| 64 | k = i | ||
| 65 | while len(I) == 0 and k < len(A[0]): | ||
| 66 | swapcol(A, c, i, k) | ||
| 67 | I = [j for j in range(i, len(A)) if A[j][i] != 0] | ||
| 68 | k += 1 | ||
| 69 | |||
| 70 | # If no such column is found, we are done | ||
| 71 | if len(I) == 0: | ||
| 72 | break | ||
| 73 | |||
| 74 | # Swap rows so that A[i][i] is non-zero | ||
| 75 | swaprow(A, b, i, I[0]) | ||
| 76 | |||
| 77 | # Reduce all other rows | ||
| 78 | for j in range(i+1, len(A)): | ||
| 79 | reducerow(A, b, i, j) | ||
| 80 | |||
| 81 | # Remove all rows of zero and check if the system is solvable | ||
| 82 | I = [i for i in range(len(A)) if any(a != 0 for a in A[i])] | ||
| 83 | A = [A[i] for i in I] | ||
| 84 | b = [b[i] for i in I] | ||
| 85 | |||
| 86 | # Back substitution | ||
| 87 | for i in range(len(A)-1, -1, -1): | ||
| 88 | for j in range(i): | ||
| 89 | reducerow(A, b, i, j) | ||
| 90 | |||
| 91 | return A, b, c | ||
| 92 | |||
| 93 | # Find all combinations of parameters respecting the bounds in c. The | ||
| 94 | # free parameters are always the last n-rank(A) columns of the matrix A | ||
| 95 | # (where n is the total number of columns). | ||
| 96 | def paramcomb(nparam, c): | ||
| 97 | if nparam == 0: | ||
| 98 | return [[]] | ||
| 99 | |||
| 100 | ret = [] | ||
| 101 | for i in range(c[-nparam]+1): | ||
| 102 | ret += [[i, *l] for l in paramcomb(nparam-1, c)] | ||
| 103 | return ret | ||
| 104 | |||
| 105 | # Solve the integer linear system Ax = b, minimizing the sum of the | ||
| 106 | # coordinates of x. | ||
| 107 | def solve_system_min_sum(A, b, c): | ||
| 108 | k = len(A[0]) - len(A) | ||
| 109 | mins = inf | ||
| 110 | for c in paramcomb(k, c): | ||
| 111 | sol = sum(c) | ||
| 112 | for i in range(len(A)): | ||
| 113 | cc = (c[j]*A[i][len(A[0])-k+j] for j in range(len(c))) | ||
| 114 | s = b[i]-sum(cc) | ||
| 115 | a = s // A[i][i] | ||
| 116 | # If a is negative or not integer, we skip this solution | ||
| 117 | if a < 0 or s % A[i][i] != 0: | ||
| 118 | sol = inf | ||
| 119 | break | ||
| 120 | sol += a | ||
| 121 | mins = min(mins, sol) | ||
| 122 | return mins | ||
| 123 | |||
| 124 | with fileinput.input() as lines: | ||
| 125 | print(sum(solve_system_min_sum(*reduce(*readline(l))) for l in lines)) | ||
diff --git a/2025/README.md b/2025/README.md index d75a4d3..dc87240 100644 --- a/2025/README.md +++ b/2025/README.md | |||
| @@ -11,6 +11,7 @@ Example | |||
| 11 | 11 | ||
| 12 | ``` | 12 | ``` |
| 13 | Day -Part 1- -Part 2- | 13 | Day -Part 1- -Part 2- |
| 14 | 10 00:27:43 11:51:51 | ||
| 14 | 9 00:05:05 02:11:41 | 15 | 9 00:05:05 02:11:41 |
| 15 | 8 00:29:14 00:33:02 | 16 | 8 00:29:14 00:33:02 |
| 16 | 7 00:05:27 00:20:40 | 17 | 7 00:05:27 00:20:40 |
| @@ -173,3 +174,8 @@ For example with this input: | |||
| 173 | The first version of my program return 4851 instead of 202. I added a | 174 | The first version of my program return 4851 instead of 202. I added a |
| 174 | check for this case in `b-fixed.py`, hopefully it works in 100% of the | 175 | check for this case in `b-fixed.py`, hopefully it works in 100% of the |
| 175 | cases now. | 176 | cases now. |
| 177 | |||
| 178 | ### Day 10: Factory | ||
| 179 | |||
| 180 | Phew, this was a hard one! | ||
| 181 | (More details coming soon, for now read the code and the comments) | ||
