Powered by AppSignal & Oban Pro

Day 4

2021/notebooks/day-04.livemd

Day 4

Setup

input = Aoc.get_input(4)
textarea = Kino.Input.textarea("Puzzle input", default: input)
test_input = """
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
"""
textarea_test = Kino.Input.textarea("Test input", default: test_input)
lines =
  textarea
  |> Kino.Input.read()
  |> String.split("\n\n")
  |> Enum.map(&String.split(&1, "\n", trim: true))
test_lines =
  textarea_test
  |> Kino.Input.read()
  |> String.split("\n\n")
  |> Enum.map(&String.split(&1, "\n", trim: true))
options = [
  puzzle: "Puzzle",
  test: "Test"
]

select = Kino.Input.select("Input Source", options)
lines =
  select
  |> Kino.Input.read()
  |> case do
    :puzzle -> lines
    :test -> test_lines
  end

:ok

Part 1

[numbers | boards] = lines
numbers = Enum.at(numbers, 0) |> String.split(",") |> IO.inspect(label: "numbers")

boards =
  boards
  |> Enum.map(fn rows ->
    rows
    |> Enum.map(&(String.split(&1, ~r{\s+}, trim: true) |> Enum.map(fn a -> {a, false} end)))
  end)
{winning_board, last_call} =
  numbers
  |> Enum.reduce_while(boards, fn n, boards ->
    # update boards
    boards =
      boards
      |> Enum.map(fn rows ->
        rows
        |> Enum.map(fn row ->
          row
          |> Enum.map(fn
            {^n, false} -> {n, true}
            r -> r
          end)
        end)
      end)

    boards
    |> Enum.find(fn rows ->
      # check if winner
      winning_row? =
        rows
        |> Enum.find(fn
          [{_, true}, {_, true}, {_, true}, {_, true}, {_, true}] -> true
          _ -> false
        end)

      winning_col? =
        rows
        |> Enum.reduce(%{}, fn row, acc ->
          row
          |> Enum.with_index()
          |> Enum.reduce(acc, fn {{_, bool}, i}, acc ->
            if bool == true do
              Map.update(acc, i, 1, fn v -> v + 1 end)
            else
              acc
            end
          end)
        end)
        |> Enum.find(fn {_k, v} -> v == 5 end)

      winning_row? || winning_col?
    end)
    |> case do
      nil ->
        {:cont, boards}

      board ->
        {:halt, {board, n}}
    end
  end)
%{false: unmarked, true: marked} =
  winning_board
  |> Enum.flat_map(& &1)
  |> Enum.group_by(fn {_n, b?} -> b? end)

sum =
  unmarked
  |> Enum.map(fn {n, _} -> n |> String.to_integer() end)
  |> IO.inspect(label: "unmarked")
  |> Enum.sum()
  |> IO.inspect(label: "sum")

marked |> Enum.map(fn {n, _} -> n |> String.to_integer() end) |> IO.inspect(label: "marked")

last_call = String.to_integer(last_call) |> IO.inspect(label: "last call")
score = sum * last_call

Part 2

[numbers | boards] = lines
numbers = Enum.at(numbers, 0) |> String.split(",") |> IO.inspect(label: "numbers")

boards =
  boards
  |> Enum.map(fn rows ->
    rows
    |> Enum.map(&(String.split(&1, ~r{\s+}, trim: true) |> Enum.map(fn a -> {a, false} end)))
  end)
{winning_board, last_call} =
  numbers
  |> Enum.reduce_while(boards, fn n, boards ->
    # update boards
    boards =
      boards
      |> Enum.map(fn rows ->
        rows
        |> Enum.map(fn row ->
          row
          |> Enum.map(fn
            {^n, false} -> {n, true}
            r -> r
          end)
        end)
      end)

    boards
    |> Enum.reject(fn rows ->
      # check if winner
      winning_row? =
        rows
        |> Enum.find(fn
          [{_, true}, {_, true}, {_, true}, {_, true}, {_, true}] -> true
          _ -> false
        end)

      winning_col? =
        rows
        |> Enum.reduce(%{}, fn row, acc ->
          row
          |> Enum.with_index()
          |> Enum.reduce(acc, fn {{_, bool}, i}, acc ->
            if bool == true do
              Map.update(acc, i, 1, fn v -> v + 1 end)
            else
              acc
            end
          end)
        end)
        |> Enum.find(fn {_k, v} -> v == 5 end)

      winning_row? || winning_col?
    end)
    |> case do
      [] ->
        [board] = boards
        {:halt, {board, n}}

      boards ->
        {:cont, boards}
    end
  end)
%{false: unmarked, true: marked} =
  winning_board
  |> Enum.flat_map(& &1)
  |> Enum.group_by(fn {_n, b?} -> b? end)

sum =
  unmarked
  |> Enum.map(fn {n, _} -> n |> String.to_integer() end)
  |> IO.inspect(label: "unmarked", charlists: :as_lists)
  |> Enum.sum()
  |> IO.inspect(label: "sum")

marked |> Enum.map(fn {n, _} -> n |> String.to_integer() end) |> IO.inspect(label: "marked")

last_call = String.to_integer(last_call) |> IO.inspect(label: "last call")
score = sum * last_call