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

Day 13

day14.livemd

Day 13

Mix.install([
  {:kino, "~> 0.14.2"},
  {:kino_aoc, "~> 0.1.7"},
  {:tucan, "~> 0.4.1"},
  {:kino_vega_lite, "~> 0.1.13"}
])

Part 1

example = """
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
"""

{{:ok, input}, grid_dims} = if false do
    {
      KinoAOC.download_puzzle("2024", "14", System.fetch_env!("LB_AOC_SESSION")),
      {101, 103}
    }
else
  {{:ok, example}, {11, 7}}
end

parse_num_pair = fn pair -> pair
  |> String.split(",")
  |> Enum.map(
    fn piece ->
      String.replace(piece, ~r/[^\d|^-]/, "") |> String.to_integer()
    end)
  |> List.to_tuple()
end

parse_robot = fn string ->
  parts = String.split(string, "v")
  loc = Enum.at(parts, 0) |> parse_num_pair.()
  vel = Enum.at(parts, 1) |> parse_num_pair.()
  {loc, vel}
end

robots = input |> String.trim() |> String.split("\n")
  |> Enum.map(&parse_robot.(&1))
defmodule Part1 do
  def get_final_pos({{xi, yi}, {xv, yv}}, {xsize, ysize}, iter) do
    {
      rem(rem((xsize + xv) * iter, xsize) + xi, xsize),
      rem(rem((ysize + yv) * iter, ysize) + yi, ysize),
    }
  end

  def get_all_final_pos(robots, grid_dims, iter) do
    Enum.map(
      robots,
      fn robot -> get_final_pos(robot, grid_dims, iter) end
    )
  end

  def get_quadrant_counts(finals, mx, my) do
    Enum.reduce(
      finals, %{}, fn {x, y}, acc ->
        cond do
          x < mx and y < my -> Map.update(acc, :a, 1, &amp; &amp;1 + 1)
          x < mx and y > my -> Map.update(acc, :b, 1, &amp; &amp;1 + 1)
          x > mx and y < my -> Map.update(acc, :c, 1, &amp; &amp;1 + 1)
          x > mx and y > my -> Map.update(acc, :d, 1, &amp; &amp;1 + 1)
          true -> acc
        end
      end
    )
  end
end

finals = Part1.get_all_final_pos(robots, grid_dims, 100)
{xsize, ysize} = grid_dims
{mx, my} = Enum.map(
  [xsize, ysize],
  &amp; :math.floor(&amp;1 / 2) |> trunc()
)
  |> List.to_tuple()

quadrant_counts = Part1.get_quadrant_counts(finals, mx, my)
result = quadrant_counts |> Map.values() |> Enum.product()

Part 2

iters = 0..100
gen_plot = fn iter ->
  finals = Part1.get_all_final_pos(robots, grid_dims, iter)
  {xs, ys} = Enum.reduce(finals, {[], []}, fn {x, y}, {xs, ys} -> {[x | xs], [y | ys]} end)
  
  VegaLite.new(width: 640, height: 640)
    |> VegaLite.data_from_values(x: xs, y: ys)
    |> Tucan.scatter("x", "y")
    |> Tucan.set_title("#{iter}")
end

Stream.iterate(0, &amp; &amp;1 + 1)
  |> Enum.chunk_every(10)
  |> Enum.map(
    fn some_iters -> some_iters |> Task.async_stream(
      fn iter ->
        IO.puts("Writing image #{iter}...")
        plotfile = gen_plot.(iter) |> Tucan.Export.save!("#{iter}.png")
      end
    )
    |> Task.await_many()
    end
  )