Not the most efficient way to do it (especially part 2) but it takes like 1ms to run anyway so it literally doesn’t matter ![]()
Now back to doing previous year puzzles that I never completed…
I used Enum.dedup/1 instead of Enum.uniq/1 when attempting to solve part 1. This mistake hid another bug, and so I got the correct result for the example but not for my input.
The following solution is refactored to share most of the code for the solution:
#!/usr/bin/env elixir
# AoC 2024. day 8.
###########################################################
# Setup
{coords2antennas, nrows, ncols} = File.stream!("../day08.txt")
|> Stream.with_index(1)
|> Enum.reduce({%{}, 0, 0}, fn {line, row}, {map, nrows, ncols} ->
line
|> String.trim_trailing()
|> String.to_charlist()
|> Enum.with_index(1)
|> Enum.reduce({map, nrows, ncols}, fn {c, col}, {map, nrows, ncols} ->
map = (if c != ?., do: Map.put(map, {row,col}, c), else: map)
{map, max(nrows, row), max(ncols, col)}
end)
end)
defmodule M do
# values become keys and vice-versa. values are stored in a list.
def inside_out(map) do
map |> Enum.reduce(%{}, fn {k,v}, res ->
Map.update(res, v, [k], fn lst -> [k|lst] end)
end)
end
def offset({r1,c1}, {r2,c2}), do: {r2-r1,c2-c1}
def add({r,c},{ro,co}), do: {r+ro,c+co}
def sub({r,c},{ro,co}), do: {r-ro,c-co}
def inside({r,c}, nrows, ncols), do: r >= 1 && r <= nrows && c >= 1 && c <= ncols
def pairs([]), do: []
def pairs([x | rest]), do: Enum.map(rest, fn y -> {x,y} end) ++ pairs(rest)
end
###########################################################
# Part 1
coords2antennas
|> M.inside_out()
|> Enum.reduce(MapSet.new(), fn {_antenna, coords}, set ->
pairs = M.pairs(coords)
Enum.reduce(pairs, set, fn {coord1, coord2}, set ->
offset = M.offset(coord1, coord2)
set
|> then(fn set ->
anti = M.sub(coord1, offset)
if M.inside(anti, nrows, ncols), do: MapSet.put(set, anti), else: set
end)
|> then(fn set ->
anti = M.add(coord2, offset)
if M.inside(anti, nrows, ncols), do: MapSet.put(set, anti), else: set
end)
end)
end)
|> tap(fn set -> IO.puts("Part 1. Number of antinodes: #{MapSet.size(set)}") end)
###########################################################
# Part 2
coords2antennas
|> M.inside_out()
|> Enum.reduce(MapSet.new(), fn {_antenna, coords}, set ->
pairs = M.pairs(coords)
Enum.reduce(pairs, set, fn {coord1, coord2}, set ->
offset = M.offset(coord1, coord2)
set = Stream.unfold(coord1, fn coord ->
anti = M.sub(coord, offset)
if M.inside(anti, nrows, ncols), do: {anti, anti}
end)
|> Enum.reduce(set, fn anti, set -> MapSet.put(set, anti) end)
Stream.unfold(coord2, fn coord ->
anti = M.add(coord, offset)
if M.inside(anti, nrows, ncols), do: {anti, anti}
end)
|> Enum.reduce(set, fn anti, set -> MapSet.put(set, anti) end)
|> MapSet.put(coord1)
|> MapSet.put(coord2)
end)
end)
|> tap(fn set -> IO.puts("Part 2. Number of antinodes: #{MapSet.size(set)}") end)
Fun day! Finally less bruteforce thinking!
Both solutions were pretty fast to run:
===== YEAR 2024 DAY 8 PART 1 =====
Result:
Took: 5ms
===== YEAR 2024 DAY 8 PART 2 =====
Result:
Took: 2ms
Solution (still working to generalize it):
defmodule AOC.Y2024.Day8 do
@moduledoc false
use AOC.Solution
@impl true
def load_data() do
Data.load_day_as_grid(2024, 8)
|> then(fn {grid, m, n} ->
antennas =
grid
|> Enum.filter(fn {_, v} -> v != "." end)
|> Enum.group_by(fn {_, v} -> v end, fn {k, _} -> k end)
{antennas, m, n}
end)
end
@impl true
def part_one({antennas, m, n}) do
solve(antennas, m, n, &find_antinodes/3)
end
@impl true
def part_two({antennas, m, n}) do
solve(antennas, m, n, &find_antinodes2/3)
end
defp solve(antennas, m, n, func) do
antennas
|> Enum.flat_map(fn {_, coords} ->
func.(coords, m, n)
end)
|> Enum.uniq()
|> Enum.count()
end
defp find_antinodes(coords, m, n) do
coords
|> Enum.with_index(1)
|> Enum.flat_map(fn {coord, i} ->
coords |> Enum.slice(i..-1//1) |> Enum.map(fn other -> {coord, other} end)
end)
|> Enum.flat_map(fn {{a, b}, {c, d}} ->
da = abs(a - c)
db = abs(b - d)
da_sign = div(a - c, abs(a - c))
db_sign = div(b - d, abs(b - d))
nx = {a + da * da_sign, b + db * db_sign}
ny = {c + da * -da_sign, d + db * -db_sign}
[nx, ny]
end)
|> Enum.filter(fn {a, b} ->
a in 0..(m - 1) and b in 0..(n - 1)
end)
end
defp find_antinodes2(coords, m, n) do
coords
|> Enum.with_index(1)
|> Enum.flat_map(fn {coord, i} ->
coords |> Enum.slice(i..-1//1) |> Enum.map(fn other -> {coord, other} end)
end)
|> Enum.flat_map(fn {x, y} ->
inf_antinodes(x, y, m, n)
end)
end
defp inf_antinodes({a, b}, {c, d}, m, n) do
da = abs(a - c)
db = abs(b - d)
da_sign = div(a - c, abs(a - c))
db_sign = div(b - d, abs(b - d))
Stream.iterate({a, b}, fn {x, y} ->
{x + da * da_sign, y + db * db_sign}
end)
|> Stream.take_while(fn {x, y} -> x in 0..(m - 1) and y in 0..(n - 1) end)
|> Stream.concat(
Stream.iterate({c, d}, fn {x, y} ->
{x + da * -da_sign, y + db * -db_sign}
end)
|> Stream.take_while(fn {x, y} -> x in 0..(m - 1) and y in 0..(n - 1) end)
)
|> Enum.to_list()
|> Enum.uniq()
end
end
I struggled way more than expected with my x and y calculations ![]()
Part 1
defmodule Advent.Y2024.Day08.Part1 do
@grid_size 49
def run(puzzle) do
puzzle
|> parse()
|> find_antinodes(&antinodes/2)
|> Enum.count()
end
def parse(puzzle) do
for {line, y} <- puzzle |> String.split("\n") |> Enum.with_index(), reduce: %{} do
acc ->
for {c, x} <- line |> String.graphemes() |> Enum.with_index(), c != ".", reduce: acc do
acc -> Map.update(acc, c, [{x, y}], fn pos -> [{x, y} | pos] end)
end
end
end
def find_antinodes(g, fun) do
for {_, pos} <- g, a1 <- pos, a2 <- pos, a1 != a2, n <- fun.(a1, a2), reduce: MapSet.new() do
nodes -> MapSet.put(nodes, n)
end
end
defp antinodes({x1, y1}, {x2, y2}) do
dx = abs(x1 - x2)
dy = abs(y1 - y2)
Enum.filter(
[
{if(x1 > x2, do: x1 + dx, else: x1 - dx), if(y1 > y2, do: y1 + dy, else: y1 - dy)},
{if(x2 > x1, do: x2 + dx, else: x2 - dx), if(y2 > y1, do: y2 + dy, else: y2 - dy)}
],
fn {x, y} -> x in 0..@grid_size and y in 0..@grid_size end
)
end
end
Part 2
defmodule Advent.Y2024.Day08.Part2 do
@grid_size 49
alias Advent.Y2024.Day08.Part1
def run(puzzle) do
puzzle
|> Part1.parse()
|> Part1.find_antinodes(&antinodes/2)
|> Enum.count()
end
defp antinodes({x1, y1}, {x2, y2}) do
dx = abs(x1 - x2)
dy = abs(y1 - y2)
[{x1, y1}, {x2, y2}] ++
in_direction({x1, y1}, {if(x1 > x2, do: dx, else: -dx), if(y1 > y2, do: dy, else: -dy)}) ++
in_direction({x2, y2}, {if(x2 > x1, do: dx, else: -dx), if(y2 > y1, do: dy, else: -dy)})
end
defp in_direction({x, y}, {dx, dy}, acc \\ []) do
{nx, ny} = {x + dx, y + dy}
if nx in 0..@grid_size and ny in 0..@grid_size do
in_direction({nx, ny}, {dx, dy}, [{nx, ny} | acc])
else
acc
end
end
end
Pretty tame after Friday’s loop detection, I was expecting worse for Sunday (although I took a break yesterday so I’m not sure how that was).
Each part completes in under a millisecond.
def calc_resonant_harmonics({{x_a, y_a}, {x_b, y_b}}, max_x, max_y) do
dx = x_a - x_b
dy = y_a - y_b
[{x_a, y_a}, {x_b, y_b}] ++
resonate(x_a, y_a, dx, dy, max_x, max_y) ++
resonate(x_b, y_b, dx * -1, dy * -1, max_x, max_y)
end
def resonate(x, y, dx, dy, max_x, max_y, multiplier \\ 1) do
next_x = x + dx * multiplier
next_y = y + dy * multiplier
if next_x < 0 or next_x >= max_x or next_y < 0 or next_y >= max_y do
[]
else
[{next_x, next_y} | resonate(x, y, dx, dy, max_x, max_y, multiplier + 1)]
end
end
Sundays are supposed to be harder but today was quite easy:)
defmodule AdventOfCode.Solutions.Y24.Day08 do
alias AdventOfCode.Grid
alias AoC.Input
def parse(input, _part) do
{_grid, _bounds} =
input
|> Input.stream!()
|> Grid.parse_lines(fn
_, ?. -> :ignore
_, ?\n -> raise "parses new line"
_, c -> {:ok, c}
end)
end
def part_one({grid, bounds}) do
for({xy_l, l} <- grid, {xy_r, r} <- grid, l == r, xy_l < xy_r, do: antinodes_p1(xy_l, xy_r))
|> :lists.flatten()
|> Enum.uniq()
|> Enum.filter(&in_bounds?(&1, bounds))
|> length()
end
defp antinodes_p1({xl, yl}, {xr, yr}) do
x_diff = xr - xl
y_diff = yr - yl
[
# Lower node
{xl - x_diff, yl - y_diff},
# Higher node
{xr + x_diff, yr + y_diff}
]
end
defp in_bounds?({x, y}, {xa, xo, ya, yo}) do
x >= xa and x <= xo and
y >= ya and y <= yo
end
def part_two({grid, bounds}) do
for(
{xy_l, l} <- grid,
{xy_r, r} <- grid,
l == r,
xy_l < xy_r,
do: antinodes_p2(xy_l, xy_r, bounds)
)
|> :lists.flatten()
|> Enum.uniq()
|> length()
end
defp antinodes_p2({xl, yl}, {xr, yr}, bounds) do
x_diff = xr - xl
y_diff = yr - yl
higher =
{xr, yr}
|> Stream.iterate(fn {x, y} -> {x + x_diff, y + y_diff} end)
|> Enum.take_while(&in_bounds?(&1, bounds))
lower =
{xl, yl}
|> Stream.iterate(fn {x, y} -> {x - x_diff, y - y_diff} end)
|> Enum.take_while(&in_bounds?(&1, bounds))
[higher, lower]
end
end
No optimization at all
its under 1ms as well.
My solution today:
That’s a clever way of iterating through the grid.
I did a Map.values() into a MapSet to get possible values, then did a Map.filter() on each to get the matching tower locations, which seems messy in comparison
.
The grid ones are tricky to golf.
LOC: 23
defmodule Aoc2024.Day08 do
import Enum
def part1(file), do: main(file, 1..1)
def part2(file), do: main(file)
def main(file, range \\ nil) do
{grid, n} = file_to_charmap_grid(file)
for {{x1, y1}, z1} <- grid, {{x2, y2}, z2} <- grid, z1 == z2, z1 != ?., x1 < x2 do
map(range || -n..n, fn m -> {m * (x2 - x1), m * (y2 - y1)} end)
|> flat_map(fn {dx, dy} -> [[x1 - dx, y1 - dy], [x2 + dx, y2 + dy]] end)
|> filter(fn coor -> all?(coor, &(&1 in 0..(n - 1))) end)
end
|> reduce(MapSet.new(), &MapSet.union(&2, MapSet.new(&1)))
|> MapSet.size()
end
def file_to_charmap_grid(f) do
r = f |> File.read!() |> String.trim() |> String.split("\n") |> map(&String.to_charlist/1)
{for({s, i} <- with_index(r), {x, j} <- with_index(s), into: %{}, do: {{i, j}, x}), length(r)}
end
end
This one made me wish I could reach for the extra comprehension powers hinted at in the for let proposal.
Here is mine:
defmodule Aoc2024.Solutions.Y24.Day08 do
alias AoC.Input
def parse(input, _part) do
stream = Input.stream!(input, trim: true)
antennas =
stream
|> Stream.with_index()
|> Enum.reduce(%{}, fn {line, x}, antennas ->
line
|> String.to_charlist()
|> Stream.with_index()
|> Enum.reduce(antennas, fn
{?., _}, antennas -> antennas
{symbol, y}, antennas -> Map.update(antennas, symbol, [{x, y}], &[{x, y} | &1])
end)
end)
|> Map.values()
row_end = Enum.count(stream) - 1
col_end = length(String.to_charlist(Enum.at(stream, 0))) - 1
{antennas, row_end, col_end}
end
def part_one({antennas, row_end, col_end}) do
antennas
|> generate_combinations()
|> Enum.reduce(MapSet.new(), fn combinations, antinodes ->
Enum.reduce(combinations, antinodes, fn {{x1, y1}, {x2, y2}}, antinodes ->
antinodes
|> add_antinodes(x1, y1, x2 - x1, y2 - y1, row_end, col_end)
|> add_antinodes(x2, y2, x1 - x2, y1 - y2, row_end, col_end)
end)
end)
|> Enum.count()
end
def part_two({antennas, row_end, col_end}) do
antennas
|> generate_combinations()
|> Enum.reduce(MapSet.new(List.flatten(antennas)), fn combinations, antinodes ->
Enum.reduce(combinations, antinodes, fn {{x1, y1}, {x2, y2}}, antinodes ->
antinodes
|> add_antinodes_recursive(x1, y1, x2 - x1, y2 - y1, row_end, col_end)
|> add_antinodes_recursive(x2, y2, x1 - x2, y1 - y2, row_end, col_end)
end)
end)
|> Enum.count()
end
defp generate_combinations(antennas) do
for antenna <- antennas do
for {a, x} <- Enum.with_index(antenna),
{b, y} <- Enum.with_index(antenna),
x < y,
do: {a, b}
end
end
defp add_antinodes(antinodes, x_original, y_original, dx, dy, row_end, col_end) do
{x, y} = {x_original - dx, y_original - dy}
if x >= 0 and x <= row_end and y >= 0 and y <= col_end do
MapSet.put(antinodes, {x, y})
else
antinodes
end
end
defp add_antinodes_recursive(antinodes, x_original, y_original, dx, dy, row_end, col_end) do
{x, y} = {x_original - dx, y_original - dy}
if x >= 0 and x <= row_end and y >= 0 and y <= col_end do
antinodes = MapSet.put(antinodes, {x, y})
add_antinodes_recursive(antinodes, x, y, dx, dy, row_end, col_end)
else
antinodes
end
end
end
Can somebody explain to me why is part 2 faster although it’s the same kind of function for calculation as in part 1 only recursive. ![]()
Solution for 2024 day 8
part_one: 376 in 8.85ms
part_two: 1352 in 1.22ms
@ken-kost your part 1 was faster for me. I’m not using aoc though so that might have something to do with it?
I ran:
Benchee.run(%{
"part1" => fn ->
"inputs/day08/input2.txt"
|> Aoc2024.Solutions.Y24.Day08.parse(1)
|> Aoc2024.Solutions.Y24.Day08.part_one()
end,
"part2" => fn ->
"inputs/day08/input2.txt"
|> Aoc2024.Solutions.Y24.Day08.parse(2)
|> Aoc2024.Solutions.Y24.Day08.part_two()
end
})
After replacing the Aoc.Input part with:
# Before
stream = Input.stream!(input, trim: true)
# After
stream =
input
|> File.stream!()
|> Stream.map(&String.trim/1)
|> Stream.reject(&(&1 == ""))
and I got:
Name ips average deviation median 99th %
part1 1.25 K 0.80 ms ±39.13% 0.75 ms 1.43 ms
part2 0.99 K 1.01 ms ±19.68% 0.95 ms 1.70 ms
Today’s problem is more like a reading comprehension problem than an algorithm problem, especially for a non English native speaker. Fortunately, I was a student of physics when I was at university, and this problem brings me back to those days.
I really liked how this worked out in the end <3
and i like to use structs as you can see. It saves me from hidden issues like calling non existing keys on maps … and getting nil back …
i just changed the existing part1 soluition for part2 as always
this was a fun excercice ![]()
btw i see most of you try to make it as few lines of possible and as fast as possible … but i just sincerely enjoy doing the exercises
I wonder what i will do rest of the year now, after december … excercism ?
There’s always previous year puzzles to do, if you haven’t done those!
https://everybody.codes/ is anothe one that popped up last month for more puzzley goodness
Catching back up.
Here’s my pt 1:
#!/usr/bin/env elixir
defmodule Day8.Part1 do
@open_space ~c"." |> List.first()
defp parse(str) do
[row | _rows] =
rows =
str
|> String.split("\n")
|> Enum.map(&to_charlist/1)
height = Enum.count(rows)
length = Enum.count(row)
antennae =
for y <- 0..(height - 1), x <- 0..(length - 1) do
{x, y}
end
|> Enum.reduce(
%{},
fn {x, y} = pos, acc ->
c = rows |> Enum.at(y) |> Enum.at(x)
if c == @open_space,
do: acc,
else: acc |> Map.update("#{c}", [pos], fn coordinates -> [pos | coordinates] end)
end
)
%{
antennae: antennae,
height: height,
length: length
}
end
defp on_board?(height, length, {x, y}) when x < 0 or x >= length or y < 0 or y >= height,
do: false
defp on_board?(_height, _length, _pair), do: true
# Produce a List of coordinate pairs representing antinodes
defp pairwise_antinodes_on_board(height, length, antennae) do
Stream.unfold(
antennae,
fn
[] -> nil
[_h | t] = antennae -> {antennae, t}
end
)
|> Enum.map(fn antennae -> pairwise_antinodes_on_board_for_antenna(antennae, height, length) end)
|> List.flatten()
end
defp pairwise_antinodes_on_board_for_antenna(
[_antenna],
_height,
_length
),
do: []
defp pairwise_antinodes_on_board_for_antenna(
[{x_1, y_1} = antenna_1, {x_2, y_2} = _antenna_2 | antennae],
height,
length
)
when abs(x_1 - x_2) > length / 2 or abs(y_1 - y_2) > height / 2,
do: pairwise_antinodes_on_board_for_antenna([antenna_1 | antennae], height, length)
defp pairwise_antinodes_on_board_for_antenna(
[{x_1, y_1} = antenna_1, {x_2, y_2} = _antenna_2 | antennae],
height,
length
) do
south_ish? = y_2 - y_1 < 0
west_ish? = x_2 - x_1 < 0
{x_min, x_max} = Enum.min_max([x_1, x_2])
delta_x = abs(x_1 - x_2)
{y_min, y_max} = Enum.min_max([y_1, y_2])
delta_y = abs(y_1 - y_2)
{antinode_1_x, antinode_2_x} =
if west_ish? do
{x_max + delta_x, x_min - delta_x}
else
{x_min - delta_x, x_max + delta_x}
end
{antinode_1_y, antinode_2_y} =
if south_ish? do
{y_max + delta_y, y_min - delta_y}
else
{y_min - delta_y, y_max + delta_y}
end
[
[{antinode_1_x, antinode_1_y}, {antinode_2_x, antinode_2_y}]
|> Enum.filter(&on_board?(height, length, &1))
| pairwise_antinodes_on_board_for_antenna([antenna_1 | antennae], height, length)
]
|> List.flatten()
end
defp count_antinodes(%{
antennae: antennae,
height: height,
length: length
}) do
antennae
|> Map.keys()
|> Enum.reduce(
MapSet.new(),
fn frequency, antinodes ->
pairwise_antinodes_on_board(height, length, antennae[frequency])
|> Enum.reduce(antinodes, &MapSet.put(&2, &1))
end
)
|> Enum.count()
end
def solve() do
File.read!("08/input.txt")
|> parse()
|> count_antinodes()
|> IO.puts()
end
end
Day8.Part1.solve()
And part 2:
#!/usr/bin/env elixir
defmodule Day8.Part1 do
@open_space ~c"." |> List.first()
defp parse(str) do
[row | _rows] =
rows =
str
|> String.split("\n")
|> Enum.map(&to_charlist/1)
height = Enum.count(rows)
length = Enum.count(row)
antennae =
for y <- 0..(height - 1), x <- 0..(length - 1) do
{x, y}
end
|> Enum.reduce(
%{},
fn {x, y} = pos, acc ->
c = rows |> Enum.at(y) |> Enum.at(x)
if c == @open_space,
do: acc,
else: acc |> Map.update("#{c}", [pos], fn coordinates -> [pos | coordinates] end)
end
)
%{
antennae: antennae,
height: height,
length: length
}
end
defp pairwise_antinodes_on_board(height, length, antennae) do
Stream.unfold(
antennae,
fn
[] -> nil
[_h | t] = antennae -> {antennae, t}
end
)
|> Enum.map(fn antennae -> pairwise_antinodes_on_board_for_antenna(antennae, height, length) end)
|> List.flatten()
end
defp pairwise_antinodes_on_board_for_antenna(
[_antenna],
_height,
_length
),
do: []
defp pairwise_antinodes_on_board_for_antenna(
[{x_1, y_1} = antenna_1, {x_2, y_2} = _antenna_2 | antennae],
height,
length
) do
south_ish? = y_2 - y_1 < 0
west_ish? = x_2 - x_1 < 0
{x_min, x_max} = Enum.min_max([x_1, x_2])
delta_x = x_max - x_min
{y_min, y_max} = Enum.min_max([y_1, y_2])
delta_y = y_max - y_min
north_eastern = [x_max..(length - 1)//delta_x, y_max..(height - 1)//delta_y]
south_western = [x_min..0//-delta_x, y_min..0//-delta_y]
south_eastern = [x_max..(length - 1)//delta_x, y_min..0//-delta_y]
north_western = [x_min..0//-delta_x, y_max..(height - 1)//delta_y]
[antinodes_1, antinodes_2] =
if west_ish? and south_ish? or (not west_ish? and not south_ish?) do
[south_western, north_eastern]
else
[south_eastern, north_western]
end
|> Enum.map(
fn ranges ->
ranges
|> Enum.map(&Enum.to_list(&1))
|> Enum.zip()
end
)
[
antinodes_1 | [antinodes_2 | pairwise_antinodes_on_board_for_antenna([antenna_1 | antennae], height, length)]
]
|> List.flatten()
end
defp count_antinodes(%{
antennae: antennae,
height: height,
length: length
}) do
antennae
|> Map.keys()
|> Enum.reduce(
MapSet.new(),
fn frequency, antinodes ->
pairwise_antinodes_on_board(height, length, antennae[frequency])
|> Enum.reduce(antinodes, &MapSet.put(&2, &1))
end
)
|> Enum.count()
end
def solve() do
File.read!("08/input.txt")
|> parse()
|> count_antinodes()
|> IO.puts()
end
end
Day8.Part1.solve()
A bit of a crude solution, particularly when it came to part2. Copy/paste/modify got the job done without bothering to refactor anything. Trying to get caught up.
defmodule Aoc2024.Day8 do
@moduledoc false
defp coord(i, width) do
{Integer.mod(i, width), Integer.floor_div(i, width)}
end
defp get_bounds(data) do
map =
data
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, "", trim: true))
{length(List.first(map)) - 1, length(map) - 1}
end
defp get_input(data) do
map =
data
|> String.split("\n", trim: true)
|> Enum.map(&String.split(&1, "", trim: true))
width = length(List.first(map))
data
|> String.replace("\n", "")
|> String.split("", trim: true)
|> Enum.with_index()
|> Enum.filter(fn {c, _} -> c != "." end)
|> Enum.reduce(Map.new(), fn {v, i}, antennas ->
Map.update(antennas, v, [coord(i, width)], fn s ->
[coord(i, width) | s]
end)
end)
end
# Generate the pair of antinodes for a given pair of nodes.
defp antinode(a = {ax, ay}, b = {bx, by}) do
dx = bx - ax
dy = by - ay
if a != b, do: [{ax - dx, ay - dy}, {bx + dx, by + dy}], else: []
end
# Generate all antinodes including those out of bounds and duplicates.
defp find_antinodes(antennas) do
for locations <- Map.values(antennas) do
for a <- locations, b <- locations do
antinode(a, b)
end
end
end
defp in_bounds?({x, y}, {last_x, last_y}) do
x >= 0 and x <= last_x and y >= 0 and y <= last_y
end
def part1(file) do
data = File.read!(file)
get_input(data)
|> find_antinodes()
|> List.flatten()
|> Enum.uniq()
|> Enum.filter(&in_bounds?(&1, get_bounds(data)))
|> Enum.count()
end
# Generate all antinodes for a pair of antennas radiating outward.
defp antinode2(a = {ax, ay}, b = {bx, by}, mult \\ 1) do
# Cheating a bit here instead of passing in the maximum position indexes.
size = 49
dx = (bx - ax) * mult
dy = (by - ay) * mult
if a == b do
[a]
else
n = {ax - dx, ay - dy}
m = {bx + dx, by + dy}
if not in_bounds?(n, {size, size}) and not in_bounds?(m, {size, size}) do
[]
else
[n | [m | antinode2(a, b, mult + 1)]]
end
end
end
defp find_antinodes2(antennas) do
for locations <- Map.values(antennas) do
for a <- locations, b <- locations do
antinode2(a, b)
end
end
end
def part2(file) do
data = File.read!(file)
get_input(data)
|> find_antinodes2()
|> List.flatten()
|> Enum.uniq()
|> Enum.filter(&in_bounds?(&1, get_bounds(data)))
|> Enum.sort()
|> Enum.count()
end
end






















