Powered by AppSignal & Oban Pro

d04

d04/d04.livemd

d04

Section

defmodule D04 do
  # @ can also be used for constants, but it seems to be preferred
  # in guards or places where a function would not work.
  def deltas, do: for(dr <- -1..1, dc <- -1..1, do: {dr, dc}) -- [{0, 0}]

  def parse(input) do
    lists =
      String.split(input, "\n", trim: true)
      # charlist are deprecated but more convenient than Unicode strings.
      |> Enum.map(&String.to_charlist/1)

    for {row, i} <- Enum.with_index(lists),
        {letter, j} <- Enum.with_index(row),
        into: %{} do
      {{i, j}, letter}
    end
  end

  def matches?(_, [], _, _), do: true

  def matches?(grid, [x | xs], {r, c}, {dr, dc}) do
    Map.get(grid, {r, c}) == x &&
      D04.matches?(grid, xs, {r + dr, c + dc}, {dr, dc})
  end

  def part1(grid) do
    {height, width} = Enum.max(Map.keys(grid))

    for row <- 0..height,
        col <- 0..width,
        delta <- D04.deltas() do
      D04.matches?(grid, ~c"XMAS", {row, col}, delta)
    end
    |> Enum.count(& &1)
  end

  defp part2_pattern?(~c"AMMSS"), do: true
  defp part2_pattern?(~c"AMSMS"), do: true
  defp part2_pattern?(~c"ASMSM"), do: true
  defp part2_pattern?(~c"ASSMM"), do: true
  defp part2_pattern?(_), do: false

  def part2(grid) do
    {height, width} = Enum.max(Map.keys(grid))

    for row <- 0..height,
        col <- 0..width do
      cross = [{0,0},{-1,-1},{-1,1},{1,-1},{1,1}]
        |> Enum.map(fn {dr, dc} -> Map.get(grid, {row + dr, col + dc}) end)
      if part2_pattern?(cross) do
        1
      else
        0
      end
    end
    |> Enum.sum()
  end
end
sample = "MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX
"

sample_grid = D04.parse(sample)
18 = D04.part1(sample_grid)
9 = D04.part2(sample_grid)
input = File.read!(__DIR__ <> "/input")
grid = D04.parse(input)
D04.part1(grid)
D04.part2(grid)