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

Day 8

2024/day8.livemd

Day 8

Mix.install([
  {:kino, "~> 0.13.0"}
])

Part1

content =
  Kino.FS.file_path("day8_1.txt")
  |> File.read!()
  |> String.split("\n", trim: true)
  |> Enum.with_index()
  |> Enum.flat_map(fn {line, row} ->
    String.to_charlist(line)
    |> Enum.with_index()
    |> Enum.flat_map(fn {char, col} ->
      [{{row, col}, char}]
    end)
  end)
  |> Map.new()
test = "............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............"
  |> String.split("\n", trim: true)
  |> Enum.with_index()
  |> Enum.flat_map(fn {line, row} ->
    String.to_charlist(line)
    |> Enum.with_index()
    |> Enum.flat_map(fn {char, col} ->
      [{{row, col}, char}]
    end)
  end)
  |> Map.new()
defmodule Part1 do
  # Finds antenna locations
  # Returns a new map with the key being unique antenna types and values a list of locations
  def find_antenna_locations(grid) do
    Enum.reduce(grid, %{}, fn {location, character}, acc ->
      if character not in [?.] do
        Map.update(acc, character, [location], fn locations -> [location | locations] end)
      else
        acc
      end
    end)
  end

  def check_antinode(grid, {{x1, y1}, antenna_list}) do
    # For each antinode, find distance to every other antinode
    # and check if there is something twice that far
    # returns antinode location so we can check for duplicates
    Enum.map(antenna_list, fn {x2, y2} ->
      if {x1, y1} == {x2, y2} do
        false
      else
        vx = (x2 - x1) * 2
        vy = (y2 - y1) * 2
        location = {x1 + vx, y1 + vy}

        case Map.get(grid, location) do
          nil -> false
          _ -> location
        end
      end
    end)
  end

  def check_antinodes(grid) do
    antennas = Part1.find_antenna_locations(grid)

    Enum.flat_map(antennas, fn {_type, list} ->
      Enum.flat_map(list, fn a -> Part1.check_antinode(grid, {a, list}) end)
    end)
  end
end

content
|> Part1.check_antinodes()
# Make sure we only count unique locations
|> Enum.uniq()
# remove the single false
|> Enum.filter(fn e -> e end)
|> Enum.count()

Part 2

Now we need to modify a bit, to search every single grid space for intersections with two antennas

defmodule Part2 do
  def collect_locations(grid, {x, y}, {vx, vy, iteration}, list \\ []) do
    location = {x + vx * iteration, y + vy * iteration}

    case Map.get(grid, location) do
      nil -> list
      _ -> collect_locations(grid, {x, y}, {vx, vy, iteration + 1}, [location | list])
    end
  end

  # Modified to use the recursive collect_locations to find all possible locations until grid ends
  def check_antinode(grid, {{x1, y1}, antenna_list}) do
    Enum.map(antenna_list, fn {x2, y2} ->
      if {x1, y1} == {x2, y2} do
        false
      else
        vx = x2 - x1
        vy = y2 - y1

        collect_locations(grid, {x1, y1}, {vx, vy, 1})
      end
    end)
  end

  def check_antinodes(grid) do
    antennas = Part1.find_antenna_locations(grid)

    Enum.flat_map(antennas, fn {_type, list} ->
      Enum.flat_map(list, fn a -> Part2.check_antinode(grid, {a, list}) end)
      # this flatten was very important :(
      |> List.flatten()
    end)
  end
end

content
|> Part2.check_antinodes()
|> Enum.uniq()
|> Enum.filter(fn e -> e end)
|> Enum.count()