— Day 8: Resonant Collinearity —
Mix.install([{:kino_aoc, "~> 0.1"}, {:kino_explorer, "~> 0.1.20"}])
Setup
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2024", "8", System.fetch_env!("LB_AOC_SESSION_COOKIE"))
input = Kino.Input.textarea("input")
defmodule ResonantCollinearity do
def parse(input) do
input
|> String.split("\n", trim: true)
|> Enum.map(&String.graphemes/1)
|> Enum.with_index()
|> Enum.reduce(%{}, fn {row, row_i}, acc ->
for {point, col_i} <- Enum.with_index(row), point != ".", reduce: acc do
acc -> Map.put(acc, {row_i, col_i}, point)
end
end)
end
def max_bounds(input) do
input
|> String.split("\n", trim: true)
|> length()
|> Kernel.-(1)
end
def maybe_add_to_set(set, node, max) do
if in_bounds?(node, max), do: MapSet.put(set, node), else: set
end
def in_bounds?({row, col}, max) do
row in 0..max and col in 0..max
end
def map_tl(map) do
with [hd | _tl] <- Map.keys(map) do
Map.delete(map, hd)
end
end
# a bottom right b top left
def nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff}) when ra >= rb and ca >= cb do
[{ra + r_diff, ca + c_diff}, {rb - r_diff, cb - c_diff}]
end
# a bottom left b top right
def nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff}) when ra >= rb and ca <= cb do
[{ra + r_diff, ca - c_diff}, {rb - r_diff, cb + c_diff}]
end
# a top right b bottom left
def nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff}) when ra <= rb and ca >= cb do
[{ra - r_diff, ca + c_diff}, {rb + r_diff, cb - c_diff}]
end
# a top left b bottom right
def nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff}) when ra <= rb and ca <= cb do
[{ra - r_diff, ca - c_diff}, {rb + r_diff, cb + c_diff}]
end
end
Part 1
import ResonantCollinearity
test = Kino.Input.read(input)
grid = parse(puzzle_input)
max = max_bounds(puzzle_input)
for {{ra, ca}, pa} <- grid, reduce: MapSet.new() do
set ->
for {{rb, cb}, pb} <- grid, pa == pb, {ra, ca} != {rb, cb}, reduce: set do
set ->
r_diff = abs(ra - rb)
c_diff = abs(ca - cb)
[a_node, b_node] = nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff})
set
|> maybe_add_to_set(a_node, max)
|> maybe_add_to_set(b_node, max)
end
end
|> MapSet.size()
Part 2
import ResonantCollinearity
test = Kino.Input.read(input)
grid = parse(puzzle_input)
max = max_bounds(puzzle_input)
for {{ra, ca}, pa} <- grid, reduce: MapSet.new() do
set ->
for {{rb, cb}, pb} <- grid, pa == pb, {ra, ca} != {rb, cb}, reduce: set do
set ->
r_diff = abs(ra - rb)
c_diff = abs(ca - cb)
start_nodes = nodes([{ra, ca}, {rb, cb}], {r_diff, c_diff})
set =
Enum.reduce(start_nodes ++ [{ra, ca}, {rb, cb}], set, &maybe_add_to_set(&2, &1, max))
Enum.reduce(0..49, {start_nodes, set}, fn _i, {prev_nodes, set} ->
new_nodes = nodes(prev_nodes, {r_diff, c_diff})
{new_nodes, Enum.reduce(new_nodes, set, &maybe_add_to_set(&2, &1, max))}
end)
|> elem(1)
end
end
|> MapSet.size()