You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
130 lines
3.9 KiB
Elixir
130 lines
3.9 KiB
Elixir
defmodule Day8 do
|
|
def run do
|
|
map =
|
|
File.read!("data.txt")
|
|
|> String.split("\n")
|
|
|> Enum.drop(-1)
|
|
|> Enum.map(&String.graphemes(&1))
|
|
|
|
height = length(map)
|
|
width = length(hd(map))
|
|
|
|
dbg(map)
|
|
|
|
# generate list of antennas
|
|
# flatten the map, add indices, if char isn't a dot, grab width and height, stick them in a map
|
|
antennas =
|
|
Enum.concat(map)
|
|
|> Enum.with_index()
|
|
|> Enum.reduce(%{}, fn {char, idx}, acc ->
|
|
if char != "." do
|
|
location = {div(idx, height), rem(idx, width)}
|
|
Map.update(acc, char, [location], fn list -> [location | list] end)
|
|
else
|
|
acc
|
|
end
|
|
end)
|
|
|
|
dbg(antennas, charlists: :as_lists)
|
|
|
|
# generate all possible pairs of locations for this antenna
|
|
pairs =
|
|
Enum.map(antennas, fn {char, locations} ->
|
|
pairs =
|
|
for x <- 0..(length(locations) - 2),
|
|
y <- (x + 1)..(length(locations) - 1),
|
|
x != y,
|
|
do: {Enum.at(locations, x), Enum.at(locations, y)}
|
|
|
|
{char, pairs}
|
|
end)
|
|
|
|
dbg(pairs, charlists: :as_lists)
|
|
|
|
# find antinodes
|
|
antinodes =
|
|
Enum.reduce(pairs, MapSet.new(), fn {char, nodes}, acc ->
|
|
Enum.reduce(nodes, acc, fn {{x1, y1}, {x2, y2}}, acc2 ->
|
|
{dx, dy} = {x2 - x1, y2 - y1}
|
|
|
|
new_points =
|
|
if (dx > 0 and dy > 0) or (dx < 0 and dy < 0) do
|
|
# slope \
|
|
[
|
|
{min(x1, x2) - abs(dx), min(y1, y2) - abs(dy)},
|
|
{max(x1, x2) + abs(dx), max(y1, y2) + abs(dy)}
|
|
]
|
|
else
|
|
# slope /
|
|
[
|
|
{max(x1, x2) + abs(dx), min(y1, y2) - abs(dy)},
|
|
{min(x1, x2) - abs(dx), max(y1, y2) + abs(dy)}
|
|
]
|
|
end
|
|
# ensure in bounds
|
|
|> Enum.filter(fn {x, y} -> x >= 0 and y >= 0 and x < height and y < width end)
|
|
|
|
# add each new point to the MapSet
|
|
Enum.reduce(new_points, acc2, fn point, acc3 ->
|
|
MapSet.put(acc3, point)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
dbg(MapSet.size(antinodes))
|
|
|
|
antinodes2 =
|
|
Enum.reduce(pairs, MapSet.new(), fn {char, nodes}, acc ->
|
|
Enum.reduce(nodes, acc, fn {{x1, y1}, {x2, y2}}, acc2 ->
|
|
{dx, dy} = {x2 - x1, y2 - y1}
|
|
|
|
# generate points along these lines until out of bounds
|
|
# roughly how many times do we need to loop? we'll stop early if both points are out of bounds
|
|
loops = trunc(min(abs(width / dy), abs(height / dx)))
|
|
|
|
new_points =
|
|
Enum.reduce_while(1..(loops + 1), [], fn idx, acc ->
|
|
points =
|
|
if (dx > 0 and dy > 0) or (dx < 0 and dy < 0) do
|
|
# slope \
|
|
[
|
|
{min(x1, x2) - abs(dx) * idx, min(y1, y2) - abs(dy) * idx},
|
|
{max(x1, x2) + abs(dx) * idx, max(y1, y2) + abs(dy) * idx}
|
|
]
|
|
else
|
|
# slope /
|
|
[
|
|
{max(x1, x2) + abs(dx) * idx, min(y1, y2) - abs(dy) * idx},
|
|
{min(x1, x2) - abs(dx) * idx, max(y1, y2) + abs(dy) * idx}
|
|
]
|
|
end
|
|
|> Enum.filter(fn {x, y} -> x >= 0 and y >= 0 and x < height and y < width end)
|
|
|
|
if length(points) == 0 do
|
|
{:halt, points ++ acc}
|
|
else
|
|
{:cont, points ++ acc}
|
|
end
|
|
end)
|
|
|
|
# add each new point to the MapSet
|
|
Enum.reduce(new_points, acc2, fn point, acc3 ->
|
|
MapSet.put(acc3, point)
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
# add all the original antennae to the MapSet
|
|
part2 =
|
|
Enum.reduce(antennas, antinodes2, fn {char, points}, acc ->
|
|
Enum.reduce(points, acc, fn point, acc2 ->
|
|
MapSet.put(acc2, point)
|
|
end)
|
|
end)
|
|
|
|
dbg(MapSet.size(part2))
|
|
end
|
|
end
|
|
|
|
Day8.run()
|