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

Day 4

2021/day-04.livemd

Day 4

Setup

Mix.install([
  {:kino, "~> 0.4.1"}
])
input = Kino.Input.textarea("Please paste your input:")
defmodule Board do
  empty_board = Tuple.duplicate(Tuple.duplicate(false, 5), 5)
  @enforce_keys [:numbers]
  defstruct numbers: %{}, grid: empty_board

  def new(numbers) when is_map(numbers) do
    %Board{numbers: numbers}
  end

  def mark(%Board{numbers: numbers} = board, number) do
    case numbers do
      %{^number => {row, col}} ->
        put_in(board, [Access.key(:grid), Access.elem(row), Access.elem(col)], true)

      %{} ->
        board
    end
  end

  def unmarked_sum(%Board{numbers: numbers, grid: grid}) do
    Enum.sum(
      for {number, {row, col}} <- numbers,
          grid |> elem(row) |> elem(col) == false,
          do: number
    )
  end

  def won?(%Board{grid: grid}) do
    row_won?(grid) or column_won?(grid)
  end

  defp column_won?(grid) do
    Enum.any?(0..4, fn col ->
      Enum.all?(0..4, fn row -> grid |> elem(row) |> elem(col) end)
    end)
  end

  defp row_won?(grid) do
    Enum.any?(0..4, fn row ->
      elem(grid, row) == {true, true, true, true, true}
    end)
  end
end
[numbers | grids] =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)

boards =
  grids
  |> Enum.chunk_every(5)
  |> Enum.map(fn rows ->
    Board.new(
      for {line, row} <- Enum.with_index(rows, 0),
          {number, col} <- Enum.with_index(String.split(line), 0),
          into: %{} do
        {String.to_integer(number), {row, col}}
      end
    )
  end)

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

Part 1

{number, board = %Board{}} =
  Enum.reduce_while(numbers, boards, fn number, boards ->
    boards = Enum.map(boards, &amp;Board.mark(&amp;1, number))

    if board = Enum.find(boards, &amp;Board.won?/1) do
      {:halt, {number, board}}
    else
      {:cont, boards}
    end
  end)

number * Board.unmarked_sum(board)

Part 2

{number, board = %Board{}} =
  Enum.reduce_while(numbers, boards, fn number, boards ->
    boards = Enum.map(boards, &amp;Board.mark(&amp;1, number))

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

      boards ->
        {:cont, boards}
    end
  end)

number * Board.unmarked_sum(board)