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

Advent of Code 2021 day 04

2021/priv/day-04.livemd

Advent of Code 2021 day 04

Setup

Mix.install([
  {:kino, github: "livebook-dev/kino"}
])
input = Kino.Input.textarea("Please paste your input file:")

Part 1

defmodule Day4 do
  def build_board(line) do
    list = flatten(line)

    for i <- 0..4, j <- 0..4, into: %{} do
      {[i, j], {Enum.at(list, i * 5 + j), false}}
    end
  end

  defp flatten(line) do
    line
    |> String.split("\n")
    |> Enum.flat_map(&amp;String.split(&amp;1, " ", trim: true))
  end

  def col_marks(board) do
    for i <- 0..4, do: for(j <- 0..4, do: board |> Map.get([j, i]) |> then(&amp;elem(&amp;1, 1)))
  end

  def row_marks(board) do
    for i <- 0..4, do: for(j <- 0..4, do: board |> Map.get([i, j]) |> then(&amp;elem(&amp;1, 1)))
  end

  def mark(board, draw) do
    board
    |> Enum.find(fn {_k, {str, _}} -> str == draw end)
    |> then(&amp;maybe_update(board, &amp;1))
  end

  defp maybe_update(board, nil), do: board

  defp maybe_update(board, {key, {draw, _}}) do
    Map.replace(board, key, {draw, true})
  end

  def bingo?(board) do
    lines = row_marks(board) ++ col_marks(board)
    Enum.any?(lines, &amp;Enum.all?/1)
  end

  def unmarked_points(board) do
    board
    |> Map.values()
    |> Enum.reject(&amp;elem(&amp;1, 1))
    |> Enum.map(&amp;elem(&amp;1, 0))
    |> Enum.map(&amp;String.to_integer/1)
    |> Enum.sum()
  end

  def score({board, draw}) do
    board
    |> unmarked_points()
    |> then(fn i -> i * String.to_integer(draw) end)
  end
end

[draw_line | board_str] =
  input
  |> Kino.Input.read()
  |> String.split("\n\n", trim: true)

draws = String.split(draw_line, ",", trim: true)
boards = Enum.map(board_str, &amp;Day4.build_board/1)

Enum.reduce_while(draws, boards, fn draw, boards ->
  boards = Enum.map(boards, &amp;Day4.mark(&amp;1, draw))

  if board = Enum.find(boards, &amp;Day4.bingo?/1) do
    {:halt, {board, draw}}
  else
    {:cont, boards}
  end
end)
|> then(&amp;Day4.score/1)

Part 2

Enum.reduce_while(draws, boards, fn draw, boards ->
  boards = Enum.map(boards, &amp;Day4.mark(&amp;1, draw))

  case Enum.reject(boards, &amp;Day4.bingo?/1) do
    [] ->
      [board] = boards
      {:halt, {board, draw}}

    boards ->
      {:cont, boards}
  end
end)
|> Day4.score()