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

Advent of code day 14

2024/livebooks/day-14.livemd

Advent of code day 14

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

Setup input

example = Kino.Input.textarea("Please paste your input example:")
input = Kino.Input.textarea("Please paste your real input:")
defmodule Robot do
  @max_row 103
  @max_col 101

  defstruct [:current_position, :velocity]

  def position_after_seconds(
        %__MODULE__{current_position: cp, velocity: v} = robot,
        seconds \\ 1
      ) do
    {row, col} = cp
    {vr, vc} = v

    row = Integer.mod(row + vr * seconds, @max_row)
    col = Integer.mod(col + vc * seconds, @max_col)

    %{robot | current_position: {row, col}}
  end

  def height do
    @max_row
  end

  def width do
    @max_col
  end
end
parsed =
  example
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(fn line ->
    [[col], [row], [vc], [vr]] = Regex.scan(~r/-?\d+(\.\d+)?/, line)

    {{String.to_integer(row), String.to_integer(col)}, {String.to_integer(vr), String.to_integer(vc)}}
    end)
robots =
  parsed
  |> Enum.map(fn {position, velocity} ->
    %Robot{
      current_position: position,
      velocity: velocity
    }
  end)

Part 01

robots
|> Enum.reduce(%{q1: 0, q2: 0, q3: 0, q4: 0}, fn robot, acc ->
  robot = Robot.position_after_seconds(robot, 100)

  row_mid = div(Robot.height(), 2)
  col_mid = div(Robot.width(), 2)

  case robot.current_position do
    {row, col} when row < row_mid and col < col_mid -> Map.update(acc, :q1, 0, &amp;(&amp;1 + 1))
    {row, col} when row < row_mid and col > col_mid -> Map.update(acc, :q2, 0, &amp;(&amp;1 + 1))
    {row, col} when row > row_mid and col < col_mid -> Map.update(acc, :q3, 0, &amp;(&amp;1 + 1))
    {row, col} when row > row_mid and col > col_mid -> Map.update(acc, :q4, 0, &amp;(&amp;1 + 1))
    _ -> acc
  end
end)
|> Map.values()
|> Enum.product()
defmodule GridPrinter do
  def print_grid(map) do
    # Extract all the coordinates
    coordinates = Map.keys(map)

    # Determine the grid bounds
    {min_x, max_x} = Enum.min_max(Enum.map(coordinates, fn {x, _y} -> x end))
    {min_y, max_y} = Enum.min_max(Enum.map(coordinates, fn {_x, y} -> y end))

    # Build the grid as a string
    for y <- min_y..max_y do
      for x <- min_x..max_x do
        # Get the value or a default space
        Map.get(map, {x, y}, " ")
      end
      # Join the row into a string
      |> Enum.join("")
    end
    # Join all rows with a newline
    |> Enum.join("\n")
    # Print the grid
    |> IO.puts()
  end

  def as_string(map) do
    # Extract all the coordinates
    coordinates = Map.keys(map)

    # Determine the grid bounds
    {min_x, max_x} = Enum.min_max(Enum.map(coordinates, fn {x, _y} -> x end))
    {min_y, max_y} = Enum.min_max(Enum.map(coordinates, fn {_x, y} -> y end))

    # Build the grid as a string
    for y <- min_y..max_y do
      for x <- min_x..max_x do
        # Get the value or a default space
        Map.get(map, {x, y}, " ")
      end
      # Join the row into a string
      |> Enum.join("")
    end
    # Join all rows with a newline
    |> Enum.join("\n")
  end
end

Part 02

rows = Robot.height() - 1
cols = Robot.width() - 1

grid =
  for l <- 0..rows, c <- 0..cols, into: %{} do
    {{l, c}, "."}
  end

{iteration, set} =
  0
  |> Stream.iterate(&amp;(&amp;1 + 1))
  |> Enum.reduce_while({grid, MapSet.new()}, fn iteration, {grid, set} ->
    robots = Enum.map(robots, fn robot -> Robot.position_after_seconds(robot, iteration) end)

    picture =
      Enum.reduce(robots, grid, fn robot, grid ->
        Map.put(grid, robot.current_position, "#")
      end)

    {:ok, pid} = File.open("/advent/tree.txt", [:append])

    IO.binwrite(pid, "#{iteration}\n" <> GridPrinter.as_string(picture) <> "\n\n")

    cond do
      MapSet.member?(set, picture) ->
        {:halt, {iteration, set}}

      true ->
        set = MapSet.put(set, picture)

        {:cont, {grid, set}}
    end
  end)