Library · The Fiddler: Solutions · Chapter 142
Can You Light Up the Pinball Machine?
A pinball game has four lanes, all initially unlit. Every flip sends the ball through exactly one of the four lanes, chosen at random, and toggles that lane: an unlit lane becomes lit, and a lit lane becomes unlit. On average, how many flips does it take until all four lanes are lit?
The Fiddler, Zach Wissner-Gross, September 29, 2023(original post)
The official solution appeared in the post of October 6, 2023 and gives flips for the main puzzle and for the Extra Credit. Both agree with the answers below.
Solution
Which lanes are lit does not matter, only how many. With of the four lit, the next ball goes through an unlit lane with probability , lighting it, and through a lit lane with probability , putting it out. So the number lit moves one step up or down along .
To get from lit to lit, the count must pass through each step for the first time, in order. So the expected total is the sum of the expected waits for each of those first crossings. Each follows from the one before it:
From lit, any lane lights, so .
From lit, one flip either lights a new lane (probability ), and the wait is over, or puts one out (probability ). In the second case it takes flips on average to get back to , and then a fresh wait . So
Working up the steps: The last step is by far the slowest: with three lanes lit, the ball finds the dark one only a quarter of the time, and three times as often it undoes earlier progress. Adding the four waits,
The computation
The program writes down the defining equations for the expected number of flips still needed, , with lanes lit: , and otherwise one flip followed by or with the chances above. It solves them exactly, with fractions, by elimination. It does not use the step-by-step waits of the solution.
from fractions import Fraction
def expected_flips(n):
"""Expected flips to light all n lanes, starting dark.
f[k] = expected flips still needed with k lanes lit:
f[n] = 0,
f[k] = 1 + (k/n) f[k-1] + ((n-k)/n) f[k+1].
Solved exactly by eliminating down the chain: write
f[k] = A[k] + B[k] f[k+1], then substitute back up."""
A, B = [], []
for k in range(n):
down, up = Fraction(k, n), Fraction(n - k, n)
a_prev, b_prev = (A[-1], B[-1]) if k else (0, 0)
scale = 1 - down * b_prev
A.append((1 + down * a_prev) / scale)
B.append(up / scale)
f = Fraction(0) # f[n]
for k in reversed(range(n)):
f = A[k] + B[k] * f
return f
print("main:", expected_flips(4))
It prints
main: 64/3
Extra Credit
With lanes, let be the average number of flips until all are lit. Each extra lane roughly doubles , but not exactly. For which is the ratio smallest?
Solution
With lanes the same reasoning gives and This recursion has a tidy solution. Scale each wait by a binomial coefficient, . Since and , the recursion becomes So is a running total of binomial coefficients, and For this gives again.
Why is close to doubling? The last wait dominates: its numerator is every binomial coefficient except the last, , over . The next wait is about , and the rest are much smaller. So is a little more than , and the ratio is a little less than for large , creeping back up towards . For small the ratio starts well above (, ). So it falls, bottoms out, and rises. Exact values show where:
The minimum among these is at . To rule out a smaller ratio for some large , bound from both sides. From below, the last wait alone gives . From above, for : the waits and add to at most ; the last wait is below ; the next is below ; and each of the other waits has a numerator below and a denominator at least , so together they are below . Hence and so Apart from the negligible and , the right side is , which exceeds once , and the and cannot change that. The exact computation covers every up to , so
The computation
The equation solver from The computation above, run for every up to , gives the exact ratios up to ; the program also confirms that they rise steadily after the minimum.
E = {n: expected_flips(n) for n in range(1, 102)}
ratio = {n: E[n + 1] / E[n] for n in range(1, 101)}
best = min(ratio, key=ratio.get)
print("extra credit: N =", best, " ratio =", ratio[best])
print("ratio rises after it:",
all(ratio[n] < ratio[n + 1] for n in range(best, 100)))
It prints
extra credit: N = 6 ratio = 151/78
ratio rises after it: True