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

Advent of Code 2021 / D04

day04.livemd

Advent of Code 2021 / D04

Giant Squid - Setup

input =
  "inputs/day04.txt"
  |> File.read!()
testInput = """
7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1

22 13 17 11  0
 8  2 23  4 24
21  9 14 16  7
 6 10  3 18  5
 1 12 20 15 19

 3 15  0  2 22
 9 18 13 17  5
19  8  7 25 23
20 11 10 24  4
14 21 16 12  6

14 21 17 24  4
10 16 15  9 19
18  8 23 26 20
22 11 13  6  5
 2  0 12  3  7
"""

defmodule Bingo do
  def draw_numbers(input) do
    input
    |> String.split("\n", trim: true)
    |> hd
    |> String.split(",")
    |> Enum.map(&String.to_integer(&1, 10))
  end

  def boards(input) do
    input
    |> String.split("\n", trim: true)
    |> then(fn [_hd | tl] -> tl end)
    |> Enum.map(&String.split(&1, ~r{\s+}))
    |> Enum.map(fn n ->
      Enum.filter(n, &(String.length(&1) > 0))
      |> Enum.map(fn n -> {String.to_integer(n, 10), 0} end)
    end)
    |> Enum.chunk_every(5)
  end

  def mark_board(board, pick) do
    board
    |> Enum.map(fn row ->
      row
      |> Enum.map(fn {number, mark} ->
        if number == pick, do: {number, 1}, else: {number, mark}
      end)
    end)
  end

  defp axis_sum(axis) do
    Enum.reduce(axis, 0, fn {_number, mark}, acc -> if mark == 1, do: acc + 1, else: acc end)
  end

  defp axis_sum(axis, value_check) do
    Enum.reduce(axis, 0, fn {number, mark}, acc ->
      if mark == value_check, do: acc + number, else: acc
    end)
  end

  def check_winner(board) do
    row_won? =
      board
      |> Enum.map(fn row ->
        row
        |> Enum.reduce(0, fn {_number, mark}, acc -> if mark == 1, do: acc + 1, else: acc end)
      end)
      |> Enum.any?(fn x -> x == 5 end)

    col_won? =
      Enum.any?(0..4, fn col ->
        column = Enum.map(0..4, fn i -> board |> Enum.at(i) |> Enum.at(col) end)
        axis_sum(column) == 5
      end)

    # IO.inspect({row_won?, col_won?},  label: "check row and colum win")
    Enum.any?([row_won?, col_won?])
  end

  def sum_unmarked(board) do
    Enum.reduce(board, 0, fn row, acc ->
      acc + axis_sum(row, 0)
    end)
  end
end

Part 1

For test input.

# mark boards for number 
# IO.inspect(testInput |> Bingo.boards |> hd, label: "Boards")
boards = Bingo.boards(testInput)
draw_numbers = Bingo.draw_numbers(testInput)

{board, number} =
  Enum.reduce_while(draw_numbers, boards, fn number, boards ->
    boards = Enum.map(boards, fn b -> Bingo.mark_board(b, number) end)

    if board = Enum.find(boards, &Bingo.check_winner(&1)) do
      {:halt, {board, number}}
    else
      {:cont, boards}
    end
  end)

IO.inspect({board, number}, label: "winning board & number")
Bingo.sum_unmarked(board) * number

For realz tho…

boards = Bingo.boards(input)
draw_numbers = Bingo.draw_numbers(input)

{board, number} =
  Enum.reduce_while(draw_numbers, boards, fn number, boards ->
    boards = Enum.map(boards, fn b -> Bingo.mark_board(b, number) end)

    if board = Enum.find(boards, &Bingo.check_winner(&1)) do
      {:halt, {board, number}}
    else
      {:cont, boards}
    end
  end)

IO.inspect({board, number}, label: "winning board & number")
# should be25023
Bingo.sum_unmarked(board) * number

Part 2

