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

Day 04

2021/day-04.livemd

Day 04

Setup

Mix.install([
  {:kino, "~> 0.4.0"}
])

Example input

example_input = Kino.Input.textarea("example input:")

Real input

real_input = Kino.Input.textarea("real input: ")

Part 1

[draws | boards] =
  real_input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)

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

boards =
  boards
  |> Enum.map(&String.split/1)
  |> Enum.map(&Enum.map(&1, fn n -> {String.to_integer(n), false} end))
  |> Enum.chunk_every(5)

apply_draw = fn
  board, draw ->
    board
    |> Enum.map(fn row ->
      row
      |> Enum.map(fn
        {num, false} when num == draw ->
          {num, true}

        row ->
          row
      end)
    end)
end

winning_row? = fn
  board ->
    board
    |> Enum.any?(fn row ->
      row
      |> Enum.all?(&elem(&1, 1))
    end)
end

winning_column? = fn
  board ->
    board
    |> Enum.zip()
    |> Enum.map(&Tuple.to_list/1)
    |> winning_row?.()
end

winning? = fn
  board -> winning_row?.(board) || winning_column?.(board)
end

draws
|> Enum.reduce_while(boards, fn draw, boards ->
  boards =
    boards
    |> Enum.map(&apply_draw.(&1, draw))

  winning_board =
    boards
    |> Enum.find(&winning?.(&1))

  if winning_board do
    sum =
      winning_board
      |> Enum.reduce(0, fn row, acc ->
        row
        |> Enum.reject(&elem(&1, 1))
        |> Enum.map(&elem(&1, 0))
        |> Enum.sum()
        |> Kernel.+(acc)
      end)

    {:halt, sum * draw}
  else
    {:cont, boards}
  end
end)

Part 2

[draws | boards] =
  real_input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)

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

boards =
  boards
  |> Enum.map(&String.split/1)
  |> Enum.map(&Enum.map(&1, fn n -> {String.to_integer(n), false} end))
  |> Enum.chunk_every(5)

apply_draw = fn
  board, draw ->
    board
    |> Enum.map(fn row ->
      row
      |> Enum.map(fn
        {num, false} when num == draw ->
          {num, true}

        row ->
          row
      end)
    end)
end

winning_row? = fn
  board ->
    board
    |> Enum.any?(fn row ->
      row
      |> Enum.all?(&elem(&1, 1))
    end)
end

winning_column? = fn
  board ->
    board
    |> Enum.zip()
    |> Enum.map(&Tuple.to_list/1)
    |> winning_row?.()
end

winning? = fn
  board -> winning_row?.(board) || winning_column?.(board)
end

draws
|> Enum.reduce_while(boards, fn draw, boards ->
  boards =
    boards
    |> Enum.map(&apply_draw.(&1, draw))

  if Enum.all?(boards, &winning?.(&1)) do
    sum =
      boards
      |> hd()
      |> Enum.reduce(0, fn row, acc ->
        row
        |> Enum.reject(&elem(&1, 1))
        |> Enum.map(&elem(&1, 0))
        |> Enum.sum()
        |> Kernel.+(acc)
      end)

    {:halt, sum * draw}
  else
    {:cont, boards |> Enum.reject(&winning?.(&1))}
  end
end)