119 lines
3.5 KiB
Elixir
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())
|