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

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()