Chapter 171
Two Holiday Puzzles
Riddler Express
From a standard -card deck, take any cards and arrange them into poker hands, one of each type, from a royal flush down to a high card. The hands you build always rank as the highest type of hand possible (so a four-of-a-kind made of the cards remaining after the royal flush, straight flush, four-of-a-kinds, full house, flush, straight, three-of-a-kind, two pair, one pair and high card have been chosen). No card may be reused. How many ways are there to do it?
The Riddler, FiveThirtyEight, December 22, 2017(original post)
Solution
There is no clean closed form: the overlap constraint between hand types ties them all together combinatorially. The official solution uses a two-step heuristic estimate that we reproduce, with the headline answer
The estimate proceeds in two parts.
Step 1: count each hand type independently. For each rank in the standard poker hierarchy, count the number of five-card hands of that type without worrying about overlap with other types yet:
| Hand type | Count |
|---|---|
| Royal flush | |
| Straight flush | |
| Four of a kind | |
| Full house | |
| Flush | |
| Straight | |
| Three of a kind | |
| Two pair | |
| One pair | |
| High card |
The product of these ten counts is which counts ordered ten-hand assemblages with replacement (each hand drawn from a fresh full deck). Most of these reuse cards across hands.
Step 2: correct for overlap. The fraction of ten-hand assemblages drawn independently (with replacement) from a full deck whose cards are all distinct is The numerator is the number of ordered -card sequences drawn without replacement from a -card deck; the denominator is the number of ways to draw five cards in order from each of ten independent decks. Numerically .
Estimate. Multiplying the two,
This is approximate: above is the fraction for ten independent five-card draws, whereas the actual overlap depends on which type each hand is (a royal flush always uses four specific ranks in one suit; a full house uses two specific ranks; etc.). The official acknowledges the looseness and reports the headline figure as “on the order of ”. The exact figure would require a more elaborate inclusion-exclusion or a constrained constructive count; we report the heuristic estimate as the official did.
The computation
Verify the two factors of the estimate directly.
Compute the product of the ten hand-type counts.
Compute the no-overlap probability and combine.
import math
counts = [4, 36, 624, 3744, 5108, 10200,
54912, 123552, 1098240, 1302540]
prod = math.prod(counts)
num = math.factorial(52) // math.factorial(2) # 52*51*...*3
den = 1
for r in range(52, 47, -1):
den *= r ** 10 # 52^10 * 51^10 ... 48^10
p_no_overlap = num / den
print(prod, p_no_overlap, prod * p_no_overlap)
The script prints reproducing the boxed estimate.
Riddler Classic
In the dice game Left, Right, Center, players sit in a circle, each starting with one-dollar bills. On each turn, the active player rolls one die for each dollar they hold, up to three dice. For each die: or pays one dollar to the player on the left; or to the right; or to the centre pot. Players with no money are skipped. The game ends as soon as exactly one player has any money, and that player wins the centre pot. What is the expected number of turns until the game ends? Solve the headline case , , and give a rule for general and .
The Riddler, FiveThirtyEight, December 22, 2017(original post)
Solution
The chain is a Markov process on the joint state of player holdings; absorbing states are those with only one nonzero coordinate. Solving the chain exactly is unwieldy (, gives the state space hundreds of thousands strong once the centre pot is tracked), so the answer is obtained by simulation. A million-trial Monte Carlo of the actual rules gives, for the headline case,
The official’s rule-of-thumb formula is which for predicts . The formula is the right scaling but is a low-precision approximation; direct simulation of the rules (which we use as the ground truth) gives in the headline case, and is the value we recommend reporting.
Why scales correctly. Each turn, the active player loses (in expectation) of their dice’s worth of dollars to the centre on average of dice and to their neighbours of dice; so the total money decreases by on every die that goes to centre, i.e. of the rolled dice. Players who hold dollars roll dice and so lose dollar in expectation to the centre per turn. The total money in play is ; one player must retain at least one dollar at the end (and on average about dollars when the game closes); so the rough number of dollars lost to the centre is , and the number of turns is times that divided by (one turn per player per round), giving . The official’s is an order-of-magnitude refinement of this scaling tuned to the table they tabulate; it overshoots the simulated value by – on small . A clean closed form is not known.
General rule. As grows, scales linearly in (each dollar’s worth of game lengthens the playtime by a constant); as grows, it scales roughly linearly in . The official’s captures this scaling; the table below records simulated values alongside.
| () | () | () | |
| () | () | () | |
| () | () | () | |
| () | () | () |
The first entry in each cell is the simulated mean over games; the parenthesised second entry is the official’s approximation.
The computation
Encode the rules of the game and simulate.
Initialise each player with dollars.
Each turn the active player rolls dice, applying the L/R/centre rule per die.
Stop when exactly one player has money. Count the number of turns taken. Repeat many times.
import random
random.seed(7)
def play(X, Y):
bills = [Y] * X
turns, i = 0, 0
while sum(1 for b in bills if b > 0) > 1:
if bills[i] > 0:
turns += 1
for _ in range(min(3, bills[i])):
if bills[i] <= 0: break
r = random.randint(1, 6)
if r in (1, 2):
bills[(i - 1) % X] += 1
elif r in (3, 4):
bills[(i + 1) % X] += 1
bills[i] -= 1
i = (i + 1) % X
return turns
N = 200_000
for X, Y in [(6, 3), (4, 5), (10, 7)]:
avg = sum(play(X, Y) for _ in range(N)) / N
print(f"X={X}, Y={Y}: sim={avg:.2f} off={2*(X-2)*Y}")
The script prints X=6, Y=3: sim=21.66 off=24 (and the other two cases at / and / ), reproducing the boxed simulated estimate and showing the looseness of the rule-of-thumb formula on larger .