Powered by AppSignal & Oban Pro

Advent of Code 2021 - Day 4

2021/day04.livemd

Advent of Code 2021 - Day 4

Utils

defmodule Utils do
  def read_textarea(name) do
    Stream.iterate("", fn _ -> IO.gets(name) end)
    |> Stream.take_while(&(&1 != :eof))
    |> Enum.join("\r\n")
  end
end
{:module, Utils, <<70, 79, 82, 49, 0, 0, 7, ...>>, {:read_textarea, 1}}

Part 1

defmodule Board do
  def mark(board, drawn_number) do
    for row <- board do
      for number <- row do
        if drawn_number == number do
          -1
        else
          number
        end
      end
    end
  end

  def score(board, last_number) do
    board
    |> List.flatten()
    |> Enum.filter(fn number -> number != -1 end)
    |> Enum.sum()
    |> then(fn sum -> sum * last_number end)
  end

  def bingo?(board) do
    cond do
      row_bingo?(board) -> true
      col_bingo?(board) -> true
      true -> false
    end
  end

  defp row_bingo?(board) do
    Enum.any?(board, fn row ->
      Enum.all?(row, fn number ->
        number == -1
      end)
    end)
  end

  defp col_bingo?(board) do
    Enum.any?(0..4, fn col_num ->
      Enum.all?(0..4, fn row_num ->
        number =
          board
          |> Enum.at(row_num)
          |> Enum.at(col_num)

        number == -1
      end)
    end)
  end
end

defmodule Day4 do
  def solve(input) do
    {drawn_numbers, boards} = parse_input(input)
    {winner, last_number} = find_winning_board(drawn_numbers, boards)
    Board.score(winner, last_number)
  end

  defp find_winning_board([number | drawn_numbers], boards) do
    boards = Enum.map(boards, &amp;Board.mark(&amp;1, number))

    case Enum.find(boards, &amp;Board.bingo?/1) do
      nil -> find_winning_board(drawn_numbers, boards)
      winner -> {winner, number}
    end
  end

  def parse_input(input) do
    {moves, input} = parse_moves(input)
    boards = parse_boards(input)
    {moves, boards}
  end

  defp parse_moves(input) do
    [moves_str, input] = String.split(input, ~r{\s}, trim: true, parts: 2)

    moves =
      moves_str
      |> String.split(",")
      |> Enum.map(&amp;String.to_integer/1)

    {moves, String.trim(input)}
  end

  defp parse_boards(input) do
    input
    |> String.split(~r{\s}, trim: true)
    |> Enum.map(&amp;String.to_integer/1)
    |> Enum.chunk_every(5)
    |> Enum.chunk_every(5)
  end
end

Day4.solve(Utils.read_textarea("example_input"))
Day4.solve(Utils.read_textarea("my_input"))
89001

Part2

defmodule Day4.Part2 do
  def solve(input) do
    {drawn_numbers, boards} = Day4.parse_input(input)
    {winner, last_number} = find_last_winning_board(drawn_numbers, boards)
    Board.score(winner, last_number)
  end

  defp find_last_winning_board([number | drawn_numbers], boards) do
    {winning_boards, boards} =
      boards
      |> Enum.map(&amp;Board.mark(&amp;1, number))
      |> Enum.split_with(&amp;Board.bingo?/1)

    case boards do
      [] -> {Enum.at(winning_boards, 0), number}
      _ -> find_last_winning_board(drawn_numbers, boards)
    end
  end
end

Day4.Part2.solve(Utils.read_textarea("example_input"))
Day4.Part2.solve(Utils.read_textarea("my_input"))
7296