AoC18/lib/day6/day6.ex

136 lines
2.8 KiB
Elixir

defmodule Day6 do
@doc """
Parses a node from a string.
## Examples
iex> Day6.parse_node("1, 6")
{1, 6}
"""
def parse_node(s) do
[x, y] = String.split(s, ", ")
{
x |> String.to_integer(),
y |> String.to_integer()
}
end
@doc """
Distance between two coordinates.
## Examples
iex> Day6.distance({0, 0}, {3, 7})
10
"""
def distance({x1, y1}, {x2, y2}) do
abs(x2 - x1) + abs(y2 - y1)
end
@doc """
## Examples
iex> Day6.nearest_node({0, 0}, [{-1, -1}, {1, 2}, {0, 1}, {3, 4}])
{0, 1}
iex> Day6.nearest_node({0, 0}, [{0, -1}, {1, 0}])
nil
"""
def nearest_node(point, all) do
nearest =
all
|> Enum.map(fn other -> {other, distance(point, other)} end)
{min_pt, min_dist} = nearest |> Enum.min_by(fn {_pt, dist} -> dist end)
case Enum.count(nearest, fn {_pt, dist} -> dist == min_dist end) do
1 -> min_pt
_ -> nil
end
end
def largest_region(nodes, grid_size \\ 1000) do
{areas, infinite_area_nodes} =
0..grid_size
|> Enum.reduce({%{}, []}, fn y, acc ->
0..grid_size
|> Enum.reduce(acc, fn x, {areas, infinite_area_nodes} ->
pt = {x, y}
case nearest_node(pt, nodes) do
nil ->
{areas, infinite_area_nodes}
nearest ->
if x == 0 or y == 0 or x == grid_size or y == grid_size do
{
areas,
[nearest | infinite_area_nodes]
}
else
{
Map.update(areas, nearest, 1, &(&1 + 1)),
infinite_area_nodes
}
end
end
end)
end)
areas
|> Enum.reject(fn {node, _area} -> node in infinite_area_nodes end)
|> Enum.max_by(fn {_node, area} -> area end)
end
@doc """
Sums the distance between the point and each of the nodes.
## Examples
iex> Day6.total_distances({0, 0}, [{0, 2}, {-2, 0}, {3, 5}])
12
"""
def total_distances(pt, nodes) do
nodes
|> Enum.map(fn node -> distance(pt, node) end)
|> Enum.sum()
end
def region_near_most(nodes, grid_size \\ 1000, max_dist \\ 10000) do
0..grid_size
|> Enum.reduce([], fn y, acc ->
0..grid_size
|> Enum.reduce(acc, fn x, acc ->
pt = {x, y}
cond do
total_distances(pt, nodes) < max_dist ->
[pt | acc]
true ->
acc
end
end)
end)
end
def parse_input() do
File.read!("lib/day6/input.txt")
|> String.split("\n", trim: true)
|> Enum.map(&parse_node/1)
end
def part1() do
parse_input()
|> largest_region()
end
def part2() do
parse_input()
|> region_near_most()
|> Enum.count()
end
end