#!/usr/bin/env nix-shell (* #!nix-shell --pure -i ocaml -p ocaml *) module CoordSet = Set.Make (struct type t = int * int let compare = compare end) type direction = North | South | East | West module CoordMap = Map.Make (struct type t = (int * int) * direction let compare = compare end) let walls, start_point, end_point = let with_wall wall (walls, start_point, end_point) = (CoordSet.add wall walls, start_point, end_point) in let with_start_point start_point (walls, _, end_point) = (walls, Some start_point, end_point) in let with_end_point end_point (walls, start_point, _) = (walls, start_point, Some end_point) in let rec parse_line x y = function | '#' :: xs -> parse_line (x + 1) y xs |> with_wall (x, y) | 'E' :: xs -> parse_line (x + 1) y xs |> with_end_point (x, y) | 'S' :: xs -> parse_line (x + 1) y xs |> with_start_point (x, y) | '.' :: xs -> parse_line (x + 1) y xs | x :: xs -> failwith (Printf.sprintf "invalid character in input %c" x) | [] -> (CoordSet.empty, None, None) in let rec aux walls start_point end_point y = try let new_walls, new_start_point, new_end_point = read_line () |> String.to_seq |> List.of_seq |> parse_line 0 y in let start_point = match start_point with Some _ as x -> x | None -> new_start_point in let end_point = match end_point with Some _ as x -> x | None -> new_end_point in aux (CoordSet.union walls new_walls) start_point end_point (y + 1) with End_of_file -> (walls, Option.get start_point, Option.get end_point) in aux CoordSet.empty None None 0 let next_coords (x, y) = function | North -> (x, y - 1) | South -> (x, y + 1) | West -> (x - 1, y) | East -> (x + 1, y) let invert_direction = function | North -> South | South -> North | East -> West | West -> East let directions = [North; South; East; West] type state = {dist: int CoordMap.t; queue: (int * (int * int) * direction) list} let traverse start_point origins = let initial_state = { dist= List.fold_left (fun acc origin -> CoordMap.add (start_point, origin) 0 acc) CoordMap.empty origins ; queue= List.map (fun origin -> (0, start_point, origin)) origins } in let is_better_path dist (x, y) dir score = match CoordMap.find_opt ((x, y), dir) dist with | Some v -> v > score | None -> true in let add_to_queue queue entry = List.sort compare (entry :: queue) in let process_direction state (score, (x, y), dir) next_dir = let new_score = score + 1000 in if not (is_better_path state.dist (x, y) next_dir new_score) then state else { dist= CoordMap.add ((x, y), next_dir) new_score state.dist ; queue= add_to_queue state.queue (new_score, (x, y), next_dir) } in let process_forward state (score, (x, y), dir) = let next_pos = next_coords (x, y) dir in let new_score = score + 1 in if CoordSet.mem next_pos walls || not (is_better_path state.dist next_pos dir new_score) then state else { dist= CoordMap.add (next_pos, dir) new_score state.dist ; queue= add_to_queue state.queue (new_score, next_pos, dir) } in let process_node state ((score, pos, dir) as node) = if is_better_path state.dist pos dir score then state else let state_after_directions = List.fold_left (fun acc_state next_dir -> if next_dir = dir then acc_state else process_direction acc_state node next_dir ) state directions in process_forward state_after_directions node in let rec process_queue state = match state.queue with | [] -> state.dist | node :: rest -> let state' = {state with queue= rest} in process_queue (process_node state' node) in process_queue initial_state let part1 = traverse start_point [East] |> CoordMap.to_list |> List.filter (fun ((pos, _), _) -> pos = end_point) |> List.map snd |> List.fold_left min max_int let _ = part1 |> print_int |> print_newline let part2 = let zip o1 o2 = match (o1, o2) with Some v1, Some v2 -> Some (v1, v2) | _ -> None in let from_start = traverse start_point [East] in let from_end = traverse end_point directions in let max_x = walls |> CoordSet.to_list |> List.map fst |> List.fold_left max min_int in let max_y = walls |> CoordSet.to_list |> List.map snd |> List.fold_left max min_int in let coordinates = List.init (max_y + 1) (fun y -> List.init (max_x + 1) (fun x -> (x, y))) |> List.concat in let is_valid_point (x, y) = directions |> List.filter_map (fun dir -> zip (CoordMap.find_opt ((x, y), dir) from_start) (CoordMap.find_opt ((x, y), invert_direction dir) from_end) ) |> List.exists (fun (a, b) -> a + b = part1) in coordinates |> List.filter is_valid_point |> List.fold_left (fun acc coord -> CoordSet.add coord acc) CoordSet.empty |> CoordSet.cardinal |> print_int |> print_newline