type grid = { grid : string; width : int; height : int } let grid_of_file fname = let strs = Aoc.strings_of_file fname in let grid = List.fold_left String.cat "" strs in let width = String.length (List.hd strs) in let height = String.length grid / width in { grid; width; height } let grid_length grid = String.length grid.grid let grid_get_by_idx grid idx = grid.grid.[idx] let grid_get_by_pos grid (x, y) = grid.grid.[x + (y * grid.width)] let grid_pos_of_idx grid idx = (idx mod grid.width, idx / grid.width) let grid_pos_is_valid grid (x, y) = x >= 0 && y >= 0 && x < grid.width && y < grid.height let next_char ch = Char.chr (1 + Char.code ch) let ten = next_char '9' let one = '1' let find_trail grid pos0 = let add_pos lst pos digit = if grid_pos_is_valid grid pos && grid_get_by_pos grid pos = digit then pos :: lst else lst in let add_poses lst (x, y) digit = let lst = add_pos lst (x - 1, y) digit in let lst = add_pos lst (x + 1, y) digit in let lst = add_pos lst (x, y - 1) digit in let lst = add_pos lst (x, y + 1) digit in lst in let rec find_next acc digit = function | [] -> acc | h :: t -> find_next (add_poses acc h digit) digit t in let rec impl acc digit = if digit = ten then acc else impl (find_next [] digit acc) (next_char digit) in impl [ pos0 ] one let find_trails grid = let rec impl acc idx = if idx >= grid_length grid then acc else if grid_get_by_idx grid idx <> '0' then impl acc (idx + 1) else (* grid_get_by_idx grid idx = 0 *) impl (find_trail grid (grid_pos_of_idx grid idx) :: acc) (idx + 1) in impl [] 0 |> List.rev let part1 grid = find_trails grid |> List.map (List.sort_uniq compare) |> List.map List.length |> List.fold_left ( + ) 0 let part2 grid = find_trails grid |> List.map List.length |> List.fold_left ( + ) 0 let _ = Aoc.main grid_of_file [ (string_of_int, part1); (string_of_int, part2) ]