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

Day 4

day4.livemd

Day 4

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

Part 1

https://adventofcode.com/2024/day/4

input = Kino.FS.file_path("day4_input.txt")
  |> File.read!()
  |> String.trim()
  |> String.split("\n")
  |> Enum.map(fn line ->
    String.codepoints(line)  # Split characters
  end)
defmodule Searcher do
  def next_pos({row, col}, direction) do
    {row, col} = case direction do
      :r -> {row, col + 1}
      :l -> {row, col - 1}
      :d -> {row + 1, col}
      :dr -> {row + 1, col + 1}
      :dl -> {row + 1, col - 1}
      :u -> {row - 1, col}
      :ur -> {row - 1, col + 1}
      :ul -> {row - 1, col - 1}
    end
    
    if row < 0 or col < 0, do: raise("Illegal move.")
    {row, col}
  end

  def is_word_at?([letter], array, pos, _), do: letter == value_at(pos, array)
  def is_word_at?([letter | rest], array, pos, direction) do
    try do
      ^letter = value_at(pos, array)
      new_pos = next_pos(pos, direction)
      is_word_at?(rest, array, new_pos, direction)
    rescue
      _ -> false
    end
  end
  
  def value_at({row, col}, array) do
    Enum.at(array, row) |> Enum.at(col)
  end

  def get_all_positions(letter, array = [a_row | _]) do
    Enum.map(0..length(array) - 1, fn row ->
      Enum.map(0..length(a_row) - 1, fn col ->
        if letter == value_at({row, col}, array), do: {row, col}
      end)
    end)
    |> List.flatten()
    |> Enum.filter(&amp; &amp;1)
  end
end

directions = [:r, :l, :d, :dr, :dl, :u, :ur, :ul]
word = ["X", "M", "A", "S"]

all_x_positions = Searcher.get_all_positions(List.first(word), input)
result = all_x_positions
  |> Enum.map(fn pos ->
    Enum.map(directions, fn dir ->
      Searcher.is_word_at?(word, input, pos, dir)
    end)
  end)
  |> List.flatten()
  |> Enum.filter(&amp; &amp;1)
  |> Enum.count()

Part 2

defmodule XSearcher do
  import Searcher
  
  def is_valid_x_mas?(a_pos, array) do
    try do
      diag1 = 
        [next_pos(a_pos, :ur), next_pos(a_pos, :dl)]
        |> Enum.map(fn pos -> value_at(pos, array) end)
      diag2 =
        [next_pos(a_pos, :dr), next_pos(a_pos, :ul)]
        |> Enum.map(fn pos -> value_at(pos, array) end)
  
      "M" in diag1 and "S" in diag1 and "M" in diag2 and "S" in diag2
    rescue
      _ -> false
    end
  end
end

a_positions = Searcher.get_all_positions("A", input)
validity = Enum.map(a_positions, fn pos ->
  XSearcher.is_valid_x_mas?(pos, input)
end)
result = validity |> Enum.filter(&amp; &amp;1) |> Enum.count()