AdventOfCode/2018/day6/day6.exs

119 lines
3.5 KiB
Elixir

defmodule Area do
defstruct [:min_x, :min_y, :max_x, :max_y]
def all_coords(area) do
for x <- area.min_x..area.max_x, y <- area.min_y..area.max_y, do: {x, y}
end
def edge_coords(area) do
top_and_bottom = for x <- area.min_x..area.max_x, y <- [area.min_y, area.max_y], do: {x, y}
sides = for x <- [area.min_x, area.max_x], y <- (area.min_y + 1)..(area.max_y - 1), do: {x, y}
top_and_bottom ++ sides
end
end
defmodule Day6 do
def parse_danger_pt_list do
File.stream!("input")
|> Stream.map(&String.trim/1)
|> Stream.map(fn line -> String.split(line, ", ") |> Enum.map(&String.to_integer/1) end)
|> Enum.map(fn [x, y] -> {x, y} end)
end
def bounding_box(coords) do
{max_x, max_y} =
Enum.reduce(coords, {0, 0}, fn {x, y}, {max_x, max_y} ->
{max(x, max_x), max(y, max_y)}
end)
{min_x, min_y} =
Enum.reduce(coords, {max_x, max_y}, fn {x, y}, {min_x, min_y} ->
{min(x, min_x), min(y, min_y)}
end)
%Area{min_x: min_x, min_y: min_y, max_x: max_x, max_y: max_y}
end
def manhattan_distance({x1, y1}, {x2, y2}), do: abs(x2 - x1) + abs(y2 - y1)
# %{ grid_coordinate => {distance, dangerous_point} }
def proximity_grid_for(danger_pt, field) do
Area.all_coords(field)
|> Enum.map(fn coord -> {coord, {manhattan_distance(coord, danger_pt), [danger_pt]}} end)
|> Map.new()
end
def proximity_grid(danger_pts, field) do
empty_grid = Enum.map(danger_pts, fn danger_pt -> {danger_pt, {nil, []}} end) |> Map.new()
Enum.reduce(danger_pts, empty_grid, fn danger_pt, merged_grid ->
Map.merge(proximity_grid_for(danger_pt, field), merged_grid, fn
_grid_coord, {dist, [danger_pt]}, {_dist, []} ->
{dist, [danger_pt]}
_grid_coord, {dist1, [danger_pt]}, {dist2, _danger_pts} when dist1 < dist2 ->
{dist1, [danger_pt]}
_grid_coord, {dist1, _danger_pt}, {dist2, danger_pts} when dist2 < dist1 ->
{dist2, danger_pts}
_grid_coord, {dist1, [danger_pt]}, {dist2, danger_pts} when dist1 === dist2 ->
{dist1, [danger_pt | danger_pts]}
end)
end)
end
def find_infinite_danger_pts(proximity_grid, field) do
Enum.reduce(Area.edge_coords(field), MapSet.new(), fn coord, infinite_pts ->
with {_dist, [danger_pt]} <- proximity_grid[coord] do
MapSet.put(infinite_pts, danger_pt)
else
_ -> infinite_pts
end
end)
end
def remove_shared_and_infinite(proximity_grid, infinite_pts) do
proximity_grid
|> Enum.reject(fn {_, {_dist, danger_pts}} -> length(danger_pts) > 1 end)
|> Enum.reject(fn {_, {_dist, [danger_pt]}} -> MapSet.member?(infinite_pts, danger_pt) end)
|> Map.new()
end
def to_areas(proximity_grid) do
Enum.reduce(proximity_grid, %{}, fn {_coord, {_dist, [danger_pt]}}, areas ->
Map.update(areas, danger_pt, 1, &(&1 + 1))
end)
end
def part1 do
danger_pts = parse_danger_pt_list()
field = bounding_box(danger_pts)
proximity_grid = proximity_grid(danger_pts, field)
infinite_pts = find_infinite_danger_pts(proximity_grid, field)
remove_shared_and_infinite(proximity_grid, infinite_pts)
|> to_areas()
|> Map.values()
|> Enum.max()
end
def part2 do
danger_pts = parse_danger_pt_list()
danger_pts
|> bounding_box()
|> Area.all_coords()
|> Enum.map(fn coord ->
Enum.reduce(danger_pts, 0, fn danger_pt, dist ->
dist + manhattan_distance(coord, danger_pt)
end)
end)
|> Enum.filter(&(&1 < 10_000))
|> length()
end
end
IO.puts(Day6.part1())
IO.puts(Day6.part2())