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

Day 14: Restroom Redoubt

day14.livemd

Day 14: Restroom Redoubt

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

Input

input = Kino.Input.textarea("Please, paste your input:")
defmodule Day14Shared do
  def parse(input) do
    input
    |> Kino.Input.read()
    |> String.split("\n")
    |> Enum.map(fn line ->
      [x0, y0, vx, vy] =
        ~r/p\=(\d+),(\d+) v\=(-?\d+),(-?\d+)/
        |> Regex.run(line, capture: :all_but_first)
        |> Enum.map(&String.to_integer/1)

      %{initial: {x0, y0}, velocity: {vx, vy}}
    end)
  end

  def delta(initial, velocity, limit, seconds),
    do: rem(initial + seconds * velocity + seconds * limit, limit)
end

# Day14Shared.parse(input)

Part 1

defmodule Day14Part1 do
  import Day14Shared, only: [delta: 4]

  @seconds 100

  def process(robots, width, height) do
    h_half = div(height, 2)
    v_half = div(width, 2)

    robots
    |> Enum.map(fn %{initial: {x0, y0}, velocity: {vx, vy}} ->
      final_position = {delta(x0, vx, width, @seconds), delta(y0, vy, height, @seconds)}

      quadrant(final_position, h_half, v_half)
    end)
    |> Enum.reject(&(&1 == :middle))
    |> Enum.frequencies()
    # |> IO.inspect()
    |> Enum.reduce(1, fn {_, n}, acc -> acc * n end)
  end

  defp quadrant({x, y}, h_half, v_half) do
    cond do
      x >= 0 and x <= v_half - 1 and y >= 0 and y <= h_half - 1 -> :first
      x >= v_half + 1 and y >= 0 and y <= h_half - 1 -> :second
      x >= 0 and x <= v_half - 1 and y >= h_half + 1 -> :third
      x >= v_half + 1 and y >= h_half + 1 -> :fourth
      true -> :middle
    end
  end
end

input
|> Day14Shared.parse()
# |> Day14Part1.process(11, 7)
|> Day14Part1.process(101, 103)

# 217183590 is too high
# 214109808 is the right answer

Part 2

defmodule Day14Part2 do
  import Day14Shared, only: [delta: 4]

  def process(robots, up_to_seconds, width, height) do
    # The main assumption was is that tree should look like a funnel with robots on its edge,
    # so we were looking for such a pattern in the "printed" map.
    #
    # Actually, tree was found with this original regex (with a huge amount of luck), so leaving
    # it here for historical purposes:
    #
    # ex_mas_tree =
    #   Regex.compile!(
    #     "R.{0,}\n.{0,}R.{1}R.{0,}\n.{0,}R.{3}R.{0,}\n.{0,}R\.{5}R.{0,}\n.{0,}R\.{7}R.{0,}\n.{0,}R\.{9}R.{0,}\n.{0,}R\.{11}R.{0,}\n.{0,}R\.{13}R.{0,}\n.{0,}R\.{15}R.{0,}\n.{0,}R\.{17}R.{0,}\n.{0,}R\.{19}R.{0,}\n"
    #   )
    #
    # With the next regex we can find the tree much faster, and it's an only one!
    ex_mas_tree =
      Regex.compile!(
        "R.{0,}\n.{0,}RRR.{0,}\n.{0,}RRRRR.{0,}\n.{0,}RRRRRRR.{0,}\n.{0,}RRRRRRRRR.{0,}\n"
      )

    Stream.map(0..up_to_seconds, fn second ->
      picture =
        robots
        |> Enum.reduce(MapSet.new(), fn %{initial: {x0, y0}, velocity: {vx, vy}}, roboset ->
          {x, y} = {delta(x0, vx, width, second), delta(y0, vy, height, second)}

          MapSet.put(roboset, {x, y})
        end)
        |> print(width, height)

      {second, picture}
    end)
    |> Stream.filter(fn {_, picture} -> String.match?(picture, ex_mas_tree) end)
    |> Enum.map(fn {second, picture} -> "Second: #{second}\n\n" <> picture end)
    |> Enum.join("\n\n\n\n")
    |> Kino.Text.new()
    
    # ^ it took some extra time and manual search through the hundred of "printed" maps
    # to find how exactly the Xmas tree should look like... That's why we're leaving the
    # traces of the "thought process" in the code, otherwise, it could be simplified to
    # just right away filtering
  end

  defp print(roboset, width, height) do
    Enum.map(0..height, fn y ->
      Enum.map(0..width, fn x ->
        if MapSet.member?(roboset, {x, y}), do: "R", else: "."
      end)
      |> Enum.join("")
    end)
    |> Enum.join("\n")
  end
end

input
|> Day14Shared.parse()
|> Day14Part2.process(8000, 101, 103)

# 8000 is too high
# 7687 is the right answer