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

Advent 2024 - Day 4

day4.livemd

Advent 2024 - Day 4

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

Setup

input = Kino.Input.textarea("Please paste your input file")
input = input |> Kino.Input.read()

rows = String.split(input, "\n")

row_count = length(rows)
col_count = Enum.at(rows, 0) |> String.length()

input = input
  |> String.replace("\n", "")
  |> String.graphemes()
  |> Enum.map(&(String.to_atom(&1)))

input

Part 1

x_locations = input
|> Enum.with_index()
|> Enum.filter(fn {char, _index} -> char == :X end)

defmodule WordSearchV1 do
  @next %{
    :X => :M,
    :M => :A,
    :A => :S
  }

  def navigate({input, row_count, col_count} = board, {x, y}, vec, expecting) do
    if (x < 0 or x > row_count) or (y < 0 or y > col_count) do
      false
    else
      index = row_count * y + x
      val = Enum.at(input, index)

      if val == expecting do
        if val == :S do
          true
        else
          {x_mod, y_mod} = vec
          pos = {x + x_mod, y + y_mod}
          navigate(board, pos, vec, Map.fetch!(@next, expecting))
        end
      else
        false
      end
    end
  end

  def find_xmases({_input, row_count, col_count} = board, start_index) do
    i_x = rem(start_index, col_count)
    i_y = div(start_index, row_count)

    for x <- -1..1 do
      for y <- -1..1 do
        if x == 0 &amp;&amp; y == 0 do
          false
        else
          vec = {x, y}
          pos = {i_x + x, i_y + y}
          navigate(board, pos, vec, :M)
        end
      end
    end
    |> List.flatten()
    |> Enum.filter(&amp;(&amp;1))
  end
end

for {_x, x_location} <- x_locations do
  WordSearchV1.find_xmases({input, row_count, col_count}, x_location)
end
|> List.flatten()
|> Enum.count()

Part 2

a_locations = input
|> Enum.with_index()
|> Enum.filter(fn {char, _index} -> char == :A end)

defmodule WordSearchV2 do
  def get_value_from_pos({input, row_count, col_count}, x, y) do
    if x < 0 or x >= col_count or y < 0 or y >= row_count do
      nil
    else
      index = row_count * y + x
      Enum.at(input, index)
    end
  end
  
  def find_x_mases({_input, row_count, col_count} = board, center) do
    c_x = rem(center, col_count)
    c_y = div(center, row_count)

    if c_x - 1 < 0 or c_x + 1 > col_count or c_y - 1 < 0 or c_y + 1 > row_count do
      false
    else
      top_left = get_value_from_pos(board, c_x - 1, c_y - 1)
      top_right = get_value_from_pos(board, c_x + 1, c_y - 1)
      bottom_left = get_value_from_pos(board, c_x - 1, c_y + 1)
      bottom_right = get_value_from_pos(board, c_x + 1, c_y + 1)

      left_pair = case {top_left, bottom_right} do
        {:M, :S} -> true
        {:S, :M} -> true
        _ -> false
      end

      right_pair = case {top_right, bottom_left} do
        {:M, :S} -> true
        {:S, :M} -> true
        _ -> false
      end

      is_valid = left_pair and right_pair
      
      is_valid
    end
  end
end

for {_x, a_location} <- a_locations do
  WordSearchV2.find_x_mases({input, row_count, col_count}, a_location)
end
|> List.flatten()
|> Enum.filter(&amp;(&amp;1))
|> Enum.count()