Day 4: Scratchcards
Puzzle
Part 1
Recently, I’ve started to dabble in a bit of sports gambling. And while this endeavor is short-lived (I somehow always lose), I still find it exciting to count the winnings (when they do happen). Today’s puzzle is about calculating your winnings from a series of scratchcards.
Given a series of “cards”, each showing a series of winning numbers as well as your numbers (separated by a vertical bar). An example is below:
For each scratchcard, your total winning is dependent on the number of matches that your numbers have with the winning numbers. Obviously, if you have no matches, then your total winning is 0. If you have 1 matching card, then your total is 1. Every match thereafter the first doubles your total winnings.
So, Card 1
has 4 matching numbers (lucky you!). This means that the total value of this card is 8
since the first card has 1 point, and then the remaining 3 matches each double the total, accounting to 8
.
Mathematically, this is relatively straightforward. We can separate the possibilities into two scenarios: either we have 0 matches or some positive number n
matches. The former is trivial: 0 is 0. The latter isn’t too bad either. The number of times we double our winnings is exactly n-1
. Since the initial value is 1
(for the first match), the total is 1*2^(n-1) = 2^(n-1)
.
Finding the number of matches is also easy. We can tell that no number is duplicated, so a Set is sufficient for our purposes. Sets contain a function inter
, which (surprise!) computes the intersection of two sets – this is exactly what we want in order to find the number of matches.
Equipped with these hints, Part 1 becomes relatively straightforward, with the bulk of the implementation dedicated to parsing the input and being exhaustive.
Part 2
No true dealer will ever let its players off that easy, and the masterminds behind AoC are no different. Instead of winning “points”, your prize is more scratchcards.
Specifically, you now win copies of subsequent scratchcards based on the number of matches in your current card. Confusing huh?
Let’s run through an example:
Before we start processing the cards, we have 1 copy of each of the cards. Visually, we’ll represent our “inventory” in JSON format (card number to copies):
Card 1 is up first, and we only have 1 copy of this card. There’s 4 matching numbers, so we get 1 copy of the next 4 cards (Card 2, Card 3, Card 4, and Card 5). Now, our inventory is the following:
Card 2 is up next, and we have 2 copies of this card. There’s 2 matching numbers, so we get 1 copy of the next 2 cards (Card 3 and Card 4). Now, our inventory is the following:
You get the idea from here on out. The goal is to count the total number of cards (originals and copies) after all cards are processed.
Initially, this sounds like a completely new problem compared to Part 1, but it’s not too unrelated! We still use the same logic to parse each line and to find the number of matches. There’s just a couple of things to notice for this part:
First, we can see that once we process a card, we never need to reprocess it. We only generate copies of future cards, not prior ones. This makes it much simpler to handle.
Second, we can treat copies and originals all at once rather than individually/one at a time. Each “copy” (here, the word “copy” includes the original card) will produce the same result, so “n” copies will produce “n” future “copies” each.
The tricky part (since we’re on a road of purely functional programming with no mutability involved) is ensuring that our map of card IDs to copies is always correct. To accomplish this, we can write a helper function to increment the number of copies for us. At the end, a simple sum
function across the counts is enough to get us our answer (and out of the gambling room).
Disclaimer: I’m still learning the language. Please excuse the handwavy / round-about / ugly implementations - I’m working on it :)