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

Day 8: Resonant Collinearity

day08.livemd

Day 8: Resonant Collinearity

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

Input

input = Kino.Input.textarea("Please, paste your input:")
defmodule Day8Shared do
  def parse(input) do
    input
    |> Kino.Input.read()
    |> String.split("\n")
    |> Enum.with_index()
    |> Enum.reduce(%{}, fn {line, y}, map ->
      line
      |> String.codepoints()
      |> Enum.with_index()
      |> Enum.reduce(map, fn {char, x}, map ->
        map
        |> Map.put({x, y}, char)
        |> Map.update(:max_x, x, &max(&1, x))
        |> Map.update(:max_y, y, &max(&1, y))
      end)
    end)
  end

  # Generates list of unique pairs for the given input. Example:
  #
  # - for input `[:a, :b, :c]`
  # - we will get [[:a, :b], [:a, :c], [:b, :c]] (and skip like [:c, :a] or [:c, :b])
  def unique_pairs(coords) do
    for i <- 0..(length(coords) - 2),
        j <- (i + 1)..(length(coords) - 1) do
      {Enum.at(coords, i), Enum.at(coords, j)}
    end
  end
end

# Day8Shared.parse(input)

Part 1

defmodule Day8Part1 do
  def process(%{max_x: max_x, max_y: max_y} = map) do
    antennas =
      map
      |> Enum.filter(&amp;(is_binary(elem(&amp;1, 1)) &amp;&amp; elem(&amp;1, 1) != "."))
      |> Enum.group_by(&amp;elem(&amp;1, 1), &amp;elem(&amp;1, 0))

    Enum.map(antennas, fn {_label, coords} ->
      coords
      |> Day8Shared.unique_pairs()
      |> Enum.map(&amp;antinodes/1)
    end)
    |> List.flatten()
    |> Enum.reject(fn {x, y} -> x < 0 or x > max_x or y < 0 or y > max_y end)
    |> Enum.uniq()
    |> Enum.count()
  end

  def antinodes({{x1, y1}, {x2, y2}}) do
    [
      {x2 + (x2 - x1), y2 + (y2 - y1)},
      {x1 - (x2 - x1), y1 - (y2 - y1)}
    ]
  end
end

input |> Day8Shared.parse() |> Day8Part1.process()

# 285 is the right answer

Part 2

defmodule Day8Part2 do
  def process(%{max_x: max_x, max_y: max_y} = map) do
    antennas =
      map
      |> Enum.filter(&amp;(is_binary(elem(&amp;1, 1)) &amp;&amp; elem(&amp;1, 1) != "."))
      |> Enum.group_by(&amp;elem(&amp;1, 1), &amp;elem(&amp;1, 0))

    Enum.map(antennas, fn {_label, coords} ->
      coords
      |> Day8Shared.unique_pairs()
      |> Enum.map(&amp;antinodes(&amp;1, max_x, max_y))
    end)
    |> List.flatten()
    |> Enum.uniq()
    |> Enum.count()
  end

  def antinodes({{x1, y1}, {x2, y2}}, max_x, max_y) do
    delta_x = x2 - x1
    delta_y = y2 - y1

    [
      all_nodes({x1, y1}, :dec, delta_x, delta_y, max_x, max_y, 0, []),
      all_nodes({x2, y2}, :inc, delta_x, delta_y, max_x, max_y, 0, [])
    ]
  end

  def all_nodes({x, y}, op, delta_x, delta_y, max_x, max_y, distance_n, nodes) do
    {new_x, new_y} =
      new_node =
      case op do
        :inc -> {x + delta_x * distance_n, y + delta_y * distance_n}
        :dec -> {x - delta_x * distance_n, y - delta_y * distance_n}
      end

    if new_x < 0 or new_x > max_x or new_y < 0 or new_y > max_y do
      nodes
    else
      all_nodes({x, y}, op, delta_x, delta_y, max_x, max_y, distance_n + 1, [new_node | nodes])
    end
  end
end

input |> Day8Shared.parse() |> Day8Part2.process()

# 944 is the right answer