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

Day 4

2023/day_4.livemd

Day 4

Mix.install([:kino])

Get input

input =
  Kino.Input.textarea("Paste input here",
    default: """
    Card 1: 41 48 83 86 17 | 83 86  6 31 17  9 48 53
    Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
    Card 3:  1 21 53 59 44 | 69 82 63 72 16 21 14  1
    Card 4: 41 92 73 84 69 | 59 84 76 51 58  5 54 83
    Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
    Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
    """
  )
cards =
  input
  |> Kino.Input.read()
  |> String.split("\n", trim: true)
  |> Enum.map(fn line ->
    [_, card, winners, numbers] =
      Regex.run(~r/Card\s+(\d+):((?:\s+\d+)+)\s+?\|((?:\s+\d+)+)/, line)

    {String.to_integer(card), String.split(winners, " ", trim: true),
     String.split(numbers, " ", trim: true)}
  end)
defmodule Helper do
  def count_winners(winners, numbers) do
    MapSet.intersection(
      MapSet.new(winners),
      MapSet.new(numbers)
    )
    |> MapSet.size()
  end
end

Part 1

cards
|> Stream.map(fn {_, winners, numbers} ->
  Helper.count_winners(winners, numbers)
end)
|> Stream.filter(&Kernel.>(&1, 0))
|> Stream.map(fn match_count -> 2 ** (match_count - 1) end)
|> Enum.sum()

Part 2

cards
|> Stream.map(fn {card, winners, numbers} ->
  {card, Helper.count_winners(winners, numbers)}
end)
# for each card in the stack, get which copies of other cards it wins and increment 
# the win count for each of them in the `count` lookup Map. We also have to remember to
# increment the win count for the current card too.
|> Enum.reduce(%{}, fn
  {n, 0}, counts ->
    Map.update(counts, n, 1, &(&1 + 1))

  {n, wins}, counts ->
    Enum.reduce(1..wins, Map.update(counts, n, 1, &(&1 + 1)), fn j, cc ->
      inc = Map.get(cc, n)

      Map.update(cc, n + j, inc, &(&1 + inc))
    end)
end)
|> Map.values()
|> Enum.sum()