advent of code 2025 in ts and nix
1let
2 input = builtins.readFile ../../shared/02/input.txt;
3
4 # Parse comma-separated ranges into list of {start, end}
5 ranges =
6 let
7 rangeStrings = builtins.filter builtins.isString (builtins.split "," input);
8 parseRange = str:
9 let parts = builtins.match "([0-9]+)-([0-9]+)" str;
10 in { start = builtins.fromJSON (builtins.elemAt parts 0);
11 end = builtins.fromJSON (builtins.elemAt parts 1); };
12 in map parseRange rangeStrings;
13
14 # Generate all numbers with equal halves in a range
15 generateEqualHalves = start: end:
16 let
17 startLen = builtins.stringLength (builtins.toString start);
18 endLen = builtins.stringLength (builtins.toString end);
19
20 # For even digit counts, generate patterns directly
21 generateForDigits = numDigits:
22 let
23 halfDigits = numDigits / 2;
24 isEven = (numDigits - halfDigits * 2) == 0;
25 in
26 if !isEven || numDigits < 2 then []
27 else
28 let
29 # Calculate 10^halfDigits for multiplier
30 multiplier = builtins.foldl' (a: _: a * 10) 1 (builtins.genList (x: x) halfDigits);
31 halfMin = multiplier / 10;
32 halfMax = multiplier - 1;
33
34 # Generate number from half: n = half * (10^half + 1)
35 makeNum = half: half * (multiplier + 1);
36
37 minVal = makeNum halfMin;
38 maxVal = makeNum halfMax;
39
40 # Only generate if range overlaps
41 actualMin = if minVal < start then
42 let h = (start + multiplier) / (multiplier + 1); in if makeNum h >= start then h else h + 1
43 else halfMin;
44 actualMax = if maxVal > end then
45 end / (multiplier + 1)
46 else halfMax;
47
48 count = if actualMax >= actualMin then actualMax - actualMin + 1 else 0;
49 in
50 if count > 0 then builtins.genList (i: makeNum (actualMin + i)) count else [];
51
52 allDigits = builtins.genList (d: startLen + d) (endLen - startLen + 1);
53 allNums = builtins.concatMap generateForDigits allDigits;
54 in allNums;
55
56 # Generate all repeating pattern numbers in a range
57 generateRepeating = start: end:
58 let
59 startLen = builtins.stringLength (builtins.toString start);
60 endLen = builtins.stringLength (builtins.toString end);
61
62 # Calculate 10^n efficiently
63 pow10 = n: builtins.foldl' (a: _: a * 10) 1 (builtins.genList (x: x) n);
64
65 # Generate numbers with specific total length and chunk size
66 generateForPattern = totalDigits: chunkSize:
67 let
68 reps = totalDigits / chunkSize;
69 isValid = (totalDigits - reps * chunkSize) == 0 && chunkSize * 2 <= totalDigits;
70 in
71 if !isValid then []
72 else
73 let
74 # Calculate multiplier for repeating: e.g., for 3 reps of 2 digits: 10^4 + 10^2 + 1
75 calcMultiplier =
76 let terms = builtins.genList (i: pow10 (i * chunkSize)) reps;
77 in builtins.foldl' builtins.add 0 terms;
78
79 multiplier = calcMultiplier;
80 chunkMin = pow10 (chunkSize - 1);
81 chunkMax = pow10 chunkSize - 1;
82
83 makeNum = chunk: chunk * multiplier;
84
85 minVal = makeNum chunkMin;
86 maxVal = makeNum chunkMax;
87
88 # Calculate actual range
89 actualMin = if minVal < start then
90 let c = (start + multiplier - 1) / multiplier;
91 in if c > chunkMax then chunkMax + 1 else if c < chunkMin then chunkMin else c
92 else chunkMin;
93 actualMax = if maxVal > end then
94 end / multiplier
95 else chunkMax;
96
97 count = if actualMax >= actualMin then actualMax - actualMin + 1 else 0;
98 nums = if count > 0 then builtins.genList (i: makeNum (actualMin + i)) count else [];
99 filtered = builtins.filter (n: n >= start && n <= end) nums;
100 in filtered;
101
102 # For each digit count, try all valid chunk sizes
103 generateForDigits = numDigits:
104 let
105 maxChunk = numDigits / 2;
106 validChunks = builtins.filter
107 (c: (numDigits - numDigits / c * c) == 0)
108 (builtins.genList (i: i + 1) maxChunk);
109 in builtins.concatMap (c: generateForPattern numDigits c) validChunks;
110
111 allDigits = builtins.genList (d: startLen + d) (endLen - startLen + 1);
112 allNums = builtins.concatLists (map generateForDigits allDigits);
113
114 # Remove duplicates efficiently using sort
115 sorted = builtins.sort (a: b: a < b) allNums;
116 dedupe = builtins.foldl'
117 (acc: n: if acc.prev == n then acc else { prev = n; list = acc.list ++ [n]; })
118 { prev = -1; list = []; }
119 sorted;
120 in dedupe.list;
121
122 part1 =
123 let
124 processRange = acc: r:
125 let nums = generateEqualHalves r.start r.end;
126 in acc + builtins.foldl' builtins.add 0 nums;
127 in builtins.foldl' processRange 0 ranges;
128
129 part2 =
130 let
131 processRange = acc: r:
132 let nums = generateRepeating r.start r.end;
133 in acc + builtins.foldl' builtins.add 0 nums;
134 in builtins.foldl' processRange 0 ranges;
135
136in {
137 inherit part1 part2;
138}