···
+
input = builtins.readFile ../../shared/07/input.txt;
+
lines = builtins.filter (s: builtins.isString s && s != "") (builtins.split "\n" input);
+
# Parse grid into 2D array of characters
+
grid = builtins.map (line: builtins.genList (i: builtins.substring i 1 line) (builtins.stringLength line)) lines;
+
rows = builtins.length grid;
+
cols = builtins.length (builtins.head grid);
+
# Find starting position
+
findInRow = rowIdx: row:
+
colIdx = builtins.foldl' (acc: i:
+
if acc != null then acc
+
else if builtins.elemAt row i == "S" then i
+
) null (builtins.genList (i: i) (builtins.length row));
+
in if colIdx != null then { r = rowIdx; c = colIdx; } else null;
+
result = builtins.foldl' (acc: i:
+
if acc != null then acc
+
else findInRow i (builtins.elemAt grid i)
+
) null (builtins.genList (i: i) (builtins.length grid));
+
startPos = findStart grid;
+
# Check if position is in bounds
+
inBounds = r: c: r >= 0 && r < rows && c >= 0 && c < cols;
+
then builtins.elemAt (builtins.elemAt grid r) c
+
# Part 1: Count total splits
+
# Process one step of beams
+
nextBeams = builtins.concatMap (beam:
+
if !inBounds nr nc then []
+
let cell = getCell nr nc;
+
if cell == "." || cell == "S" then [{ r = nr; c = nc; }]
+
else if cell == "^" then
+
# Split into left and right
+
left = if inBounds nr (nc - 1) then [{ r = nr; c = nc - 1; }] else [];
+
right = if inBounds nr (nc + 1) then [{ r = nr; c = nc + 1; }] else [];
+
else [{ r = nr; c = nc; }]
+
# Count splits in this step
+
splits = builtins.foldl' (acc: beam:
+
cell = if inBounds nr nc then getCell nr nc else ".";
+
in if cell == "^" then acc + 1 else acc
+
# Remove duplicates using string keys
+
keysSet = builtins.foldl' (acc: beam:
+
acc // { "${toString beam.r},${toString beam.c}" = beam; }
+
in builtins.attrValues keysSet;
+
totalSplits = state.totalSplits + splits;
+
# Run simulation until no beams left
+
if builtins.length state.beams == 0
+
else simulate (stepBeams state);
+
beams = [{ r = startPos.r; c = startPos.c; }];
+
in simulate initialState;
+
# Part 2: Count total timelines
+
# Helper to parse "r,c" key
+
parts = builtins.match "([0-9]+),([0-9]+)" key;
+
r = builtins.fromJSON (builtins.head parts);
+
c = builtins.fromJSON (builtins.elemAt parts 1);
+
# Process one step with timeline counts
+
stepTimelines = states:
+
# For each position with count, move forward
+
nextStates = builtins.foldl' (acc: key:
+
count = builtins.getAttr key states;
+
if !inBounds nr nc then acc
+
let cell = getCell nr nc;
+
if cell == "." || cell == "S" then
+
let nkey = "${toString nr},${toString nc}";
+
in acc // { ${nkey} = (acc.${nkey} or 0) + count; }
+
else if cell == "^" then
+
# Split into two timelines
+
left = if inBounds nr (nc - 1) then
+
let lkey = "${toString nr},${toString (nc - 1)}";
+
in { ${lkey} = (acc.${lkey} or 0) + count; }
+
right = if inBounds nr (nc + 1) then
+
let rkey = "${toString nr},${toString (nc + 1)}";
+
in { ${rkey} = (acc.${rkey} or 0) + count; }
+
in acc // left // right
+
let nkey = "${toString nr},${toString nc}";
+
in acc // { ${nkey} = (acc.${nkey} or 0) + count; }
+
) {} (builtins.attrNames states);
+
# Run simulation for all rows
+
simulate = states: step:
+
nextStates = if step >= rows then {} else stepTimelines states;
+
hasStates = builtins.length (builtins.attrNames nextStates) > 0;
+
if step >= rows || !hasStates
+
then states # Return last valid states
+
else simulate nextStates (step + 1);
+
initialStates = { "${toString startPos.r},${toString startPos.c}" = 1; };
+
finalStates = simulate initialStates 0;
+
# Sum all timeline counts
+
totalTimelines = builtins.foldl' (acc: key:
+
acc + builtins.getAttr key finalStates
+
) 0 (builtins.attrNames finalStates);