Day 4: Scratchcards – Advent of Code
input =
File.stream!("/Users/pw/src/weiland/adventofcode/2023/input/04.txt")
|> Stream.map(&String.trim/1)
test_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"
|> String.split("\n")
Parse lines
parse_cards = fn lines ->
lines
|> Stream.map(&String.split(&1, ~r"[:\|]"))
|> Stream.map(fn ["Card " <> card, winnings, chosen] ->
[winnings, chosen]
|> Enum.map(fn lst ->
lst |> String.split(" ", trim: true) |> Enum.map(&String.to_integer/1)
end)
|> then(&[String.to_integer(String.trim(card)) | &1])
end)
end
Part 1
part_one = fn lines ->
lines
|> parse_cards.()
# |> Stream.map(fn [_card, a, b] -> a -- (a -- b) end) # alt for function below
|> Stream.map(fn [_card, a, b] ->
MapSet.intersection(MapSet.new(a), MapSet.new(b)) |> MapSet.to_list()
end)
|> Stream.map(&Enum.count/1)
|> Stream.map(fn c -> if c == 0, do: 0, else: 2 ** (c - 1) end)
|> Enum.sum()
end
# smoke test
part_one.(test_input) == 13
part_one.(input)
Part 2
# append the winning ranges (next cards)
card_and_wins = fn cards ->
cards
|> Stream.map(fn [card_id, wins, chosen] ->
MapSet.intersection(MapSet.new(wins), MapSet.new(chosen))
|> MapSet.to_list()
# winnings count
|> Enum.count()
|> then(&if &1 == 0, do: [], else: (card_id + 1)..(&1 + card_id))
|> Enum.to_list()
# prepend the card id to the list of ranges
|> then(&[card_id, &1])
end)
end
part_two = fn lines ->
lines
|> parse_cards.()
|> card_and_wins.()
# start bottom up
|> Enum.reverse()
# create map with *all* wins
|> Enum.reduce(%{}, fn [card, wins], acc ->
Map.put(acc, card, [card | Enum.map(wins, &Map.get(acc, &1))])
end)
# convert map to list
|> Enum.reduce([], fn {_card, v}, acc -> [v | acc] end)
# |> Enum.flat_map(fn {_card, v} -> if is_map(v), do: v, else: [v] end) # same as above
|> List.flatten()
|> Enum.frequencies_by(& &1)
|> Enum.reduce(0, fn {_card, count}, total -> total + count end)
end
# smoke test
part_two.(test_input) == 30
part_two.(input)