···
+
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:
+
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; }
+
# Sort ranges by start position using merge sort
+
if builtins.length l == 0 then acc ++ r
+
else if builtins.length r == 0 then acc ++ l
+
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 [];
+
let len = builtins.length list;
+
in if len <= 1 then list
+
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
+
merge = builtins.foldl' (state: range:
+
if state.current == null then { current = range; result = []; }
+
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; }
+
# Gap - save current and start new
+
result = state.result ++ [curr]; }
+
) { current = null; result = []; } sortedRanges;
+
in if merge.current == null then merge.result else merge.result ++ [merge.current];
+
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
+
if builtins.length l == 0 then acc ++ r
+
else if builtins.length r == 0 then acc ++ l
+
lHead = builtins.head l;
+
rHead = builtins.head r;
+
lTail = builtins.tail l;
+
rTail = builtins.tail r;
+
then mergeImpl lTail r (acc ++ [lHead])
+
else mergeImpl l rTail (acc ++ [rHead]);
+
in mergeImpl left right [];
+
let len = builtins.length list;
+
in if len <= 1 then list
+
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
+
# Use foldl' to process ingredients with range index tracking
+
result = builtins.foldl' (state: ingredient:
+
# Find the appropriate range starting from current index
+
if rangeIdx >= builtins.length mergedRanges then { found = false; newIdx = rangeIdx; }
+
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;
+
count = state.count + (if rangeResult.found then 1 else 0);
+
rangeIdx = rangeResult.newIdx;
+
) { count = 0; rangeIdx = 0; } sortedIngredients;
+
# Part 2: Total coverage of all merged ranges
+
part2 = builtins.foldl' (sum: range: sum + (range.end - range.start + 1)) 0 mergedRanges;