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}