Powered by AppSignal & Oban Pro

aoc2021-day4

2021/elixir/day-4.livemd

aoc2021-day4

Setup

Mix.install([{:kino, "~> 0.4.0"}])
input = Kino.Input.textarea("")

Part 1

defmodule Bingo do
  def make_board_map(raw_board) do
    raw_board
    |> Enum.with_index()
    |> Enum.reduce(%{}, fn {line, r}, m ->
      0..4
      |> Enum.map(&{{r, &1}, {elem(line, &1), false}})
      |> Enum.into(m)
    end)
  end

  def play([], _boards), do: raise("no winner")

  def play([drawn | rest], boards) do
    next_boards =
      boards
      |> Enum.map(&mark(&1, drawn))

    case Enum.find(next_boards, &check_bingo(&1)) do
      nil -> play(rest, next_boards)
      board -> get_score(board, drawn)
    end
  end

  # this is for Part 2
  def play_to_lose([], _boards), do: raise("no winner")

  def play_to_lose([drawn | rest], boards) do
    next_boards =
      boards
      |> Enum.map(&mark(&1, drawn))

    case Enum.reject(next_boards, &check_bingo(&1)) do
      [] -> get_score(hd(next_boards), drawn)
      without_winner -> play_to_lose(rest, without_winner)
    end
  end

  def mark(board_map, drawn) do
    Enum.map(board_map, fn
      {k, {^drawn, false}} ->
        {k, {drawn, true}}

      other ->
        other
    end)
    |> Enum.into(%{})
  end

  def check_bingo(board) do
    0..4
    |> Enum.any?(fn i ->
      0..4 |> Enum.all?(&(board |> Map.get({&1, i}) |> elem(1))) or
        0..4 |> Enum.all?(&(board |> Map.get({i, &1}) |> elem(1)))
    end)
  end

  def get_score(board, last_drawn) do
    board
    |> Enum.filter(fn {_k, v} -> not elem(v, 1) end)
    |> Enum.map(fn {_k, {n, _}} -> String.to_integer(n) end)
    |> Enum.sum()
    |> Kernel.*(String.to_integer(last_drawn))
  end
end
inputs = input |> Kino.Input.read() |> String.split("\n", trim: true)

[drawn_numbers | board_lines] = inputs

boards =
  board_lines
  |> Enum.map(&(String.split(&1, " ", trim: true) |> List.to_tuple()))
  |> Enum.chunk_every(5)
  |> Enum.map(&Bingo.make_board_map/1)

drawn_numbers
|> String.split(",")
|> Bingo.play(boards)

Part 2

inputs = input |> Kino.Input.read() |> String.split("\n", trim: true)

[drawn_numbers | board_lines] = inputs

boards =
  board_lines
  |> Enum.map(&(String.split(&1, " ", trim: true) |> List.to_tuple()))
  |> Enum.chunk_every(5)
  |> Enum.map(&Bingo.make_board_map/1)

drawn_numbers
|> String.split(",")
|> Bingo.play_to_lose(boards)