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