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

Advent of Code 2024/6

2024/6.livemd

Advent of Code 2024/6

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

Section

input = Kino.Input.textarea("input")
defmodule AoC2024_6 do
  alias VegaLite, as: Vl

  def parse(input) do
    Kino.Input.read(input)
    |> String.split("\n\n", trim: true)
    |> Enum.map(fn part -> String.split(part, "\n", trim: true) |> Enum.map(fn row -> String.split(row, "", trim: true) end) end)
    |> Enum.at(0)
  end

  def get(matrix, row, col) do
    matrix
    |> Enum.at(row, [])
    |> Enum.at(col)
  end

  def set(matrix, row, col, value) do
    List.update_at(matrix, row, fn r ->
      List.update_at(r, col, fn _ -> value end)
    end)
  end

  def start(matrix) do
    row_map = Enum.map(matrix, fn row -> Enum.find_index(row, fn el -> el == "^" end) end)
    row = Enum.find_index(row_map, fn e -> e != nil end)
    { row, Enum.find(row_map, fn e -> e != nil end), :up }
  end

  def new_coord({row, col, :up}), do: {row - 1, col}
  def new_coord({row, col, :down}), do: {row + 1, col}
  def new_coord({row, col, :left}), do: {row, col - 1}
  def new_coord({row, col, :right}), do: {row, col + 1}

  def new_direction(:up), do: :right
  def new_direction(:right), do: :down
  def new_direction(:down), do: :left
  def new_direction(:left), do: :up

  def contains_consecutive?(enum, value1, value2) do
    enum
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.any?(fn [a, b] -> a == value1 and b == value2 end)
  end
  
  def step(matrix, {row, col, direction}, history \\ []) do
    {new_row, new_col} = new_coord({row, col, direction})
    cond do
      (new_row < 0 || new_row == length(matrix) || new_col < 0 || new_col == length(Enum.at(matrix, 0))) == true ->
        history
      get(matrix, new_row, new_col) != "." &amp;&amp; get(matrix, new_row, new_col) != "^" ->
        step(matrix, {row, col, new_direction(direction)}, history)
      contains_consecutive?(history, {row, col, direction}, {new_row, new_col, direction}) ->
        nil
      true ->
        step(matrix, {new_row, new_col, direction}, history ++ [{new_row, new_col, direction}])
    end
  end

  def draw(history, matrix) do
    Enum.reduce(history, matrix, fn {row, col}, acc -> set(acc, row, col, "X") end)
  end

  def part_1(input) do
    matrix = input
    |> parse

    start = start(matrix)
    history = step(matrix, start, [start])
 
    Vl.new()
    |> Vl.data_from_values(Enum.map(history, fn {y, x, _} -> %{x: x, y: y} end))
    |> Vl.mark(:point)
    |> Vl.encode_field(:x, "x")
    |> Vl.encode_field(:y, "y")
    |> Kino.VegaLite.new()
    |> Kino.render()
    
    Enum.count(Enum.uniq(history |> Enum.map(fn {x, y, _} -> {x, y} end)))
  end

  def part_2(input) do
    matrix = input
    |> parse

    start = start(matrix)
    history = step(matrix, start, [start])
    
    points = Enum.filter(
      Enum.chunk_every(history, 2, 1, :discard),
      fn [{first_row, first_col, direction}, {second_row, second_col, second_direction}] ->
        (direction == second_direction) &amp;&amp;
        (step(set(matrix, second_row, second_col, "X"), {first_row, first_col, direction}) == nil) 
      end
    )
    |> Enum.map(fn [_, second] -> second end)
    |> Enum.uniq

    Vl.new()
    |> Vl.layers([
      Vl.new()
      |> Vl.data_from_values(Enum.map(history, fn {y, x, _} -> %{x: x, y: y} end))
      |> Vl.mark(:point)
      |> Vl.encode_field(:x, "x")
      |> Vl.encode_field(:y, "y"),

      Vl.new()
      |> Vl.data_from_values(Enum.map(points, fn {y, x, _} -> %{x: x, y: y} end))
      |> Vl.mark(:tick)
      |> Vl.encode_field(:x, "x")
      |> Vl.encode_field(:y, "y")
    ])
    |> Kino.VegaLite.new()
    |> Kino.render()

    Enum.count(points)
  end
end
AoC2024_6.part_1(input)
AoC2024_6.part_2(input)