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