Powered by AppSignal & Oban Pro

Advent of code day 04

2021/livebooks/day-04.livemd

Advent of code day 04

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

Setup input

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

Helpers

# Grid Helper to the following grid format 
#
# [
#   [%{number: 2, marked: false}, %{number: 1, marked: false}],
#   [%{number: 4, marked: false}, %{number: 3, marked: false}]
# ]
#
defmodule GridHelper do
  def mark_number(grid, number) do
    Enum.map(grid, fn row ->
      Enum.map(row, fn element ->
        if element.number == number do
          %{element | marked: true}
        else
          element
        end
      end)
    end)
  end

  def won?(grid) do
    has_row_with_all_marked_true(grid) or has_col_with_all_marked_true(grid)
  end

  def get_all_unmarked_numbers(grid) do
    Enum.map(grid, fn row ->
      Enum.filter(row, fn el -> el.marked == false end)
      |> Enum.map(fn %{number: number} -> number end)
    end)
  end

  defp has_row_with_all_marked_true(grid) do
    Enum.any?(grid, fn row -> all_marked_true(row) end)
  end

  defp has_col_with_all_marked_true(grid) do
    transposed_grid = transpose(grid)
    Enum.any?(transposed_grid, fn col -> all_marked_true(col) end)
  end

  defp all_marked_true(row_or_col) do
    Enum.all?(row_or_col, fn
      %{marked: true} -> true
      _ -> false
    end)
  end

  defp transpose(grid), do: Enum.zip_with(grid, &Function.identity/1)
end
[numbers | rest] = input |> Kino.Input.read() |> String.split(["\n", "\n\n"], trim: true)

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

grids =
  rest
  |> Enum.chunk_every(5)
  |> Enum.map(fn el ->
    el
    |> Enum.map(
      &(String.split(&1, " ", trim: true)
        |> Enum.map(fn el ->
          number = el |> String.to_integer()

          %{number: number, marked: false}
        end))
    )
  end)

Part 01

{winner_grid, winning_number} =
  Enum.reduce_while(numbers, grids, fn number, acc ->
    ## mark the number 
    ## check if one grid has won, 
    ## if it has returns it {grid, winning_number}
    grs = Enum.map(acc, &GridHelper.mark_number(&1, number))

    winner =
      Enum.find_value(grs, fn grid ->
        if GridHelper.won?(grid), do: grid, else: nil
      end)

    if winner == nil do
      {:cont, grs}
    else
      {:halt, {winner, number}}
    end
  end)

winner_grid
|> GridHelper.get_all_unmarked_numbers()
|> List.flatten()
|> Enum.sum()
|> Kernel.*(winning_number)

Part 02

{last_winner_grid, winning_number} =
  Enum.reduce_while(numbers, grids, fn number, acc ->
    ## mark the number 
    ## check if one grid has won, 
    ## if it has removes from grids until there is nothing left
    grs = Enum.map(acc, &GridHelper.mark_number(&1, number))

    last_winner =
      Enum.find_value(grs, fn grid ->
        if GridHelper.won?(grid), do: grid, else: nil
      end)

    grs = Enum.filter(grs, fn grid -> not GridHelper.won?(grid) end)

    if not Enum.empty?(grs) do
      {:cont, grs}
    else
      {:halt, {last_winner, number}}
    end
  end)

last_winner_grid
|> GridHelper.get_all_unmarked_numbers()
|> List.flatten()
|> Enum.sum()
|> Kernel.*(winning_number)