Powered by AppSignal & Oban Pro

Day 4: Giant Squid

2021/day_04_giant_squid.livemd

Day 4: Giant Squid

Mix.install([:kino])

input = Kino.Input.textarea("Please paste your input:")

Setup

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

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

boards =
  boards_str
  |> Enum.chunk_every(5)
  |> Enum.map(fn board_str ->
    board_str
    |> Enum.map(fn board_line ->
      board_line |> String.split() |> Enum.map(&String.to_integer/1)
    end)
  end)

Part 1

https://adventofcode.com/2021/day/4

Solve: Part 1

defmodule Day4.Part1.Bingo do
  def has_bingo?(board) do
    rows = board
    columns = board |> transpost()

    [rows, columns] |> Enum.any?(&do_has_bingo?(&1))
  end

  def mark_number(board, number) do
    board
    |> Enum.map(fn line -> line |> replace_number_with_nil(number) end)
  end

  def calc_score(board, bingo_number) do
    unmarked_number_sum = board |> List.flatten() |> Enum.reject(&is_nil(&1)) |> Enum.sum()

    unmarked_number_sum * bingo_number
  end

  defp do_has_bingo?(lines) do
    lines |> Enum.any?(fn line -> Enum.all?(line, &is_nil(&1)) end)
  end

  defp replace_number_with_nil(line, number) do
    line
    |> Enum.map(fn
      ^number -> nil
      other -> other
    end)
  end

  defp transpost(board) do
    board |> Enum.zip_with(& &1)
  end
end

alias Day4.Part1.Bingo

{_, bingo_number, bingo_board} =
  numbers
  |> Enum.reduce_while(
    {boards, _bingo_number = nil, _bingo_board = nil},
    fn number, {boards, nil, nil} ->
      new_boards = boards |> Enum.map(&Bingo.mark_number(&1, number))

      case new_boards |> Enum.find(&(&1 |> Bingo.has_bingo?())) do
        nil ->
          {:cont, {new_boards, nil, nil}}

        bingo_board ->
          {:halt, {new_boards, number, bingo_board}}
      end
    end
  )

_answer = Bingo.calc_score(bingo_board, bingo_number)

Part 2

https://adventofcode.com/2021/day/4#part2

Solve: Part 2

{_, bingo_number, bingo_board} =
  numbers
  |> Enum.reduce_while({boards, _bingo_number = nil, _bingo_board = nil}, fn number,
                                                                             {boards, nil, nil} ->
    {bingo_boards, rest_boards} =
      boards
      |> Enum.map(&Bingo.mark_number(&1, number))
      |> Enum.split_with(&Bingo.has_bingo?(&1))

    case {bingo_boards, rest_boards} do
      {[last_board], []} -> {:halt, {[], number, last_board}}
      _ -> {:cont, {rest_boards, nil, nil}}
    end
  end)

_answer = Bingo.calc_score(bingo_board, bingo_number)