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

Day 4

d04.livemd

Day 4

Section

defmodule D04 do
  def parse_input(input) do
    cards = String.split(input, "\n", trim: true)

    Enum.map(cards, fn card ->
      [id, numbers] = String.split(card, ": ")
      id = String.replace_prefix(id, "Card ", "") |> String.trim() |> String.to_integer()

      numbers = numbers |> String.split(" | ")

      [winning_numbers, numbers_you_have] =
        Enum.map(numbers, fn numbers ->
          String.split(numbers)
          |> Enum.map(&String.trim/1)
          |> Enum.map(&String.to_integer/1)
        end)

      %{id: id, winning_numbers: winning_numbers, numbers_you_have: numbers_you_have}
    end)
  end

  def count_wins(input) do
    Enum.map(input, fn card ->
      MapSet.new(card.numbers_you_have)
      |> MapSet.intersection(MapSet.new(card.winning_numbers))
      |> MapSet.size()
    end)
  end

  def p1(input) do
    count_wins(input)
    |> Enum.map(fn wins ->
      if wins == 0, do: 0, else: 2 ** (wins - 1)
    end)
    |> Enum.sum()
  end

  def card_id_to_produced_copies(input) do
    count_wins(input)
    # associate each card_id with the number of follow up cards to copy
    |> Enum.with_index(fn el, idx -> {idx + 1, el} end)
    # associate each card_id with a list of card ids it will copy
    |> Enum.map(fn {id, copies} ->
      if copies > 0 do
        {id, Enum.map(1..copies, fn x -> x + id end)}
      else
        {id, []}
      end
    end)
    # starting from the last card (it will never produce additional copies)...
    |> Enum.reverse()
    # maintaining a memo of how many total copies each card will win 
    # together with its copied card wins count the total copies produced by each card
    |> Enum.reduce(
      %{},
      fn
        {card_id, []}, memo ->
          Map.put(memo, card_id, 0)

        {card_id, wins_card_ids}, memo ->
          copies_to_win = Enum.map(wins_card_ids, &Map.get(memo, &1)) |> Enum.sum()
          Map.put(memo, card_id, copies_to_win + length(wins_card_ids))
      end
    )
  end

  def p2(input) do
    total_copies_produced = card_id_to_produced_copies(input) |> Map.values() |> Enum.sum()
    total_copies_produced + length(input)
  end
end
{:module, D04, <<70, 79, 82, 49, 0, 0, 22, ...>>, {:p2, 1}}
ExUnit.start(autorun: false)

defmodule D04Test do
  use ExUnit.Case, async: true

  @test_input D04.parse_input("""
              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
              """)

  @input Path.join(__DIR__, "inputs/d04") |> File.read!() |> D04.parse_input()

  test "part 1 works" do
    assert D04.p1(@test_input) == 13
    assert D04.p1(@input) == 23441
  end

  test "part 2 works" do
    assert D04.p2(@test_input) == 30
    assert D04.p2(@input) == 5_923_918
  end
end

ExUnit.run()
..
Finished in 0.00 seconds (0.00s async, 0.00s sync)
2 tests, 0 failures

Randomized with seed 728407
%{total: 2, failures: 0, excluded: 0, skipped: 0}