Powered by AppSignal & Oban Pro

Day 4

2021/day04.livemd

Day 4

Input

This time it is a little bit more convoluted, as there are 2 parts of the input. Fortunately we can easily disect the parts via pattern matching.

Technically the conversion to the numbers is not needed, but it does no harm and provides additional layer of safety against some whitespace characters left there and here.

The Day4.win/2 function is manually unrolled, as it is easier to write than some random jumping in the list.

[numbers | bingos] =
  File.read!("day4.txt")
  |> String.split("\n\n", trim: true)

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

bingos =
  bingos
  |> Enum.map(fn bingo ->
    bingo
    |> String.split(~r/\s+/, trim: true)
    |> Enum.map(&String.to_integer/1)
  end)

defmodule Day4 do
  def win(
        [
          a1, a2, a3, a4, a5,
          b1, b2, b3, b4, b5,
          c1, c2, c3, c4, c5,
          d1, d2, d3, d4, d5,
          e1, e2, e3, e4, e5
        ],
        nums
      ) do
    # Rows
    all_in([a1, a2, a3, a4, a5], nums) or
    all_in([b1, b3, b3, b4, b5], nums) or
    all_in([c1, c2, c3, c4, c5], nums) or
    all_in([d1, d2, d3, d4, d5], nums) or
    all_in([e1, e2, e3, e4, e5], nums) or
    # Columns
    all_in([a1, b1, c1, d1, e1], nums) or
    all_in([a2, b2, c2, d2, e2], nums) or
    all_in([a3, b3, c3, d3, e3], nums) or
    all_in([a4, b4, c4, d4, e4], nums) or
    all_in([a5, b5, c5, d5, e5], nums)
  end

  def not_matched(bingo, nums) do
    Enum.reject(bingo, &(&1 in nums))
  end

  defp all_in(list, nums) do
    Enum.all?(list, &(&1 in nums))
  end
end
{:module, Day4, <<70, 79, 82, 49, 0, 0, 15, ...>>, {:all_in, 2}}

Task 1

We simply traverse the numbers list aggregating the numbers (order doesn’t really matter, here we aggregate them in reverse order to speedup the code). When we have enough numbers that any of the bingos is winning one, then we halt the reduction and return computed result.

numbers
|> Enum.reduce_while([], fn elem, acc ->
  matches = [elem | acc]

  case Enum.find(bingos, &amp;Day4.win(&amp;1, matches)) do
    nil -> {:cont, matches}
    bingo -> {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem}
  end
end)
34506

Task 2

numbers
|> Enum.reduce_while({bingos, []}, fn elem, {bingos, acc} ->
  matches = [elem | acc]

  case bingos do
    [bingo] ->
      if Day4.win(bingo, matches) do
        {:halt, Enum.sum(Day4.not_matched(bingo, matches)) * elem}
      else
        {:cont, {bingos, matches}}
      end

    _ ->
      {:cont, {Enum.reject(bingos, &amp;Day4.win(&amp;1, matches)), matches}}
  end
end)
7686