diff --git a/2024/README.md b/2024/README.md index b372483..e43dca3 100644 --- a/2024/README.md +++ b/2024/README.md @@ -3,7 +3,7 @@ | S | M | T | W | T | F | S | | :-: | :-: | :-: | :-: | :-: | :-: | :-: | | [1] | [2] | [3] | [4] | [5] | [6] | [7] | -| 8 | 9 | 10 | 11 | 12 | 13 | 14 | +| [8] | 9 | 10 | 11 | 12 | 13 | 14 | | 15 | 16 | 17 | 18 | 19 | 20 | 21 | | 22 | 23 | 24 | 25 | | | | @@ -14,3 +14,4 @@ [5]: ./lib/2024/5.ex [6]: ./lib/2024/6.ex [7]: ./lib/2024/7.ex +[8]: ./lib/2024/8.ex diff --git a/2024/lib/2024/8.ex b/2024/lib/2024/8.ex new file mode 100644 index 0000000..b258388 --- /dev/null +++ b/2024/lib/2024/8.ex @@ -0,0 +1,59 @@ +import AOC +import AOC.Prelude + +aoc 2024, 8 do + def p1(input) do + input + |> map_grid() + |> count_antinodes(&antinodes_for_tower_pair/1) + end + + def p2(input) do + grid = map_grid(input) + + count_antinodes(grid, &resonant_harmonics_antinodes_for_tower_pair(&1, grid)) + end + + ## + + defp count_antinodes(grid, antinodes_for_tower_fun) do + grid + |> find_tower_pairs() + |> Enum.map(&elem(&1, 0)) + |> Enum.flat_map(antinodes_for_tower_fun) + |> Enum.filter(&in_bounds?(grid, &1)) + |> Enum.uniq() + |> Enum.count() + end + + defp find_tower_pairs(grid) do + tower_locaions = Map.reject(grid, &match?({_, "."}, &1)) + + for {pos_a, t} <- tower_locaions, {pos_b, ^t} <- tower_locaions, pos_a != pos_b, into: %{} do + {[pos_a, pos_b] + |> Enum.sort() + |> List.to_tuple(), t} + end + end + + defp antinodes_for_tower_pair({pos_a, pos_b}) do + d = delta(pos_a, pos_b) + + [sub_delta(pos_a, d), add_delta(pos_b, d)] + end + + defp resonant_harmonics_antinodes_for_tower_pair({pos_a, pos_b}, grid) do + d = delta(pos_a, pos_b) + + sub_stream = pos_a |> Stream.iterate(&sub_delta(&1, d)) |> Stream.take_while(&in_bounds?(grid, &1)) + add_stream = pos_b |> Stream.iterate(&add_delta(&1, d)) |> Stream.take_while(&in_bounds?(grid, &1)) + + sub_stream + |> Stream.concat(add_stream) + |> Enum.to_list() + end + + defp delta({x1, y1}, {x2, y2}), do: {x2 - x1, y2 - y1} + defp add_delta({x, y}, {xd, yd}), do: {x + xd, y + yd} + defp sub_delta({x, y}, {xd, yd}), do: {x - xd, y - yd} +end diff --git a/2024/lib/aoc/prelude.ex b/2024/lib/aoc/prelude.ex index 6b356b1..bdf708b 100644 --- a/2024/lib/aoc/prelude.ex +++ b/2024/lib/aoc/prelude.ex @@ -27,4 +27,24 @@ defmodule AOC.Prelude do |> String.split(separator, trim: true) |> Enum.map(&String.to_integer/1) end + + @type map_grid(a) :: %{{x :: non_neg_integer(), y :: non_neg_integer()} => a} + + @doc """ + Reads an Advent of Code-style grid of characters into a map of x, y positions + to a string containing the single character at that position. + """ + @spec map_grid(String.t()) :: map_grid(String.t()) + def map_grid(input) do + for {line, y} <- Enum.with_index(lines(input)), {c, x} <- Enum.with_index(String.graphemes(line)), into: %{} do + {{x, y}, c} + end + end + + @doc """ + Returns `true` if the given position tuple is within the bounds of the + map_grid. + """ + @spec in_bounds?(map_grid(any()), {x :: integer(), y :: integer()}) :: boolean() + def in_bounds?(grid, pos), do: Map.has_key?(grid, pos) end diff --git a/README.md b/README.md index 90441e8..acf045e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ | [2021] | **43/50** 🌟 | Elixir | | [2022] | **14/50** 🌟 | Elixir, Haskell | | [2023] | **19/50** 🌟 | Elixir, Haskell | -| [2024] | **14/50** 🌟 | Elixir | +| [2024] | **16/50** 🌟 | Elixir | | **Total** | **162** 🌟| | [2015]: ./2015