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

Advent of Code 2024 Day 4

adventofcode/2024/day04/day4.livemd

Advent of Code 2024 Day 4

Input Data

test_input = 
"""
MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX
"""
problem_input = File.read!("/data/files/input.txt")

Solution

m = %{{1,1} => "9", {2,3} => "8"}
{m[{1,1}],  m[{2,3}]}
defmodule Matrix do
  # Function to create a matrix from a string input
  def from_string(input) do
    input
    |> String.split("\n", trim: true)          # Split into rows
    |> Enum.with_index(1)                      # Enumerate rows starting at 1
    |> Enum.reduce(%{}, fn {row, row_idx}, acc ->
      row
      |> String.graphemes()                    # Split row into characters
      |> Enum.with_index(1)                    # Enumerate columns starting at 1
      |> Enum.reduce(acc, fn {char, col_idx}, acc ->
        Map.put(acc, {row_idx, col_idx}, char) # Add cell to map
      end)
    end)
  end

  def get_dimensions(matrix) do
    matrix    
    |> Map.keys()
    |> Enum.sort(fn ({x1,y1}, {x2,y2}) -> x1+y1 > x2+y2 end)
    |> Kernel.hd()
  end

  # Function to get a value from the matrix
  def get(matrix, row, col), do: Map.get(matrix, {row, col})

  # Function to set a value in the matrix
  def set(matrix, row, col, value), do: Map.put(matrix, {row, col}, value)



  def count_word_occurrences(matrix, rows, cols, word) do
    
    directions = [
      # Horizontal (left to right)
      fn {r, c}, step -> {r, c + step} end,
      # Horizontal (right to left)
      fn {r, c}, step -> {r, c - step} end,
      # Vertical (top to bottom)
      fn {r, c}, step -> {r + step, c} end,
      # Vertical (bottom to top)
      fn {r, c}, step -> {r - step, c} end,
      # Diagonal "\" (top-left to bottom-right)
      fn {r, c}, step -> {r + step, c + step} end,
      # Diagonal "\" (bottom-right to top-left)
      fn {r, c}, step -> {r - step, c - step} end,
      # Diagonal "/" (top-right to bottom-left)
      fn {r, c}, step -> {r + step, c - step} end,
      # Diagonal "/" (bottom-left to top-right)
      fn {r, c}, step -> {r - step, c + step} end
    ]
    
    # Count occurrences using pattern matching and filtering
    Enum.reduce(directions, 0, fn direction, acc ->
      acc + count_direction_occurrences(matrix, rows, cols, word, direction)
    end)
  end
  
  defp count_direction_occurrences(matrix, rows, cols, word, direction) do
    # Generate all possible starting positions
    for r <- 1..rows, c <- 1..cols do
      # Check if the word can fit in this direction from the starting position
      if check_word_at_position(matrix, {r, c}, word, direction) do
        1
      else
        0
      end
    end
    |> Enum.sum()
  end
  
  defp check_word_at_position(matrix, start_pos, word, direction) do
    # Check if the entire word can be matched in the given direction
    word_letters = String.graphemes(word)
    
    Enum.with_index(word_letters)
    |> Enum.all?(fn {letter, step} ->
      pos = direction.(start_pos, step)
      Map.get(matrix, pos, "") == String.upcase(letter)
    end)
  end



  def count_x_pattern_occurrences(matrix, rows, cols, word) do
    # Define the four X-pattern offsets
    x_patterns = [
      # First pattern: M.S
      #               .A.
      #               M.S
      [
        {{0, 0}, word |> String.at(0)},
        {{0, 2}, word |> String.at(2)},
        {{1, 1}, word |> String.at(1)},
        {{2, 0}, word |> String.at(0)},
        {{2, 2}, word |> String.at(2)}
      ],
      # Second pattern: M.M
      #                 .A.
      #                 S.S
      [
        {{0, 0}, word |> String.at(0)},
        {{0, 2}, word |> String.at(0)},
        {{1, 1}, word |> String.at(1)},
        {{2, 0}, word |> String.at(2)},
        {{2, 2}, word |> String.at(2)}
      ],
      # Third pattern: S.M
      #                .A.
      #                S.M
      [
        {{0, 0}, word |> String.at(2)},
        {{0, 2}, word |> String.at(0)},
        {{1, 1}, word |> String.at(1)},
        {{2, 0}, word |> String.at(2)},
        {{2, 2}, word |> String.at(0)}
      ],
      # Fourth pattern: S.S
      #                 .A.
      #                 M.M
      [
        {{0, 0}, word |> String.at(2)},
        {{0, 2}, word |> String.at(2)},
        {{1, 1}, word |> String.at(1)},
        {{2, 0}, word |> String.at(0)},
        {{2, 2}, word |> String.at(0)}
      ]
    ]
    
    # Count occurrences across the matrix
    for r <- 1..(rows-2), c <- 1..(cols-2) do
      Enum.count(x_patterns, fn pattern ->
        Enum.all?(pattern, fn {{dr, dc}, letter} ->
          Map.get(matrix, {r + dr, c + dc}, "") == letter
        end)
      end)
    end
    |> Enum.sum()
  end
end
String.graphemes("XMAS")
defmodule Day4 do
  def task1(input) do
    matrix = Matrix.from_string(input)
    {lines, cols} = Matrix.get_dimensions(matrix)

    Matrix.count_word_occurrences(matrix, lines, cols, "XMAS")
  end

  def task2(input) do
    matrix = Matrix.from_string(input)
    {lines, cols} = Matrix.get_dimensions(matrix)

    Matrix.count_x_pattern_occurrences(matrix, lines, cols, "MAS")
    
  end
end
 Day4.task1(test_input)
 Day4.task1(problem_input)
 Day4.task2(test_input)
 Day4.task2(problem_input)
Enum.all?([true, true, true, true, true])