advent of code 2025 in ts and nix
at main 5.0 kB view raw
1let 2 input = builtins.readFile ../../shared/05/input.txt; 3 4 # Split input into two sections 5 sections = builtins.filter (s: s != "") (builtins.split "\n\n" input); 6 rangesSection = builtins.head sections; 7 ingredientsSection = builtins.elemAt sections 2; # sections are [ranges, "\n\n", ingredients] 8 9 # Parse ranges from "start-end" format 10 rangeLines = builtins.filter (s: builtins.isString s && s != "") (builtins.split "\n" rangesSection); 11 parsedRanges = builtins.map (line: 12 let 13 parts = builtins.filter (s: builtins.isString s && s != "") (builtins.split "-" line); 14 start = builtins.fromJSON (builtins.head parts); 15 end = builtins.fromJSON (builtins.elemAt parts 1); 16 in { inherit start end; } 17 ) rangeLines; 18 19 # Sort ranges by start position using merge sort 20 sortedRanges = 21 let 22 merge = left: right: 23 let 24 mergeImpl = l: r: acc: 25 if builtins.length l == 0 then acc ++ r 26 else if builtins.length r == 0 then acc ++ l 27 else 28 let 29 lHead = builtins.head l; 30 rHead = builtins.head r; 31 lTail = builtins.tail l; 32 rTail = builtins.tail r; 33 in if lHead.start <= rHead.start 34 then mergeImpl lTail r (acc ++ [lHead]) 35 else mergeImpl l rTail (acc ++ [rHead]); 36 in mergeImpl left right []; 37 38 mergeSort = list: 39 let len = builtins.length list; 40 in if len <= 1 then list 41 else 42 let 43 mid = len / 2; 44 left = builtins.genList (i: builtins.elemAt list i) mid; 45 right = builtins.genList (i: builtins.elemAt list (i + mid)) (len - mid); 46 in merge (mergeSort left) (mergeSort right); 47 in mergeSort parsedRanges; 48 49 # Merge overlapping or adjacent ranges 50 mergedRanges = 51 let 52 merge = builtins.foldl' (state: range: 53 if state.current == null then { current = range; result = []; } 54 else 55 let curr = state.current; 56 in if curr.end + 1 >= range.start then 57 # Overlapping or adjacent - merge 58 { current = { start = curr.start; end = if curr.end > range.end then curr.end else range.end; }; 59 result = state.result; } 60 else 61 # Gap - save current and start new 62 { current = range; 63 result = state.result ++ [curr]; } 64 ) { current = null; result = []; } sortedRanges; 65 in if merge.current == null then merge.result else merge.result ++ [merge.current]; 66 67 # Parse ingredients 68 ingredientLines = builtins.filter (s: builtins.isString s && s != "") (builtins.split "\n" ingredientsSection); 69 ingredients = builtins.map (line: builtins.fromJSON line) ingredientLines; 70 71 # Sort ingredients using merge sort 72 sortedIngredients = 73 let 74 merge = left: right: 75 let 76 mergeImpl = l: r: acc: 77 if builtins.length l == 0 then acc ++ r 78 else if builtins.length r == 0 then acc ++ l 79 else 80 let 81 lHead = builtins.head l; 82 rHead = builtins.head r; 83 lTail = builtins.tail l; 84 rTail = builtins.tail r; 85 in if lHead <= rHead 86 then mergeImpl lTail r (acc ++ [lHead]) 87 else mergeImpl l rTail (acc ++ [rHead]); 88 in mergeImpl left right []; 89 90 mergeSort = list: 91 let len = builtins.length list; 92 in if len <= 1 then list 93 else 94 let 95 mid = len / 2; 96 left = builtins.genList (i: builtins.elemAt list i) mid; 97 right = builtins.genList (i: builtins.elemAt list (i + mid)) (len - mid); 98 in merge (mergeSort left) (mergeSort right); 99 in mergeSort ingredients; 100 101 # Part 1: Count ingredients that fall within merged ranges 102 part1 = 103 let 104 # Use foldl' to process ingredients with range index tracking 105 result = builtins.foldl' (state: ingredient: 106 let 107 # Find the appropriate range starting from current index 108 findRange = rangeIdx: 109 if rangeIdx >= builtins.length mergedRanges then { found = false; newIdx = rangeIdx; } 110 else 111 let range = builtins.elemAt mergedRanges rangeIdx; 112 in if ingredient < range.start then { found = false; newIdx = rangeIdx; } 113 else if ingredient <= range.end then { found = true; newIdx = rangeIdx; } 114 else findRange (rangeIdx + 1); 115 116 rangeResult = findRange state.rangeIdx; 117 in { 118 count = state.count + (if rangeResult.found then 1 else 0); 119 rangeIdx = rangeResult.newIdx; 120 } 121 ) { count = 0; rangeIdx = 0; } sortedIngredients; 122 in result.count; 123 124 # Part 2: Total coverage of all merged ranges 125 part2 = builtins.foldl' (sum: range: sum + (range.end - range.start + 1)) 0 mergedRanges; 126 127in { 128 inherit part1 part2; 129}