Advent of Code 2024 - Day 8

27 lines

3 Likes

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 :stuck_out_tongue:

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:

1 Like
#!/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 :exploding_head:

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 
1 Like

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 :slight_smile: its under 1ms as well.

1 Like

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 :sweat_smile:.

1 Like

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.

4 Likes

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. :confused:

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 
1 Like

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 :slight_smile:

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 :wink: 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

2 Likes

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