Day 8: Resonant Collinearity
Mix.install([
{:kino, "~> 0.14.2"}
])
Input
input = Kino.Input.textarea("Please, paste your input:")
defmodule Day8Shared do
def parse(input) do
input
|> Kino.Input.read()
|> String.split("\n")
|> Enum.with_index()
|> Enum.reduce(%{}, fn {line, y}, map ->
line
|> String.codepoints()
|> Enum.with_index()
|> Enum.reduce(map, fn {char, x}, map ->
map
|> Map.put({x, y}, char)
|> Map.update(:max_x, x, &max(&1, x))
|> Map.update(:max_y, y, &max(&1, y))
end)
end)
end
# Generates list of unique pairs for the given input. Example:
#
# - for input `[:a, :b, :c]`
# - we will get [[:a, :b], [:a, :c], [:b, :c]] (and skip like [:c, :a] or [:c, :b])
def unique_pairs(coords) do
for i <- 0..(length(coords) - 2),
j <- (i + 1)..(length(coords) - 1) do
{Enum.at(coords, i), Enum.at(coords, j)}
end
end
end
# Day8Shared.parse(input)
Part 1
defmodule Day8Part1 do
def process(%{max_x: max_x, max_y: max_y} = map) do
antennas =
map
|> Enum.filter(&(is_binary(elem(&1, 1)) && elem(&1, 1) != "."))
|> Enum.group_by(&elem(&1, 1), &elem(&1, 0))
Enum.map(antennas, fn {_label, coords} ->
coords
|> Day8Shared.unique_pairs()
|> Enum.map(&antinodes/1)
end)
|> List.flatten()
|> Enum.reject(fn {x, y} -> x < 0 or x > max_x or y < 0 or y > max_y end)
|> Enum.uniq()
|> Enum.count()
end
def antinodes({{x1, y1}, {x2, y2}}) do
[
{x2 + (x2 - x1), y2 + (y2 - y1)},
{x1 - (x2 - x1), y1 - (y2 - y1)}
]
end
end
input |> Day8Shared.parse() |> Day8Part1.process()
# 285 is the right answer
Part 2
defmodule Day8Part2 do
def process(%{max_x: max_x, max_y: max_y} = map) do
antennas =
map
|> Enum.filter(&(is_binary(elem(&1, 1)) && elem(&1, 1) != "."))
|> Enum.group_by(&elem(&1, 1), &elem(&1, 0))
Enum.map(antennas, fn {_label, coords} ->
coords
|> Day8Shared.unique_pairs()
|> Enum.map(&antinodes(&1, max_x, max_y))
end)
|> List.flatten()
|> Enum.uniq()
|> Enum.count()
end
def antinodes({{x1, y1}, {x2, y2}}, max_x, max_y) do
delta_x = x2 - x1
delta_y = y2 - y1
[
all_nodes({x1, y1}, :dec, delta_x, delta_y, max_x, max_y, 0, []),
all_nodes({x2, y2}, :inc, delta_x, delta_y, max_x, max_y, 0, [])
]
end
def all_nodes({x, y}, op, delta_x, delta_y, max_x, max_y, distance_n, nodes) do
{new_x, new_y} =
new_node =
case op do
:inc -> {x + delta_x * distance_n, y + delta_y * distance_n}
:dec -> {x - delta_x * distance_n, y - delta_y * distance_n}
end
if new_x < 0 or new_x > max_x or new_y < 0 or new_y > max_y do
nodes
else
all_nodes({x, y}, op, delta_x, delta_y, max_x, max_y, distance_n + 1, [new_node | nodes])
end
end
end
input |> Day8Shared.parse() |> Day8Part2.process()
# 944 is the right answer