boards = Bingo.boards(input)
draw_numbers = Bingo.draw_numbers(input)

{board, number} =
  Enum.reduce_while(draw_numbers, boards, fn number, boards ->
    # IO.inspect(number, label: "\nPick")
    post_marked_boards = Enum.map(boards, fn b -> Bingo.mark_board(b, number) end)

    boards =
      post_marked_boards
      # |> IO.inspect(label: "predrop")
      |> Enum.reject(&Bingo.check_winner(&1))

    # IO.inspect(boards, label: "postdrop")
    case boards do
      [] -> {:halt, {post_marked_boards |> hd, number}}
      _ -> {:cont, boards}
    end
  end)

IO.inspect({board, number}, label: "last winning board & number")
# should be 1924 for test input, 2634 for real input
Bingo.sum_unmarked(board) * number

Learnings

The data structure of the boards is important. I used a tuple, e.g. {number, mark} for each “square”. José’s solution used this data structure:

[
  %Board{
    grid:
      {{false, false, false, false, false}, {false, false, false, false, false},
       {false, false, false, false, false}, {false, false, false, false, false},
       {false, false, false, false, false}},
    numbers: %{
      0 => {0, 4},
      1 => {4, 0},
      2 => {1, 1},
      3 => {3, 2},
      4 => {1, 3},
      5 => {3, 4},
      6 => {3, 0},
      7 => {2, 4},
      8 => {1, 0},
      9 => {2, 1},
      10 => {3, 1},
      11 => {0, 3},
      12 => {4, 1},
      13 => {0, 1},
      14 => {2, 2},
      15 => {4, 3},
      16 => {2, 3},
      17 => {0, 2},
      18 => {3, 3},
      19 => {4, 4},
      20 => {4, 2},
      21 => {2, 0},
      22 => {0, 0},
      23 => {1, 2},
      24 => {1, 4}
    }
  },
  %Board{
    grid:
      {{false, false, false, false, false}, {false, false, false, false, false},
       {false, false, false, false, false}, {false, false, false, false, false},
       {false, false, false, false, false}},
    numbers: %{
      0 => {0, 2},
      2 => {0, 3},
      3 => {0, 0},
      4 => {3, 4},
      5 => {1, 4},
      6 => {4, 4},
      7 => {2, 2},
      8 => {2, 1},
      9 => {1, 0},
      10 => {3, 2},
      11 => {3, 1},
      12 => {4, 3},
      13 => {1, 2},
      14 => {4, 0},
      15 => {0, 1},
      16 => {4, 2},
      17 => {1, 3},
      18 => {1, 1},
      19 => {2, 0},
      20 => {3, 0},
      21 => {4, 1},
      22 => {0, 4},
      23 => {2, 4},
      24 => {3, 3},
      25 => {2, 3}
    }
  },
  %Board{
    grid:
      {{false, false, false, false, false}, {false, false, false, false, false},
       {false, false, false, false, false}, {false, false, false, false, false},
       {false, false, false, false, false}},
    numbers: %{
      0 => {4, 1},
      2 => {4, 0},
      3 => {4, 3},
      4 => {0, 4},
      5 => {3, 4},
      6 => {3, 3},
      7 => {4, 4},
      8 => {2, 1},
      9 => {1, 3},
      10 => {1, 0},
      11 => {3, 1},
      12 => {4, 2},
      13 => {3, 2},
      14 => {0, 0},
      15 => {1, 2},
      16 => {1, 1},
      17 => {0, 2},
      18 => {2, 0},
      19 => {1, 4},
      20 => {2, 4},
      21 => {0, 1},
      22 => {3, 0},
      23 => {2, 2},
      24 => {0, 3},
      26 => {2, 3}
    }
  }
]

What’s intersting is how he denormalized the bingo boards and separated the “square’s value” from the position of the numbers on the board. I kept the structure and struggled with finding the right elixir functions to help rangled the data structures.