advent of code 2025 in ts and nix
at main 6.0 kB view raw
1let 2 input = builtins.readFile ../../shared/07/input.txt; 3 lines = builtins.filter (s: builtins.isString s && s != "") (builtins.split "\n" input); 4 5 # Parse grid into 2D array of characters 6 grid = builtins.map (line: builtins.genList (i: builtins.substring i 1 line) (builtins.stringLength line)) lines; 7 8 rows = builtins.length grid; 9 cols = builtins.length (builtins.head grid); 10 11 # Find starting position 12 findStart = grid: 13 let 14 findInRow = rowIdx: row: 15 let 16 colIdx = builtins.foldl' (acc: i: 17 if acc != null then acc 18 else if builtins.elemAt row i == "S" then i 19 else null 20 ) null (builtins.genList (i: i) (builtins.length row)); 21 in if colIdx != null then { r = rowIdx; c = colIdx; } else null; 22 23 result = builtins.foldl' (acc: i: 24 if acc != null then acc 25 else findInRow i (builtins.elemAt grid i) 26 ) null (builtins.genList (i: i) (builtins.length grid)); 27 in result; 28 29 startPos = findStart grid; 30 31 # Check if position is in bounds 32 inBounds = r: c: r >= 0 && r < rows && c >= 0 && c < cols; 33 34 # Get cell at position 35 getCell = r: c: 36 if inBounds r c 37 then builtins.elemAt (builtins.elemAt grid r) c 38 else "."; 39 40 # Part 1: Count total splits 41 part1 = 42 let 43 # Process one step of beams 44 stepBeams = state: 45 let 46 beams = state.beams; 47 nextBeams = builtins.concatMap (beam: 48 let 49 r = beam.r; 50 c = beam.c; 51 nr = r + 1; 52 nc = c; 53 in 54 if !inBounds nr nc then [] 55 else 56 let cell = getCell nr nc; 57 in 58 if cell == "." || cell == "S" then [{ r = nr; c = nc; }] 59 else if cell == "^" then 60 # Split into left and right 61 let 62 left = if inBounds nr (nc - 1) then [{ r = nr; c = nc - 1; }] else []; 63 right = if inBounds nr (nc + 1) then [{ r = nr; c = nc + 1; }] else []; 64 in left ++ right 65 else [{ r = nr; c = nc; }] 66 ) beams; 67 68 # Count splits in this step 69 splits = builtins.foldl' (acc: beam: 70 let 71 r = beam.r; 72 c = beam.c; 73 nr = r + 1; 74 nc = c; 75 cell = if inBounds nr nc then getCell nr nc else "."; 76 in if cell == "^" then acc + 1 else acc 77 ) 0 beams; 78 79 # Remove duplicates using string keys 80 uniqueBeams = 81 let 82 keysSet = builtins.foldl' (acc: beam: 83 acc // { "${toString beam.r},${toString beam.c}" = beam; } 84 ) {} nextBeams; 85 in builtins.attrValues keysSet; 86 in 87 { 88 beams = uniqueBeams; 89 totalSplits = state.totalSplits + splits; 90 }; 91 92 # Run simulation until no beams left 93 simulate = state: 94 if builtins.length state.beams == 0 95 then state.totalSplits 96 else simulate (stepBeams state); 97 98 initialState = { 99 beams = [{ r = startPos.r; c = startPos.c; }]; 100 totalSplits = 0; 101 }; 102 in simulate initialState; 103 104 # Part 2: Count total timelines 105 part2 = 106 let 107 # Helper to parse "r,c" key 108 parseKey = key: 109 let 110 parts = builtins.match "([0-9]+),([0-9]+)" key; 111 in { 112 r = builtins.fromJSON (builtins.head parts); 113 c = builtins.fromJSON (builtins.elemAt parts 1); 114 }; 115 116 # Process one step with timeline counts 117 stepTimelines = states: 118 let 119 # For each position with count, move forward 120 nextStates = builtins.foldl' (acc: key: 121 let 122 count = builtins.getAttr key states; 123 pos = parseKey key; 124 r = pos.r; 125 c = pos.c; 126 nr = r + 1; 127 nc = c; 128 in 129 if !inBounds nr nc then acc 130 else 131 let cell = getCell nr nc; 132 in 133 if cell == "." || cell == "S" then 134 # Continue straight 135 let nkey = "${toString nr},${toString nc}"; 136 in acc // { ${nkey} = (acc.${nkey} or 0) + count; } 137 else if cell == "^" then 138 # Split into two timelines 139 let 140 left = if inBounds nr (nc - 1) then 141 let lkey = "${toString nr},${toString (nc - 1)}"; 142 in { ${lkey} = (acc.${lkey} or 0) + count; } 143 else {}; 144 right = if inBounds nr (nc + 1) then 145 let rkey = "${toString nr},${toString (nc + 1)}"; 146 in { ${rkey} = (acc.${rkey} or 0) + count; } 147 else {}; 148 in acc // left // right 149 else 150 # Continue straight 151 let nkey = "${toString nr},${toString nc}"; 152 in acc // { ${nkey} = (acc.${nkey} or 0) + count; } 153 ) {} (builtins.attrNames states); 154 in nextStates; 155 156 # Run simulation for all rows 157 simulate = states: step: 158 let 159 nextStates = if step >= rows then {} else stepTimelines states; 160 hasStates = builtins.length (builtins.attrNames nextStates) > 0; 161 in 162 if step >= rows || !hasStates 163 then states # Return last valid states 164 else simulate nextStates (step + 1); 165 166 initialStates = { "${toString startPos.r},${toString startPos.c}" = 1; }; 167 finalStates = simulate initialStates 0; 168 169 # Sum all timeline counts 170 totalTimelines = builtins.foldl' (acc: key: 171 acc + builtins.getAttr key finalStates 172 ) 0 (builtins.attrNames finalStates); 173 in totalTimelines; 174 175in { 176 inherit part1 part2; 177}