Add files via upload

This commit is contained in:
Peter Norvig 2017-08-28 19:18:00 -07:00 committed by GitHub
parent 2aee2752cb
commit 0e3cb8548f

View File

@ -17,7 +17,7 @@
"\n",
"# Analysis\n",
"\n",
"The hard part is that we are blindfolded. So we don't know the true state of the coins. We need to represent what we do know: the *set of possible states* of the coins. We call this a *belief state*. At the start of the game, each of the four coins could be either heads or tails, so that's 2<sup>4</sup> = 16 possibilities. However, some of these possibilities are just rotations of other possibilities, and since the devil is free to apply any rotation at any time, it makes more sense to collapse these possibilities together. For example, a set of four possibilities, `{'HHHT', 'HHTH', 'HTHH', 'THHH'}`, all correspond to having one tails somewhere on the table, and for purposes of the belief state, I will represent this as a single canonical possibility, `{'HHHT'}`. (I arbitrarily chose the\n",
"The hard part is that we are blindfolded. So we don't know the true state of the coins. We need to represent what we do know: the *set of possible states* of the coins. We call this a *belief state*. At the start of the game, each of the four coins could be either heads or tails, so that's 2<sup>4</sup> = 16 possibilities. However, some of these possibilities are just rotations of other possibilities, and since the devil is free to apply any rotation at any time, it makes more sense to collapse these possibilities together. For example, a set of four possibilities, `{'HHHT', 'HHTH', 'HTHH', 'THHH'}`, all correspond to having three heads on the table, and for purposes of the belief state, I will represent this as a single canonical possibility, `{'HHHT'}`. (I arbitrarily chose the\n",
"one that comes first in alphabetical order.)\n",
"\n",
"Once we have the notion of a belief state, we can then update the belief state with the player's move, which is a set of positions to flip, such as `{0, 2}`. The updated belief state consists of every coin sequence in the original belief state, rotated in every possible way, and then with the flips applied. \n",
@ -191,7 +191,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The above says that there are 16 possible coin sequences, but only 6 of them are distinct after rotations. We can name the 6: all heads, 3 heads, 2 adjacent heads, 2 opposite heads, 1 head, or all tails."
"The above says that there are 16 possible coin sequences, but only 6 of them are distinct after rotations. We can name the 6: all heads, 3 heads, 2 adjacent heads, 2 opposite heads, 1 head, all tails."
]
},
{
@ -220,14 +220,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"That says that if we flip all 4 coins, we eliminate the possibility of 4 tails, but all other coin sequences are still possible."
"That says that if we flip all 4 coins, we eliminate the possibility of 4 tails, but the other 5 coin sequences are still possible."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Everything looks good so far. One more thing: we need to find all subsets of the 4 positions:"
"Everything looks good so far. One more thing: we need to find all subsets of the 4 coin positions:"
]
},
{
@ -280,7 +280,7 @@
"# Search for a Solution\n",
"\n",
"The function `search` does a breadth-first search starting\n",
"at the initial `belief` state and applying a sequences of `moves`, trying to\n",
"at the initial `belief` state and applying a sequences of moves, trying to\n",
"find a path that leads to the goal belief state `{'HHHH'}` (meaning that the only possibility is 4 heads).\n",
"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",
@ -296,15 +296,15 @@
},
"outputs": [],
"source": [
"def search(start=initial_belief, moves=powerset(range(4)), goal={'HHHH'}):\n",
"def search(start=initial_belief):\n",
" \"Breadth-first search from starting belief state using moves.\"\n",
" explored = set()\n",
" q = deque([Node([], start)])\n",
" while q:\n",
" (path, belief) = q.popleft()\n",
" if belief == goal:\n",
" if belief == {'HHHH'}:\n",
" return path\n",
" for move in moves:\n",
" for move in powerset(range(4)):\n",
" belief2 = update(belief, move)\n",
" if belief2 not in explored:\n",
" explored.add(belief2)\n",
@ -353,7 +353,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"That's a 15-move sequence that is guaranteed to lead to a win. Do I believe it? Well, I looked into it, and it appears to work. Others who have tried the puzzle got the same answer. But here's another technique to give it further validation: The function `random_play` takes a sequence of moves (like this 15-move sequence) and plays it against a devil that chooses randomly:"
"That's a 15-move sequence that is guaranteed to lead to a win. Do I believe it? It does appear to work. A colleague did the puzzle and got the same answer. And here's further validation: The function `random_play` takes a sequence of moves and plays it against a devil that chooses randomly:"
]
},
{
@ -364,9 +364,11 @@
},
"outputs": [],
"source": [
"def random_play(moves=search(), valid_coins=list(all_coins - {'HHHH'})):\n",
"def random_play(moves):\n",
" \"Play moves against a random devil; return the number of moves until win, or None.\"\n",
" coins = random.choice(valid_coins)\n",
" coins = random.choice(list(all_coins))\n",
" if coins == 'HHHH':\n",
" return 0\n",
" for (i, move) in enumerate(moves, 1):\n",
" coins = random.choice(list(rotations(coins)))\n",
" coins = flip(coins, move)\n",
@ -378,7 +380,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"There are 15 `valid_coins` sequences, so let's call `random_play` 15,000 times, and count the results:"
"There are 16 coin sequences so let's call `random_play` 16,000 times, and count the results:"
]
},
{
@ -391,21 +393,22 @@
{
"data": {
"text/plain": [
"Counter({1: 1021,\n",
" 2: 1011,\n",
" 3: 997,\n",
" 4: 926,\n",
" 5: 1009,\n",
" 6: 999,\n",
" 7: 1019,\n",
" 8: 951,\n",
" 9: 1028,\n",
" 10: 976,\n",
" 11: 1036,\n",
" 12: 1026,\n",
" 13: 999,\n",
" 14: 1036,\n",
" 15: 966})"
"Counter({0: 993,\n",
" 1: 994,\n",
" 2: 969,\n",
" 3: 1004,\n",
" 4: 992,\n",
" 5: 992,\n",
" 6: 957,\n",
" 7: 1002,\n",
" 8: 1021,\n",
" 9: 1013,\n",
" 10: 1001,\n",
" 11: 1023,\n",
" 12: 1007,\n",
" 13: 1036,\n",
" 14: 1026,\n",
" 15: 970})"
]
},
"execution_count": 11,
@ -414,14 +417,17 @@
}
],
"source": [
"Counter(random_play() for _ in range(15000))"
"moves = search()\n",
"\n",
"Counter(random_play(moves) for _ in range(16000))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This says that the player always wins (if the player ever lost, there would be an entry for `None` in the Counter), and the number of moves it takes to win is remarkably evenly distributed."
"This says that the player always wins. If the player ever lost, there would be an entry for `None` in the Counter.\n",
"The remarkable thing, which I can't explain, is that the number of moves taken to win is so evenly distributed: very nearly 1,000 times for each of the numbers from 0 to 15 moves. Can you explain that?"
]
}
],