Apr 5, 2019
This week's Riddler falls into one of my favorite categories: a problem that can be solved analytically and via simulation. It's great to be able to solve the same problem in two different ways! Here's the full problem text.
Lucky you! You’ve won two gift cards, each loaded with 50 free drinks from your favorite coffee shop, Riddler Caffei-Nation. The cards look identical, and because you’re not one for record-keeping, you randomly pick one of the cards to pay with each time you get a drink. One day, the clerk tells you that he can’t accept the card you presented to him because it doesn’t have any drink credits left on it.What is the probability that the other card still has free drinks on it? How many free drinks can you expect are still available?
Let's define some terms first. Both cards start with drinks. The question specifically asks about but we can generalize to any . We are interested in the number drinks remaining on either card when we try (and fail) to buy a drink with one of our cards. Note that can be zero, as in the case where we draw down each card equally after buying 100 drinks total. When we frame the question like this, it lets us define the only two possible outcomes, which are:
These two terminating conditions are symmetric - outcome 1 is just as likely as outcome 2. Therefore, we can solve for the probability of outcome 1 and double it for our final answer.
If we run out of drinks on card A, we will have drinks remaining on card B, where is any number between 0 and our starting number, . We know that for this to happen, we must have purchased drinks on the first card, drinks on the second card, and attempted more purchase on the first card, for a total of . Of those purchases, must have occurred on a single card, which gives us an "n choose k" binomial formula. Here's the probability of our event 1 occurring:
Now we account for the fact that either card can run out of drinks, so we multiply this expression by 2 for our final answer. It has the effect of cancelling out the in our exponent.
Let's take our formula for a test drive, where we assume we start with 1 drink on each card. There are six possible outcomes, listed with their probabilities. For example, we can buy two drinks on card "A" in a row. If this happens, there will be one drink left on card B, and we know this occurs with 25% probability. Here's the full list.
Adding these up, we see that there is a 50% chance of ending with 1 drink, and a 50% chance of ending with 0 drinks. We can verify with our formula from above that there is a 50% chance of each outcome, so it seems like we've arrived at the right answer.
The question asked us about the expected number of drinks remaining. Using our formula, we can calculate the probability for any combination of and . Then, for every possible , we multiply by and sum the results to arrive at the expected value. It looks like this:
Using a bit of mathematical horsepower from WolframAlpha, we can simplify this expression to:
When we substitute , we calculate an expected value of . This means that when either card runs out, we expect roughly 7 drinks to be available on the other card.
We were also asked about the probability that there were any drinks available on the other card when the first card expires. We can answer this by taking the complement of the probability that both cards end with zero drinks. For this, we use values of and , and subtract the value from 1, which gives us roughly 92% odds.
We can also solve the problem by writing a short Python script. We start with both cards loaded with 50 drinks each. Next, we randomly choose one card and subtract a drink from it. We repeat this process until one of the cards reaches -1, because it means we tried to buy a drink using an empty card. When this happens, we record the number of drinks left on the other card. If we simulate this process a large number of times, then the distribution of results should converge to our analytical solution above. Here's the function we'll use.
from random import randint
def model(trials, n_drinks=50):
"""Simulate the Riddler Classic"""
for _ in range(trials):
cards = [n_drinks, n_drinks]
while min(cards) > -1:
cards[randint(0,1)] -= 1
yield max(cards)
trials = 1000000
results = np.array(list(model(trials, n_drinks=50)))
# expected value
print(results.mean())
# 7.038671
# probability of any drinks remaining
print((results > 0).sum() / trials)
# 0.920398
Our result based on one million trials is 7.038671, which is remarkably close to our analytical solution above! Looks like everything worked out the way it should. In addition to the expected value, we can also plot the full distribution of results, shown below.
Overall I was surprised the expected value is so low. There's a nearly a 50% chance that you end up with no more than 5 drinks on one of the cards using this approach! It looks like organizing and tracking gift cards may not be quite as important as one might think. Could this be a new life-hack? I doubt it, but it was a fun problem regardless.