advent of code 2025 in ts and nix

feat: add day 5 nix solution

dunkirk.sh 0b0024ac b6e2d3fc

verified
Changed files
+129
nix
+129
nix/05/solution.nix
···
+
let
+
input = builtins.readFile ../../shared/05/input.txt;
+
+
# Split input into two sections
+
sections = builtins.filter (s: s != "") (builtins.split "\n\n" input);
+
rangesSection = builtins.head sections;
+
ingredientsSection = builtins.elemAt sections 2; # sections are [ranges, "\n\n", ingredients]
+
+
# Parse ranges from "start-end" format
+
rangeLines = builtins.filter (s: builtins.isString s && s != "") (builtins.split "\n" rangesSection);
+
parsedRanges = builtins.map (line:
+
let
+
parts = builtins.filter (s: builtins.isString s && s != "") (builtins.split "-" line);
+
start = builtins.fromJSON (builtins.head parts);
+
end = builtins.fromJSON (builtins.elemAt parts 1);
+
in { inherit start end; }
+
) rangeLines;
+
+
# Sort ranges by start position using merge sort
+
sortedRanges =
+
let
+
merge = left: right:
+
let
+
mergeImpl = l: r: acc:
+
if builtins.length l == 0 then acc ++ r
+
else if builtins.length r == 0 then acc ++ l
+
else
+
let
+
lHead = builtins.head l;
+
rHead = builtins.head r;
+
lTail = builtins.tail l;
+
rTail = builtins.tail r;
+
in if lHead.start <= rHead.start
+
then mergeImpl lTail r (acc ++ [lHead])
+
else mergeImpl l rTail (acc ++ [rHead]);
+
in mergeImpl left right [];
+
+
mergeSort = list:
+
let len = builtins.length list;
+
in if len <= 1 then list
+
else
+
let
+
mid = len / 2;
+
left = builtins.genList (i: builtins.elemAt list i) mid;
+
right = builtins.genList (i: builtins.elemAt list (i + mid)) (len - mid);
+
in merge (mergeSort left) (mergeSort right);
+
in mergeSort parsedRanges;
+
+
# Merge overlapping or adjacent ranges
+
mergedRanges =
+
let
+
merge = builtins.foldl' (state: range:
+
if state.current == null then { current = range; result = []; }
+
else
+
let curr = state.current;
+
in if curr.end + 1 >= range.start then
+
# Overlapping or adjacent - merge
+
{ current = { start = curr.start; end = if curr.end > range.end then curr.end else range.end; };
+
result = state.result; }
+
else
+
# Gap - save current and start new
+
{ current = range;
+
result = state.result ++ [curr]; }
+
) { current = null; result = []; } sortedRanges;
+
in if merge.current == null then merge.result else merge.result ++ [merge.current];
+
+
# Parse ingredients
+
ingredientLines = builtins.filter (s: builtins.isString s && s != "") (builtins.split "\n" ingredientsSection);
+
ingredients = builtins.map (line: builtins.fromJSON line) ingredientLines;
+
+
# Sort ingredients using merge sort
+
sortedIngredients =
+
let
+
merge = left: right:
+
let
+
mergeImpl = l: r: acc:
+
if builtins.length l == 0 then acc ++ r
+
else if builtins.length r == 0 then acc ++ l
+
else
+
let
+
lHead = builtins.head l;
+
rHead = builtins.head r;
+
lTail = builtins.tail l;
+
rTail = builtins.tail r;
+
in if lHead <= rHead
+
then mergeImpl lTail r (acc ++ [lHead])
+
else mergeImpl l rTail (acc ++ [rHead]);
+
in mergeImpl left right [];
+
+
mergeSort = list:
+
let len = builtins.length list;
+
in if len <= 1 then list
+
else
+
let
+
mid = len / 2;
+
left = builtins.genList (i: builtins.elemAt list i) mid;
+
right = builtins.genList (i: builtins.elemAt list (i + mid)) (len - mid);
+
in merge (mergeSort left) (mergeSort right);
+
in mergeSort ingredients;
+
+
# Part 1: Count ingredients that fall within merged ranges
+
part1 =
+
let
+
# Use foldl' to process ingredients with range index tracking
+
result = builtins.foldl' (state: ingredient:
+
let
+
# Find the appropriate range starting from current index
+
findRange = rangeIdx:
+
if rangeIdx >= builtins.length mergedRanges then { found = false; newIdx = rangeIdx; }
+
else
+
let range = builtins.elemAt mergedRanges rangeIdx;
+
in if ingredient < range.start then { found = false; newIdx = rangeIdx; }
+
else if ingredient <= range.end then { found = true; newIdx = rangeIdx; }
+
else findRange (rangeIdx + 1);
+
+
rangeResult = findRange state.rangeIdx;
+
in {
+
count = state.count + (if rangeResult.found then 1 else 0);
+
rangeIdx = rangeResult.newIdx;
+
}
+
) { count = 0; rangeIdx = 0; } sortedIngredients;
+
in result.count;
+
+
# Part 2: Total coverage of all merged ranges
+
part2 = builtins.foldl' (sum: range: sum + (range.end - range.start + 1)) 0 mergedRanges;
+
+
in {
+
inherit part1 part2;
+
}