Add files via upload

This commit is contained in:
Peter Norvig 2018-01-04 15:09:26 -08:00 committed by GitHub
parent 4a94ef6a42
commit d701e013a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -10,7 +10,7 @@
"\n",
">Your goal is to get all 4 coins showing heads, by telling the devil the position(s) of some coins to flip. We call this a \"move\" on your part. The devil must faithfully perform the requested flips, but may first sneakily rotate the table any number of quarter-turns, so that the coins are in different positions. You keep making moves, and the devil keeps rotating and flipping, until all 4 coins show heads.\n",
"\n",
"> Example: You tell the devil tthe 12 o'clock and 6 o'clock positions. The devil could leave the table unrotated (or could rotate it a half-turn), and then flip the two coins that you specified. Or the devil could rotate the table a quarter turn in either direction, and then flip the coins that are now in the 12 o'clock and 6 o'clock positions (which were formerly at 3 o'clock and 9 o'clock). You won't know which of these actions the devil took.\n",
"> Example: You tell the devil the 12 o'clock and 6 o'clock positions. The devil could leave the table unrotated (or could rotate it a half-turn), and then flip the two coins that you specified. Or the devil could rotate the table a quarter turn in either direction, and then flip the coins that are now in the 12 o'clock and 6 o'clock positions (which were formerly at 3 o'clock and 9 o'clock). You won't know which of these actions the devil took.\n",
"\n",
"> What is a shortest sequence of moves that is *guaranteed* to win, no matter what the initial state of the coins, and no matter what rotations the devil applies?\n",
"\n",
@ -27,9 +27,9 @@
"\n",
"- `Coins`: A *coin sequence* is represented as a `str` of four characters, such as `'HTTT'`. \n",
"- `Belief`: A *belief state* is represented as a `frozenset` of `Coins` (frozen so that it can be hashed in a `set`).\n",
"- `Move`: A *move* is a set of positions to flip. A position will be an integer index into the coin sequence, so a move is a set of these such as `{0, 2}`, which we can interpret as \"flip the 12 o'clock and 6 o'clock positions.\" \n",
"- `Move`: A *move* is a set of positions to flip. A position will be an integer index into the coin sequence, so a move is a set of these such as `{0, 1}`, which we can interpret as \"flip the 12 o'clock (0) and 3 o'clock (1) positions.\" \n",
"- `all_coins`: Set of all possible coin sequences: `{'HHHH', 'HHHT', ...}`.\n",
"- `rotations`: The function `rotations(coins)` returns the set of all 4 rotations of the coin sequence.\n",
"- `rotations`: The function `rotations(coins)` returns a list of all 4 rotations of the coin sequence.\n",
"- `update`: The function `update(belief, move)` returns an updated belief state, representing all the possible coin sequences that could result from any devil rotation followed by the specified flip(s). (But don't flip `'HHHH'`, because the game would have already ended.)\n",
"- `flip`: The function `flip(coins, move)` flips the specified positions within the coin sequence."
]
@ -48,6 +48,7 @@
"\n",
"Coins = ''.join # A coin sequence, such as 'HHHT'.\n",
"Belief = frozenset # A belief state is a set of possible coin sequences.\n",
"Move = set # A Move is a set of positions to flip.\n",
"all_coins = Belief(map(Coins, product('HT', repeat=4)))\n",
"\n",
"def rotations(coins) -> [Coins]: \n",
@ -264,12 +265,13 @@
"source": [
"# Search for a Solution\n",
"\n",
"The function `search` does a breadth-first search starting\n",
"from a `belief` state consisting of `all_coins`, and applying a sequences of moves, trying to\n",
"find a sequence that leads to the goal belief state in which the only possibility is 4 heads: `{'HHHH'}`.\n",
"As is typical for search algorithms, we build a search tree, keeping a queue of tree `nodes` to consider, where each \n",
"The generic function `search` does a breadth-first search starting\n",
"from a `start` state, looking for a `goal` belief state, considering possible `moves` at each turn,\n",
"and updating the belief state according to the `update` function. As is typical for search algorithms, we build a search tree, keeping a queue of tree `nodes` to consider, where each \n",
"node consists of a path (a sequence of moves) and a resulting belief state. We also keep track, in `explored`, of\n",
"the states we have already explored, so that we don't have to revisit them."
"the states we have already explored, so that we don't have to revisit them. Note an interesting fact: the `search` function works on belief states, but would be exactly the same if we were searching on individual states, not belief states (the only difference would be to the parameters, not the code: the `start`, `goal`, and `update` values would be different).\n",
"\n",
"The `coin_search` function calls the generic `search` function to solve our specific problem:\n"
]
},
{
@ -280,20 +282,24 @@
},
"outputs": [],
"source": [
"def search():\n",
" \"Breadth-first search from all_coins to all heads.\"\n",
"def search(start, goal, update, moves) -> [Move]:\n",
" \"Breadth-first search from start belief state to goal.\"\n",
" explored = set()\n",
" queue = deque([Node([], all_coins)])\n",
" queue = deque([Node([], start)])\n",
" while queue:\n",
" (path, belief) = queue.popleft()\n",
" if belief == {'HHHH'}:\n",
" if belief == goal:\n",
" return path\n",
" for move in powerset(range(4)):\n",
" for move in moves:\n",
" belief2 = update(belief, move)\n",
" if belief2 not in explored:\n",
" explored.add(belief2)\n",
" queue.append(Node(path + [move], belief2))\n",
" \n",
"def coin_search() -> [Move]: \n",
" \"Use the generic `search` function to solve the Coin Flip problem.\"\n",
" return search(all_coins, {'HHHH'}, update, powerset(range(4)))\n",
" \n",
"def Node(path, belief) -> tuple: return (path, belief)"
]
},
@ -337,7 +343,7 @@
}
],
"source": [
"search()"
"coin_search()"
]
},
{
@ -348,7 +354,7 @@
"\n",
"# Verifying the Solution\n",
"\n",
"Can I verify that the solution is correct? Exploring with paper and pencil, it does appear to work. A colleague did the puzzle and got the same answer. And here's further validation: The function `random_devil` takes an initial coin sequence and a sequence of moves, and plays those moves with a devil that chooses rotations randomly, returning the number of moves it takes until the player wins:"
"Can I verify that the solution is correct? Exploring with paper and pencil, it does appear to work. A colleague did the puzzle and got the same answer, so that's a good sign. And here's further validation: The function `random_devil` takes an initial coin sequence and a sequence of moves, and plays those moves with a devil that chooses rotations randomly, returning the number of moves it takes until the player wins. Note this is dealing ith concrete, individual states of the world, like `HTHH`, not belief states."
]
},
{
@ -364,9 +370,10 @@
" return the number of moves until win, or None.\"\"\"\n",
" if coins == 'HHHH': return 0\n",
" for (i, move) in enumerate(moves, 1):\n",
" coins = flip(random.choice(list(rotations(coins))), move)\n",
" coins = flip(random.choice(rotations(coins)), move)\n",
" if coins == 'HHHH': \n",
" return i"
" return i\n",
" return None"
]
},
{
@ -388,20 +395,20 @@
"text/plain": [
"Counter({0: 1000,\n",
" 1: 1000,\n",
" 2: 986,\n",
" 3: 1014,\n",
" 4: 1052,\n",
" 5: 1004,\n",
" 6: 976,\n",
" 7: 968,\n",
" 8: 964,\n",
" 9: 1014,\n",
" 10: 985,\n",
" 11: 1003,\n",
" 12: 1056,\n",
" 13: 983,\n",
" 14: 1021,\n",
" 15: 974})"
" 2: 1026,\n",
" 3: 974,\n",
" 4: 938,\n",
" 5: 1016,\n",
" 6: 1009,\n",
" 7: 1037,\n",
" 8: 961,\n",
" 9: 1035,\n",
" 10: 983,\n",
" 11: 1013,\n",
" 12: 1009,\n",
" 13: 966,\n",
" 14: 1037,\n",
" 15: 996})"
]
},
"execution_count": 11,
@ -410,7 +417,7 @@
}
],
"source": [
"moves = search()\n",
"moves = coin_search()\n",
"\n",
"Counter(random_devil(coins, moves) \n",
" for coins in all_coins\n",
@ -428,7 +435,7 @@
"\n",
"Consider the four coin sequences `{'HHHT', 'HHTH', 'HTHH', 'THHH'}`. In a sense, these are all the same: they all denote the same sequence of coins with the table rotated to different degrees. Since the devil is free to rotate the table any amount at any time, we could be justified in treating all four of these as equivalent, and collapsing them into one representative member (we could arbitrarily choose the one that comes first in alphabetical order, `'HHHT'`). I will write a definition for `Belief` that returns a `frozenset`, just like before, but makes it a set of canonical coin sequences.\n",
"\n",
"Similarly, the four moves `[{3}, {2}, {1}, {0}]` are equivalent, in that they all say \"flip one of the coins, but since you're going to rotate the table first, it doesn't matter which one I specify.\" I will write a function `canonical_moves` that lists all the canonical rotationally invariant moves corresponding to a belief state."
"Similarly, the four moves `{3}, {2}, {1},` and `{0}` are equivalent, in that they all say \"flip exactly one of the coins, but since you're going to rotate the table first anyway, it doesn't matter which one I specify.\" I will write a function `canonical_moves` that lists all the canonical rotationally invariant moves corresponding to a belief state."
]
},
{
@ -445,8 +452,9 @@
"\n",
"def canonical_moves(coin_collection):\n",
" \"All rotationally invariant moves for a sequence of N coins.\"\n",
" return [set(i for (i, coin) in enumerate(coins) if coin == 'H' and 'H')\n",
" for coins in sorted(coin_collection) if 'H' in coins]"
" return [set(i for (i, coin) in enumerate(coins) if coin == 'H')\n",
" for coins in sorted(coin_collection) \n",
" if 'H' in coins]"
]
},
{
@ -471,6 +479,13 @@
"Belief(all_coins)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The starting belief set is down from 16 sequences to 6. Call these 4 heads, 3 heads, 2 adjacent heads, 2 opposite heads, 1 head, and no heads, respectively. "
]
},
{
"cell_type": "code",
"execution_count": 14,
@ -497,13 +512,18 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The starting belief set is down from 16 sequences to 6. Call these 4 heads, 3 heads, 2 adjacent heads, 2 opposite heads, 1 head, and no heads, respectively. Similarly, there are only 5 distinct moves, which we can call flip 4, flip 3, flip 2 adjacent, flip 2 opposite, and flip 1.\n",
"Similarly, there are only 5 distinct moves, which we can call flip 4, flip 3, flip 2 adjacent, flip 2 opposite, and flip 1.\n",
"\n",
"I have to confess to a form of cheating here: I threw in the `sorted` in `canonical_moves` even though it was not necessary. By including it, I made sure that `{0, 1, 2, 3}` is the first of the possible moves, which makes `search` faster, because half the time `{0, 1, 2, 3}` is the right move to make.\n",
"I have to confess to a form of cheating here: I threw in the `sorted` in `canonical_moves` even though it was not necessary. By including it, I made sure that `{0, 1, 2, 3}` is the first of the possible moves, which makes `coin_search` faster, because half the time `{0, 1, 2, 3}` is the right move to make."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Solutions for *N* Coins\n",
"\n",
"# Visualizing the Solution\n",
"\n",
"How does the solution work? One answer is \"it takes care of all possibilities.\" But it would be nice to gain more insight. I'll print a table showing the belief state after each move, using the newly-defined canonicalized `Belief` form, and lining them up neatly in columns."
"What if there are 3 coins on the table arranged in a triangle? Or 6 coins in a hexagon? To answer that, I'll generalize the two functions that have a \"4\" in them, `coin_search`, and `rotations`. I'll also generalize `update`, which had `'HHHH'` in it. And I'll introduce the function `canonical_coins` to return a belief state of all canonical coin sequences of length `N`."
]
},
{
@ -514,98 +534,9 @@
},
"outputs": [],
"source": [
"def show(moves, belief=all_coins):\n",
" \"For each move, print the move number, move, and belief state.\"\n",
" show_line(0, 'start', belief)\n",
" for (i, move) in enumerate(moves, 1):\n",
" belief = update(belief, move)\n",
" show_line(i, move, belief)\n",
"\n",
"def show_line(i, move, belief, order=sorted(Belief(all_coins))):\n",
" \"Print the move number, move, and belief state.\"\n",
" ordered_belief = [(coins if coins in belief else ' ')\n",
" for coins in order]\n",
" print('{:2} | {:5} | {} | {}'\n",
" .format(i, join(move), join(ordered_belief, ' '), i))\n",
" \n",
"def join(items, sep='') -> str: return sep.join(map(str, items))"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 0 | start | HHHH HHHT HHTT HTHT HTTT TTTT | 0\n",
" 1 | 0123 | HHHH HHHT HHTT HTHT HTTT | 1\n",
" 2 | 02 | HHHH HHHT HHTT HTTT TTTT | 2\n",
" 3 | 0123 | HHHH HHHT HHTT HTTT | 3\n",
" 4 | 01 | HHHH HHHT HTHT HTTT TTTT | 4\n",
" 5 | 0123 | HHHH HHHT HTHT HTTT | 5\n",
" 6 | 02 | HHHH HHHT HTTT TTTT | 6\n",
" 7 | 0123 | HHHH HHHT HTTT | 7\n",
" 8 | 0 | HHHH HHTT HTHT TTTT | 8\n",
" 9 | 0123 | HHHH HHTT HTHT | 9\n",
"10 | 02 | HHHH HHTT TTTT | 10\n",
"11 | 0123 | HHHH HHTT | 11\n",
"12 | 01 | HHHH HTHT TTTT | 12\n",
"13 | 0123 | HHHH HTHT | 13\n",
"14 | 02 | HHHH TTTT | 14\n",
"15 | 0123 | HHHH | 15\n"
]
}
],
"source": [
"show(search())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that every odd-numbered move flips all four coins to eliminate the possibility of `TTTT`, flipping it to `HHHH`. We can also see that moves 2, 4, and 6 flip two coins and have the effect of eventually eliminating the two \"two heads\" sequences from the belief state, and then move 8 flips a single coin, thus eliminating the \"three heads\" and \"one heads\" sequences, and bringing back the \"two heads\" possibilities. Repeating moves 2, 4, and 6 in moves 10, 12, and 14 then re-eliminates the \"two heads\", and move 15 gets the belief state down to `{'HHHH'}`.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Different Numbers of Coins\n",
"\n",
"What if there are 3 coins on the table arranged in a triangle? Or 6 coins in a hexagon? To answer that, I'll generalize the two functions that have a \"4\" in them, `search`, and `rotations`. I'll also generalize `update`, which had `'HHHH'` in it. And I'll introduce the function `canonical_coins` to return a belief state of all canonical coin sequences of length `N`."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def search(N=4):\n",
" \"Breadth-first search from all_coins to all heads.\"\n",
" start = canonical_coins(N)\n",
" all_moves = canonical_moves(start)\n",
" goal = {'H' * N}\n",
" explored = set()\n",
" queue = deque([Node([], start)])\n",
" while queue:\n",
" (path, belief) = queue.popleft()\n",
" if belief == goal:\n",
" return path\n",
" for move in all_moves:\n",
" belief2 = update(belief, move)\n",
" if belief2 not in explored:\n",
" explored.add(belief2)\n",
" queue.append(Node(path + [move], belief2))\n",
"def coin_search(N=4):\n",
" start = canonical_coins(N)\n",
" return search(start, {'H' * N}, update, canonical_moves(start))\n",
"\n",
"def rotations(coins) -> [Coins]: \n",
" \"A list of all possible rotations of a coin sequence.\"\n",
@ -617,7 +548,8 @@
" for coins in belief\n",
" for c in rotations(coins))\n",
"\n",
"def canonical_coins(N) -> Belief: return Belief(map(Coins, product('HT', repeat=N)))"
"def canonical_coins(N) -> Belief: \n",
" return Belief(map(Coins, product('HT', repeat=N)))"
]
},
{
@ -629,7 +561,7 @@
},
{
"cell_type": "code",
"execution_count": 18,
"execution_count": 16,
"metadata": {
"collapsed": false
},
@ -654,13 +586,13 @@
" {0, 1, 2, 3}]"
]
},
"execution_count": 18,
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"search(4)"
"coin_search(4)"
]
},
{
@ -670,34 +602,56 @@
"Now test the new definitions:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"['HHHHHT', 'HHHHTH', 'HHHTHH', 'HHTHHH', 'HTHHHH', 'THHHHH']"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rotations('HHHHHT')"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"frozenset({'HHHHHH', 'HHHHTT', 'HHHTHT', 'HHTHHT'})"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"update(_, {0})"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"['HHHHHT', 'HHHHTH', 'HHHTHH', 'HHTHHH', 'HTHHHH', 'THHHHH']"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rotations('HHHHHT')"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
@ -705,7 +659,7 @@
"frozenset({'HHHH', 'HHHT', 'HHTT', 'HTHT', 'HTTT', 'TTTT'})"
]
},
"execution_count": 20,
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
@ -716,7 +670,7 @@
},
{
"cell_type": "code",
"execution_count": 21,
"execution_count": 20,
"metadata": {
"collapsed": false
},
@ -734,7 +688,7 @@
" 'TTTTT'})"
]
},
"execution_count": 21,
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
@ -743,6 +697,28 @@
"canonical_coins(5)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[{0, 1, 2, 3, 4}, {0, 1, 2, 3}, {0, 1, 2}, {0, 1, 3}, {0, 1}, {0, 2}, {0}]"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"canonical_moves(_)"
]
},
{
"cell_type": "code",
"execution_count": 22,
@ -753,7 +729,20 @@
{
"data": {
"text/plain": [
"[{0, 1, 2, 3, 4}, {0, 1, 2, 3}, {0, 1, 2}, {0, 1, 3}, {0, 1}, {0, 2}, {0}]"
"frozenset({'HHHHHH',\n",
" 'HHHHHT',\n",
" 'HHHHTT',\n",
" 'HHHTHT',\n",
" 'HHHTTT',\n",
" 'HHTHHT',\n",
" 'HHTHTT',\n",
" 'HHTTHT',\n",
" 'HHTTTT',\n",
" 'HTHTHT',\n",
" 'HTHTTT',\n",
" 'HTTHTT',\n",
" 'HTTTTT',\n",
" 'TTTTTT'})"
]
},
"execution_count": 22,
@ -762,7 +751,7 @@
}
],
"source": [
"canonical_moves(_)"
"canonical_coins(6)"
]
},
{
@ -836,7 +825,7 @@
}
],
"source": [
"{N: search(N) for N in range(1, 8)}"
"{N: coin_search(N) for N in range(1, 8)}"
]
},
{
@ -845,11 +834,15 @@
"source": [
"Too bad; there are no solutions for N = 3, 5, 6, or 7. \n",
"\n",
"There are solutions for N = 1, 2, 4; of lengths 1, 3, 15. That suggests the conjecture: for every *N* that is a power of 2, there will be a solution of length 2<sup>*N*</sup> - 1. \n",
"There are solutions for N = 1, 2, 4; of lengths 1, 3, 15. That suggests the conjecture: \n",
"\n",
"> For every *N* that is a power of 2, there will be a shortest solution of length 2<sup>*N*</sup> - 1.\n",
"\n",
"> For every *N* that is not a power of 2, there will be no solution. \n",
"\n",
"# Solution for 8 Coins\n",
"\n",
"Even though there are 2<sup>36</sup> = 68 billion belief states fo N = 8, I hope we can find a solution early in the search process. Let's try:"
"Even though there are 2<sup>36</sup> = 68 billion belief states fo N = 8, and the desired solution has 255 steps, I hope it won't take too long. Let's try:"
]
},
{
@ -863,8 +856,8 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1min 47s, sys: 731 ms, total: 1min 48s\n",
"Wall time: 1min 50s\n"
"CPU times: user 1min 58s, sys: 1.36 s, total: 2min\n",
"Wall time: 2min 18s\n"
]
},
{
@ -879,7 +872,7 @@
}
],
"source": [
"%time solution8 = search(8)\n",
"%time solution8 = coin_search(8)\n",
"len(solution8)"
]
},
@ -887,7 +880,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Eureka! That's evidence in favor of my conjecture, and it took less time than I thought it would. It is still not proof, and I'll need a more efficient/clever approach to test even for N=16. Can you come up with that approach? Can you prove the conjecture for all powers of 2? Here's the solution for N = 8 to help you discover a pattern:"
"Eureka! That's evidence in favor of my conjecture, and it took less time than I thought it would. Here is the solution:"
]
},
{
@ -1165,6 +1158,95 @@
"source": [
"solution8"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Of course, seeing that the pattern works up to *N* = 8 is not a proof, and this leaves many questions for you, the reader, to consider:\n",
"- Can you show there are no solutions for *N* = 9, 10, 11 (and maybe further)?\n",
"- Can you prove there are no solutions for *N* that is not a power of 2?\n",
"- Can you find a solution of length 65,535 for *N* = 16 and verify that it works?\n",
"- Can you prove there are no shorter solutions for *N* = 16?\n",
"- Can you prove the conjecture in general?\n",
"- Can you *understand* and *explain* how the solution works, rather than just listing the moves?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Visualizing the Solution\n",
"\n",
"To help you understand solutions, I'll print a table showing the belief state after each move, using the canonicalized `Belief` form, and lining them up neatly in columns."
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def show(moves, belief=all_coins):\n",
" \"For each move, print the move number, move, and belief state.\"\n",
" show_line(0, 'start', belief)\n",
" for (i, move) in enumerate(moves, 1):\n",
" belief = update(belief, move)\n",
" show_line(i, move, belief)\n",
"\n",
"def show_line(i, move, belief, order=sorted(Belief(all_coins))):\n",
" \"Print the move number, move, and belief state.\"\n",
" ordered_belief = [(coins if coins in belief else ' ' * len(coins))\n",
" for coins in order]\n",
" print('{:3} | {:6} | {} | {}'\n",
" .format(i, join(move), join(ordered_belief, ' '), i))\n",
" \n",
"def join(items, sep='') -> str: return sep.join(map(str, items))"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 0 | start | HHHH HHHT HHTT HTHT HTTT TTTT | 0\n",
" 1 | 0123 | HHHH HHHT HHTT HTHT HTTT | 1\n",
" 2 | 02 | HHHH HHHT HHTT HTTT TTTT | 2\n",
" 3 | 0123 | HHHH HHHT HHTT HTTT | 3\n",
" 4 | 01 | HHHH HHHT HTHT HTTT TTTT | 4\n",
" 5 | 0123 | HHHH HHHT HTHT HTTT | 5\n",
" 6 | 02 | HHHH HHHT HTTT TTTT | 6\n",
" 7 | 0123 | HHHH HHHT HTTT | 7\n",
" 8 | 012 | HHHH HHTT HTHT TTTT | 8\n",
" 9 | 0123 | HHHH HHTT HTHT | 9\n",
" 10 | 02 | HHHH HHTT TTTT | 10\n",
" 11 | 0123 | HHHH HHTT | 11\n",
" 12 | 01 | HHHH HTHT TTTT | 12\n",
" 13 | 0123 | HHHH HTHT | 13\n",
" 14 | 02 | HHHH TTTT | 14\n",
" 15 | 0123 | HHHH | 15\n"
]
}
],
"source": [
"show(coin_search(4))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that every odd-numbered move flips all four coins to eliminate the possibility of `TTTT`, flipping it to `HHHH`. We can also see that moves 2, 4, and 6 flip two coins and have the effect of eventually eliminating the two \"two heads\" sequences from the belief state, and then move 8 eliminates the \"three heads\" and \"one heads\" sequences, while bringing back the \"two heads\" possibilities. Repeating moves 2, 4, and 6 in moves 10, 12, and 14 then re-eliminates the \"two heads\", and move 15 gets the belief state down to `{'HHHH'}`.\n",
"\n"
]
}
],
"metadata": {