aintOver goals s = not $ any (`isInfixOf` s) goals wins goals k s = (goals!!k) `isSuffixOf` s && aintOver goals (init s) -- From Data.List: isPrefixOf [] _ = True isPrefixOf _ [] = False isPrefixOf (x:xs) (y:ys)= x == y && isPrefixOf xs ys isSuffixOf xs ys = isPrefixOf (reverse xs) (reverse ys) isInfixOf needle haystack = any (isPrefixOf needle) (tails haystack) tails [] = [[]] tails xs@(x:xt) = xs : tails xt -- These should all be True: aintOver ["HHH", "THH"] "HHTHTHTH" wins ["HHH", "THH"] 0 "HHH" wins ["HHH", "THH"] 1 "HHTHTHTHH"
Penney’s Game
Alice and Bob play a game. Alice chooses a sequence of 3 coin flips. Then Bob chooses another sequence of the same length. They flip a coin over and over again and a player wins as soon as their sequence appears.
What sequence should Alice pick? Does it matter? After all, if you flip a coin 3 times, every sequence has the same chance of occurring.
Well, here’s one choice Alice should avoid. Suppose Alice picks HHH. Then if Bob picks THH, Alice can only win if the first 3 flips are all heads. Bob wins with probability 7/8.
How to play
This game, known as Penney’s game, is trickier than it may seem. But with a few clever observations, we can figure out the best strategies for sequences of any size.
Define:
-
aintOver goals s
isTrue
when no element ofgoals
appears in the strings
, that is, the game is still ongoing. -
wins goals k s
isTrue
when the string ingoals
of indexk
has just won the game described bys
, that is, the strings
ends with the goal string of indexk
and this is the first goal string of any index to appear.
The following shows all coin flip sequences of a given length:
allFlips n = replicateM n "HT" allFlips 4
For HHH versus THH, we see:
-
There are many sequences of length 4 where neither player has won yet (though Bob will inevitably win soon enough).
-
It is impossible for Alice to win in 4 flips.
-
Bob has 2 ways to win in 4 flips.
filter (aintOver ["HHH", "THH"]) $ allFlips 4 filter (wins ["HHH", "THH"] 0) $ allFlips 4 filter (wins ["HHH", "THH"] 1) $ allFlips 4
Some 4-letter strings are absent from these lists because they do not represent any game. For example, HHHH is an invalid game because it was already over after the 3rd coin flip.
Let’s switch to a less trivial example. Suppose Alice, knowing that HHH is a poor choice, picks HTH. Next, Bob picks HHT, with an innocent expression on his face. Who is more likely to win? Both goals have two Hs and one T, so maybe no one has an advantage?
goals = ["HTH", "HHT"] filter (aintOver goals) $ allFlips 4 filter (wins goals 0) $ allFlips 4 filter (wins goals 1) $ allFlips 4
There are two games of length 4 where Bob wins, but only one where Alice wins. Already there is an asymmetry worth investigating.
As we wish to compute probabilities, we care more about the number of combinations than particular combinations. Hence for a given 0-indexed list of goal strings, define:
-
to be the number of -letter strings where no one has won yet. -
to be the number of ways that the th goal string wins on the th flip.
f n = length $ filter (aintOver goals) $ allFlips n g k n = length $ filter (wins goals k) $ allFlips n
Then the probability that the
It looks like we’re close to solving the problem, but this is an infinite sum, and our functions have exponential time complexity. We’ll need to be smarter.
Recursion to the rescue
Let
(Aside: can we prove this from our code via equational reasoning?)
We check this formula for
f 4 f 5 g 0 5 g 1 5
This is a great start, but as we have three functions, we need more recurrence relations.
Again let
If nothing else, Alice wins. For example, if
However, we might have gone too far, so to speak. It could be that some strict
prefix of
This subtlety occurs exactly when a proper suffix of
This motivates the following. For any strings
The autocorrelation of a string
correlate s t = filter (\r -> take r t `isSuffixOf` s) [1..length t] autocorrelate s = correlate s s correlate "HTH" "HTH" correlate "HHT" "HTH"
-- Example from paper. correlate "HTHTTH" "HTTHT" correlate "HTTHT" "HTHTTH"
Then the above subtlety is:
f 5 g 0 (5 + 1) + g 0 (5 + 3) + g 1 (5 + 2)
Similarly:
What is the matrix?
We now have 3 recurrence relations involving
Thus the probability Alice wins is
For a correlation
We call this correlateBase
in our code because a subset of
correlateBase z x y = sum $ (z^) . pred <$> correlate x y
From our first recurrence relation, and since
The other two recurrence relations imply:
We can now solve for
The product of the main diagonal entries is a polynomial of degree higher than
all other terms of the determinant because it’s where the autocorrelations
reside (recall
Solving for
In other words, the odds that Alice wins are:
oddsBase q a b = (num `div` d, denom `div` d) where go = correlateBase q num = go b b - go b a denom = go a a - go a b d = gcd num denom oddsCoin = oddsBase 2 oddsCoin "HTH" "HHT"
We see in the HTH versus HHT battle, Alice’s odds are 1 to 2, that is, Bob is twice as likely to win.
The above generalizes for alphabets with
For example, the PIN number 2357 is easier to guess than 0000 in the sense that it’s more likely to show up first if we stab digits at random.
oddsBase 10 "2357" "0000"
We can also generalize to any number of players; even zero players.
Let
Then:
and for
Table for one
Consider the solitaire variant of this game, namely, suppose Alice is the lone
player. What goal
The probability of needing more than
From above, this turns out to be:
Presumably, Alice should choose a sequence that minimizes the wait time, such as HTT for sequences of length 3.
eThrows q a = q * correlateBase q a a [(a, eThrows 2 a) | a <- allFlips 3]
Then she has the best chance of seeing her goal show up before Bob’s, and has at least even odds of winning, right?
Wrong!
After you. I insist.
The early bird gets the worm, but the second mouse gets the cheese.
If Alice chooses HTT, then Bob chooses HHT:
oddsCoin "HTT" "HHT"
Again, Bob is twice as likely to win.
If Alice tries to pre-empt this by choosing HHT herself, then Bob chooses THH:
oddsCoin "HHT" "THH"
This time, Bob is three times more likely to win! (Alice only wins if the first two flips are both heads.) What’s going on?
From Bob’s goal string, we get Alice’s goal by adding the right letter, but not the other way around. This hints that the expected wait time to reach Bob’s goal starting from Alice’s string is much longer than the other way around.
Imagine a long random string of coin flips. We expect it to contain many
occurrences of both
We can quantify the expected wait time from one string to another with the following handy generalization:
Let
Define
Then it can be shown:
and for
Returning to Alice and Bob, set
eThrowsFrom q a b = q * correlateBase q a a - q * correlateBase q b a
This leads to an alternative derivation of the formula for Alice’s odds. But more importantly, we can use the expected wait time to shed light on Bob’s optimal strategy.
Let
The case
When
bruteCoin a = [(b, oddsCoin a b) | b <- allFlips $ length a, b /= a] bruteCoin "HH" bruteCoin "HT"
For goal strings of length 3, it turns out Bob should choose his first flip to be the opposite of his third flip.
bobs a = (`oddsCoin` a) <$> filter (/= a) ['H':ai, 'T':ai] where ai = init a bobs <$> allFlips 3
bobs <$> allFlips 5
We see that for coin flipping goals of length 5, the best strategy for Alice is
to pick a goal that caps Bob’s odds of winning at 17 to 9. More generally, it
turns out for
For general
Penney’s game is nontransitive, like the game of rock, paper, scissors. If one player moves first, they suffer a great disadvantage.
Sketch of proof
Define the periods of a string as follows:
periods s = filter (\r -> drop r s `isPrefixOf` s) [1..length s] -- In terms of the autocorrelation: periods' s = tail $ reverse $ (length s -) <$> 0 : autocorrelate s periods "abcabc" periods "abcabcd" periods "abcdabc" periods "aaaaaa"
We can interpret periods as the prefixes of
The basic period of a string is its shortest period:
basicPeriod s = head $ periods s
The periods of a string are the same as its autocorrelation except we count the number of removed letters rather than the number that remain, and we exclude zero but include the string length. Half-empty versus half-full.
Let
Assume
Let init
function), and
let
Let
This is easy to show when
We have:
Also:
Lastly, let
Therefore:
In other words, the goal
Here we happen to know
It remains to handle the cases
Let
Suppose
This implies
When
This bound then leads to bounds for
This shows Bob’s odds of winning are at least:
as
We have
It remains to show one bound is strictly greater than the other. This turns out
to only take a few steps when
References
Guibas and Odlyzko, String Overlaps, Pattern Matching, and Nontransitive Games.