advent of code 2025 in ts and nix
at main 4.1 kB view raw
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}