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

Day14

2024/elixir/day14.livemd

Day14

Mix.install([
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"}
])

Setup

{:ok, data} = KinoAOC.download_puzzle("2024", "14", System.fetch_env!("LB_AOC_SECRET"))

Solve

defmodule Day14 do
  @digits ~r/(-?\d+)/

  def parse(input) do
    input
    |> String.trim()
    |> String.split("\n", trim: true)
    |> Enum.map(&parse_robot/1)
  end

  def parse_robot(str) do
    Regex.scan(@digits, str, capture: :all_but_first)
    |> List.flatten()
    |> Enum.map(&String.to_integer/1)
    |> List.to_tuple()
  end

  def solve(data, max \\ {11, 7}) do
    robots = parse(data)
    {t1(robots, max), t2(robots, max)}
  end

  def t1(robots, max) do
    robots = simulate(robots, max, 100)
    factor(robots, max)
  end

  def t2(robots, max) do
    all = length(robots)
    find_tree(0, max, robots, all) |> handle_res(max)
  end

  def handle_res({:ok, {i, robots}}, max) do
    plot(robots, max)
    i
  end

  def handle_res({:error, {:not_found, robots}}, max) do
    plot(robots, max)
    :not_found
  end

  def find_tree(i, {mx, my}, robots, _) when i > mx*my, do: {:error, {:not_found, robots}}

  def find_tree(i, max, robots, all) do
    robots = simulate(robots, max, 1)

    seen = Enum.reduce(robots, MapSet.new(), fn {x, y, _, _}, seen ->
      MapSet.put(seen, {x,y})
    end)

    if MapSet.size(seen) == all do
      {:ok, {i, robots}}
    else
      find_tree(i+1, max, robots, all)
    end
  end

  def simulate(robots, {mx, my}, n \\ 1) do
    Enum.reduce(robots, [], fn {x, y, vx, vy}, acc ->
      x = prem(x + vx*n, mx)
      y = prem(y + vy*n, my)
      [{x,y, vx, vy} | acc]
    end)
  end

  def prem(i, max) do
    r = rem(i, max)
    (r < 0) &amp;&amp; max+r || r
  end

  def factor(robots, {mx, my}) do
    {midx, midy} = {div(mx, 2), div(my, 2)}

    Enum.reduce(robots, [0,0,0,0], fn robot, [q1,q2,q3,q4] = acc ->
      case robot do
        {x,y,_,_} when x < midx and y < midy -> [q1+1,q2,q3,q4]
        {x,y,_,_} when x > midx and y < midy -> [q1,q2+1,q3,q4]
        {x,y,_,_} when x < midx and y > midy -> [q1,q2,q3+1,q4]
        {x,y,_,_} when x > midx and y > midy -> [q1,q2,q3,q4+1]
        _ -> acc #mid
      end
    end)
    |> Enum.product()
  end

  def plot(robots, {mx, my}) do
    g = Enum.reduce(robots, %{}, fn {x, y, _, _}, g ->
        Map.update(g, {x,y}, 1, fn cnt -> cnt + 1 end)
      end)

    Enum.map((0..my-1), fn y ->
      Enum.map((0..mx-1), fn x ->
        Map.get(g, {x, y}, ".") |> to_string()
      end)
      |> Enum.join("")
    end)
    |> Enum.join("\n")
    |> IO.puts()
  end
end


tdata = """
p=0,4 v=3,-3
p=6,3 v=-1,-3
p=10,3 v=-1,2
p=2,0 v=2,-1
p=0,0 v=1,3
p=3,0 v=-2,-2
p=7,6 v=-1,-3
p=3,0 v=-1,-2
p=9,3 v=2,3
p=7,3 v=-1,2
p=2,4 v=2,-3
p=9,5 v=-3,-3
"""

Day14.solve(data, {101, 103})