Chapter 123
Cubs Win-Strings and a Risk-Free Bet
The Riddler for September 30, 2016, in the autumn the Cubs finally won it all. The Express counts the paths to a title; the Classic builds a sequence of even bets that hedges a championship.
Riddler Express
The Cubs’ road to the World Series title is a best-of-five series followed by two best-of-seven series. How many unique strings of wins and losses could the Cubs assemble if they make their way through the playoffs and win the championship? For example, one possible string is WWWWWWWWWWW (three straight sweeps); another is WWWWWWWLLLWWWW.
The Riddler, FiveThirtyEight, September 30, 2016(original post)
Solution
The three series are independent stretches of the string, and a won series always ends on a win, so we can count each series’ winning strings on its own and multiply.
Take a best-of- series that the Cubs win, needing wins. The clinching win is the last game played, so it is fixed; what varies is how the earlier games fall. If the series lasts games (with ), the first games hold exactly wins among them, in any order, which is strings. Summing over the possible lengths, For the best-of-five (): . For each best-of-seven (): . The full run is one best-of-five and two best-of-sevens, so distinct championship strings.
The computation
Encode the rule directly: generate every sequence of wins and losses for each series, keep those the Cubs win (reaching wins, never losses), and multiply the counts. This counts the strings without using the binomial formula.
from itertools import product
def winning_strings(k): # best-of-(2k-1) the Cubs win
total = 0
for n in range(k, 2*k): # series length n
for s in product("WL", repeat=n):
w = s.count("W"); l = s.count("L")
if w == k and l < k and s[-1] == "W": # clinch on the last game
# and no earlier prefix already reached k wins
if all(s[:i].count("W") < k for i in range(n)):
total += 1
return total
b5, b7 = winning_strings(3), winning_strings(4)
print("best-of-5:", b5, " best-of-7:", b7, " product:", b5 * b7 * b7)
# best-of-5: 10 best-of-7: 35 product: 12250
Riddler Classic
You are a gambler and a Cubs fan. The Cubs play a seven-game series, first to four wins. Your bookie takes any even-odds bet on any individual game. Can you construct a series of bets so that, whatever happens, you win $100 if the Cubs take the series and lose $100 if the Red Sox take it?
The Riddler, FiveThirtyEight, September 30, 2016(original post)
Solution
Yes. The trick is to decide, for every possible score, exactly how much wealth you want to be holding when you walk into that game, and then let each bet carry you between those targets. Work backwards from the end.
Let be the bankroll you aim to hold entering a game when the Cubs have wins and the Red Sox have . The targets at the end of the series are forced: if the Cubs have reached four wins you must be holding , and if the Red Sox have, : Now suppose you enter a game at holding and bet on the Cubs at even odds. A Cubs win leaves you with , and you want that to equal next game’s target ; a Cubs loss leaves , which should equal . Those two demands solve at once: Each entering target is the average of the two it can lead to, and the bet is half their gap. Rolling this average back from the edges, the start of the series sits exactly at the midpoint, so you stake nothing of your own and end at . The opening wager is and every later bet follows from the same averaging. The full schedule of opening-through-final bets, in dollars, is The pattern is intuitive once you see it: bet more as the series nears its end, and more when the score is close, because those are the games that swing the title hardest. The decisive game seven at - is a flat bet, the whole prize on one coin.
The computation
Encode the guarantee, not the formula: build the bets by the backward averaging, then play out every possible series and check the final bankroll is exactly on a Cubs title and on a Red Sox one.
from functools import lru_cache
@lru_cache(None)
def W(a, b):
if a == 4: return 100.0
if b == 4: return -100.0
return (W(a+1, b) + W(a, b+1)) / 2
def bet(a, b): return (W(a+1, b) - W(a, b+1)) / 2
paths = [0]; fails = [0]
def play(a, b, bankroll): # recurse over every game outcome
if a == 4 or b == 4:
paths[0] += 1
target = 100.0 if a == 4 else -100.0
fails[0] += abs(bankroll - target) > 1e-9
return
x = bet(a, b)
play(a+1, b, bankroll + x) # Cubs win this game
play(a, b+1, bankroll - x) # Red Sox win this game
play(0, 0, 0.0)
print("opening bet :", bet(0, 0))
print("series paths:", paths[0], " mispaid:", fails[0])
# opening bet : 31.25
# series paths: 70 mispaid: 0