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}