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

Day 4: Ceres Search

Day-04.livemd

Day 4: Ceres Search

Mix.install([
  {:kino_aoc, "~> 0.1.7"}
])

Part One

“Looks like the Chief’s not here. Next!” One of The Historians pulls out a device and pushes the only button on it. After a brief flash, you recognize the interior of the Ceres monitoring station!

As the search for the Chief continues, a small Elf who lives on the station tugs on your shirt; she’d like to know if you could help her with her word search (your puzzle input). She only has to find one word: XMAS.

This word search allows words to be horizontal, vertical, diagonal, written backwards, or even overlapping other words. It’s a little unusual, though, as you don’t merely need to find one instance of XMAS - you need to find all of them. Here are a few ways XMAS might appear, where irrelevant characters have been replaced with .:

..X...
.SAMX.
.A..A.
XMAS.S
.X....

The actual word search will be full of letters instead. For example:

MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX

In this word search, XMAS occurs a total of 18 times; here’s the same word search again, but where letters not involved in any XMAS have been replaced with .:

....XXMAS.
.SAMXMS...
...S..A...
..A.A.MS.X
XMASAMX.MM
X.....XA.A
S.S.S.S.SS
.A.A.A.A.A
..M.M.M.MM
.X.X.XMASX

Take a look at the little Elf’s word search. How many times does XMAS appear?

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2024", "4", System.fetch_env!("LB_AOC2024_SESSION"))
defmodule EnumUtil do
  def transpose(list_of_lists) do
    Enum.zip_with(list_of_lists, &Function.identity/1)
  end

  def rotate(list, n) do
    {a, b} = Enum.split(list, n)
    b ++ a
  end
end

word_search = String.split(puzzle_input)

diag_grids = 
  word_search 
  |> Enum.map(&String.pad_trailing(&1, length(word_search) + String.length(&1)))
  |> Enum.map(&to_charlist/1)
  |> Enum.with_index(&EnumUtil.rotate/2)
  |> EnumUtil.transpose()
  |> Enum.chunk_every(2)
  |> EnumUtil.transpose()
  |> Enum.map(fn diagRows -> Enum.with_index(diagRows, &EnumUtil.rotate(&1, -&2)) |> EnumUtil.rotate(div(length(word_search), 2)) end)

word_search_grid =
 Enum.map(word_search, &String.to_charlist/1)

grids = [word_search_grid | diag_grids]

grids
  |> Enum.flat_map(&[&1, EnumUtil.transpose(&1)])
  |> Enum.concat()
  |> Enum.join("\n")
  |> then(&Regex.scan(~r/X(?=MAS)|S(?=AMX)/, &1))
  |> Enum.count()

Part 2

The Elf looks quizzically at you. Did you misunderstand the assignment?

Looking for the instructions, you flip over the word search to find that this isn’t actually an XMAS puzzle; it’s an X-MAS puzzle in which you’re supposed to find two MAS in the shape of an X. One way to achieve that is like this:

M.S
.A.
M.S

Irrelevant characters have again been replaced with . in the above diagram. Within the X, each MAS can be written forwards or backwards.

Here’s the same example from before, but this time all of the X-MASes have been kept instead:

.M.S......
..A..MSMS.
.M.S.MAA..
..A.ASMSM.
.M.S.M....
..........
S.S.S.S.S.
.A.A.A.A..
M.M.M.M.M.
..........

In this example, an X-MAS appears 9 times.

Flip the word search from the instructions back over to the word search side and try again. How many times does an X-MAS appear?

diag_grids
|> Enum.flat_map(fn grid -> 
  for {row, rowIdx} <- Enum.with_index(grid) |> Enum.drop(1),
      {slice, colIdx} <- Enum.chunk_every(row, 3, 1, :discard) |> Enum.with_index(1),
      slice in [~c/SAM/, ~c/MAS/], 
      Enum.map([-1, +1], &amp;Enum.at(Enum.at(grid, rowIdx + &amp;1, []), colIdx)) in [~c/SM/, ~c/MS/] do
      {rowIdx, colIdx}
    end
end)
|> Enum.count()