advent of code 2025 in ts and nix
1let
2 input = builtins.readFile ../../shared/06/input.txt;
3 lines = builtins.filter (s: builtins.isString s && s != "") (builtins.split "\n" input);
4
5 # Helper to get character at position or space if out of bounds
6 charAt = str: idx:
7 if idx < 0 || idx >= builtins.stringLength str
8 then " "
9 else builtins.substring idx 1 str;
10
11 # Find whitespace columns (columns that are all spaces/tabs in data rows)
12 dataRows = builtins.genList (i: builtins.elemAt lines i) (builtins.length lines - 1);
13 maxLen = builtins.foldl' (max: row:
14 let len = builtins.stringLength row;
15 in if len > max then len else max
16 ) 0 dataRows;
17
18 # Find columns that are all whitespace
19 splitCols = builtins.filter (col: col != null) (builtins.genList (i:
20 let
21 allWS = builtins.foldl' (acc: row:
22 acc && (let ch = charAt row i; in ch == " " || ch == " ")
23 ) true dataRows;
24 in if allWS then i else null
25 ) maxLen);
26
27 # Split each row at whitespace columns
28 splitRow = row:
29 let
30 rowLen = builtins.stringLength row;
31 splitImpl = cuts: startPos: acc:
32 if builtins.length cuts == 0
33 then acc ++ [(builtins.substring startPos (rowLen - startPos) row)]
34 else
35 let
36 cut = builtins.head cuts;
37 restCuts = builtins.tail cuts;
38 end = if cut + 1 > rowLen then rowLen else cut + 1;
39 segment = builtins.substring startPos (end - startPos) row;
40 in splitImpl restCuts end (acc ++ [segment]);
41 in splitImpl splitCols 0 [];
42
43 # Split all lines (including operator row)
44 segmentedRows = builtins.map splitRow lines;
45
46 # Transpose to get columns (problems)
47 numProblems = builtins.length (builtins.head segmentedRows);
48 problems = builtins.genList (colIdx:
49 builtins.map (row: builtins.elemAt row colIdx) segmentedRows
50 ) numProblems;
51
52 # Fast trim - just remove spaces and tabs
53 # Extract just digits from a string for numbers
54 extractNum = str:
55 let
56 cleaned = builtins.replaceStrings [" " " "] ["" ""] str;
57 in if builtins.stringLength cleaned == 0 then 0 else builtins.fromJSON cleaned;
58
59 # Extract operator
60 extractOp = str:
61 let
62 cleaned = builtins.replaceStrings [" " " "] ["" ""] str;
63 in cleaned;
64
65 # Part 1: Normal left-to-right evaluation
66 part1 = builtins.foldl' (total: problem:
67 let
68 lastIdx = builtins.length problem - 1;
69 operator = extractOp (builtins.elemAt problem lastIdx);
70 nums = builtins.map (s: extractNum s) (builtins.genList (i: builtins.elemAt problem i) lastIdx);
71 value =
72 if operator == "*"
73 then builtins.foldl' (acc: n: acc * n) 1 nums
74 else builtins.foldl' (acc: n: acc + n) 0 nums;
75 in total + value
76 ) 0 problems;
77
78 # Part 2: Cepheid (vertical) reading
79 part2 = builtins.foldl' (total: problem:
80 let
81 lastIdx = builtins.length problem - 1;
82 operator = extractOp (builtins.elemAt problem lastIdx);
83 numStrs = builtins.genList (i: builtins.elemAt problem i) lastIdx;
84
85 # Find max width
86 maxWidth = builtins.foldl' (max: s:
87 let len = builtins.stringLength s;
88 in if len > max then len else max
89 ) 0 numStrs;
90
91 # Read vertically from right to left
92 cephNums = builtins.filter (n: n != null) (builtins.genList (colR:
93 let
94 # Build digits string from this column (right to left)
95 idx = maxWidth - colR;
96 digitsStr = builtins.concatStringsSep "" (builtins.filter (ch: ch != " " && ch != " ") (builtins.map (s:
97 if idx >= 0 && idx < builtins.stringLength s
98 then builtins.substring idx 1 s
99 else " "
100 ) numStrs));
101 in if builtins.stringLength digitsStr > 0
102 then builtins.fromJSON digitsStr
103 else null
104 ) (maxWidth + 1));
105
106 value =
107 if operator == "*"
108 then builtins.foldl' (acc: n: acc * n) 1 cephNums
109 else builtins.foldl' (acc: n: acc + n) 0 cephNums;
110 in total + value
111 ) 0 problems;
112
113in {
114 inherit part1 part2;
115}