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