AoC19/lib/day3/day3.ex

118 lines
3.1 KiB
Elixir

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