defmodule Day3 do def paths do File.read!("lib/day3/input.txt") |> String.trim() |> String.split("\n") |> Enum.map(&parse_path/1) # [parse_path("R8,U5,L5,D3"), parse_path("U7,R6,D4,L4")] # [ # parse_path("R75,D30,R83,U83,L12,D49,R71,U7,L72"), # parse_path("U62,R66,U55,R34,D71,R55,D58,R83") # ] # [ # parse_path("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51"), # parse_path("U98,R91,D20,R16,D67,R40,U7,R15,U6,R7") # ] end def part1 do [path_a, path_b] = paths() all_intersections(path_a, path_b) |> List.delete({0, 0}) |> nearest_intersection() end def part2 do [path_a, path_b] = paths() all_intersections(path_a, path_b) |> List.delete({0, 0}) |> Enum.map(fn intersection -> { intersection, steps_to_reach_point(path_a, intersection) + steps_to_reach_point(path_b, intersection) } end) |> Enum.min_by(fn {_intersection, steps} -> steps end) end def parse_path(path) do {path, _final_point} = path |> String.split(",") |> Enum.map_reduce({0, 0}, fn insn, {x, y} = point -> case apply_insn(insn, point) do {:horiz, new_x} -> { {:horiz, x, new_x, y}, {new_x, y} } {:vert, new_y} -> { {:vert, x, y, new_y}, {x, new_y} } end end) path end def apply_insn("L" <> digits, {x, _}), do: {:horiz, x - String.to_integer(digits)} def apply_insn("R" <> digits, {x, _}), do: {:horiz, x + String.to_integer(digits)} def apply_insn("U" <> digits, {_, y}), do: {:vert, y + String.to_integer(digits)} def apply_insn("D" <> digits, {_, y}), do: {:vert, y - String.to_integer(digits)} def intersection({:vert, a_x, a_y, a_y2}, {:horiz, b_x, b_x2, b_y}) do if a_x >= min(b_x, b_x2) and a_x <= max(b_x, b_x2) and b_y >= min(a_y, a_y2) and b_y <= max(a_y, a_y2) do {a_x, b_y} end end def intersection({:horiz, _, _, _} = a, {:vert, _, _, _} = b) do intersection(b, a) end def intersection(_a, _b), do: nil def all_intersections(path_a, path_b) do Enum.flat_map(path_a, fn a_segment -> path_b |> Enum.map(fn b_segment -> intersection(a_segment, b_segment) end) |> Enum.reject(&is_nil/1) end) end def nearest_intersection(intersections) do Enum.min_by(intersections, fn {x, y} -> abs(x) + abs(y) end) end def steps_to_reach_point(path, final_point, steps \\ 0) def steps_to_reach_point([{:horiz, x, x2, y} | rest_path], {final_x, final_y}, steps) do if final_x >= min(x, x2) and final_x <= max(x, x2) and final_y == y do steps + abs(final_x - x) else steps_to_reach_point(rest_path, {final_x, final_y}, steps + abs(x - x2)) end end def steps_to_reach_point([{:vert, x, y, y2} | rest_path], {final_x, final_y}, steps) do if final_x == x and final_y >= min(y, y2) and final_y <= max(y, y2) do steps + abs(final_y - y) else steps_to_reach_point(rest_path, {final_x, final_y}, steps + abs(y - y2)) end end end