Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code - Day 8

2024/08.livemd

Advent of Code - Day 8

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

Problem

https://adventofcode.com/2024/day/8

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2024", "8", System.fetch_env!("LB_AOC_SESSION"))
example_input = "............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............"
defmodule Day8 do
  def antennas(map) do
    map 
      |> Map.filter(fn {_,v} -> v != "." && v != "#" end)
      |> Enum.group_by(fn {_,v} -> v end)
  end

  def pairs(antennas) do
    for i <- antennas, j <- antennas, i != j, do: [i,j]
  end

  def find_antinodes(map, cb) do
    antennas(map) |> Enum.flat_map(fn {_, antennas} ->
      pairs(antennas)
        |> Enum.flat_map(fn p ->
            cb.(p, map)
           end)
      end) |> MapSet.new()
  end

  def antinodes([{a_pos, _}, {b_pos, _}], map) do
    {dx, dy} = delta_pos(a_pos, b_pos)

    {ax, ay} = a_pos
    {bx, by} = b_pos

    [
      {ax + dx, ay + dy},
      {bx - dx, by - dy}
    ] |> Enum.map(fn p ->
      if Map.get(map, p) do
        p
      end
    end) 
    |> Enum.filter(fn x -> x != nil end)
  end

  def compute_bounds(map) do
    Enum.reduce(map, %{xmax: 0, ymax: 0}, fn {{x, y}, _}, acc ->
      %{xmax: max(acc.xmax, x), ymax: max(acc.ymax, y)}
    end)
  end

  def antinodes2([{a_pos, _}, {b_pos, _}], map) do
    bounds = compute_bounds(map)
    {dx, dy} = delta_pos(a_pos, b_pos)

    {ax, ay} = a_pos

    yiter = ceil(bounds.ymax / dy)
    xiter = ceil(bounds.xmax / dx)

    bound = max(yiter, xiter)

    Enum.reduce(-bound..bound, [], fn magnitude, acc ->
      pt = {ax + (magnitude*dx), ay + (magnitude*dy)}
      if Map.get(map, pt) do
        acc ++ [pt]
      else
        acc
      end
    end)
  end

  def delta_pos({ax, ay}, {bx, by}) do
    {ax - bx, ay - by}
  end
    
  def parse(str) do
    str
    |> String.split("\n", trim: true)
    |> Enum.with_index()
    |> Enum.flat_map(fn {row, y} ->
      row
      |> String.graphemes()
      |> Enum.with_index()
      |> Enum.map(fn {char, x} -> {{x, y}, char} end)
    end)
    |> Enum.into(%{})
  end

  def solve_part1(str) do
    parse(str) |> find_antinodes(&amp;antinodes(&amp;1,&amp;2)) |> MapSet.size()
  end

  def solve_part2(str) do
    parse(str) |> find_antinodes(&amp;antinodes2(&amp;1,&amp;2)) |> MapSet.size()
  end
end
Day8.solve_part1(example_input)
Day8.solve_part1(puzzle_input)

Part 2

example2 = "T....#....
...T......
.T....#...
.........#
..#.......
..........
...#......
..........
....#.....
.........."
Day8.antennas(Day8.parse(example2))
Day8.solve_part2(example2)
Day8.solve_part2(example_input)
Day8.solve_part2(puzzle_input)