From 4d80be6834d9f1ce9065685f42038c0a4364c6f1 Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Tue, 29 Aug 2017 11:39:07 -0700 Subject: [PATCH] Add files via upload --- Coin Flip.ipynb | 116 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 97 insertions(+), 19 deletions(-) diff --git a/Coin Flip.ipynb b/Coin Flip.ipynb index 120447b..2175f8e 100644 --- a/Coin Flip.ipynb +++ b/Coin Flip.ipynb @@ -337,7 +337,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "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_devil` takes a sequence of moves and plays those moves with a devil that chooses randomly:" + "That's a 15-move sequence that is guaranteed to lead to a win. \n", + "\n", + "# Verifying the Solution\n", + "\n", + "Do I believe the solution is correct? Playing 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 a sequence of moves and plays those moves with a devil that chooses randomly, returning the number of moves it takes until the player wins:" ] }, { @@ -376,22 +380,22 @@ { "data": { "text/plain": [ - "Counter({0: 969,\n", - " 1: 1034,\n", - " 2: 966,\n", - " 3: 1026,\n", - " 4: 975,\n", - " 5: 979,\n", - " 6: 987,\n", - " 7: 1047,\n", - " 8: 978,\n", - " 9: 979,\n", - " 10: 998,\n", - " 11: 1033,\n", - " 12: 1025,\n", - " 13: 973,\n", - " 14: 1038,\n", - " 15: 993})" + "Counter({0: 1037,\n", + " 1: 1006,\n", + " 2: 1011,\n", + " 3: 998,\n", + " 4: 980,\n", + " 5: 976,\n", + " 6: 1057,\n", + " 7: 1043,\n", + " 8: 963,\n", + " 9: 1021,\n", + " 10: 966,\n", + " 11: 1012,\n", + " 12: 995,\n", + " 13: 975,\n", + " 14: 990,\n", + " 15: 970})" ] }, "execution_count": 11, @@ -409,8 +413,82 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This says that the player won all 16,000 times. 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 there are very nearly 1,000 results for each of the counts from 0 to 15. Can you explain that?" + "This says that the player won all 16,000 times. (If the player ever lost, there would have been an entry for `None` in the Counter.)\n", + "The remarkable thing, which I can't explain, is that there are very nearly 1,000 results for each of the counts from 0 to 15. Can you explain that?\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. But I will do two things to make the belief state easier to read. First, I will *canonicalize* the belief state to be independent of rotations. By that I mean that if the belief stae contains `{'HHHT', 'HHTH', 'HTHH', 'THHH'}`, which are all rotations of each other, I will only print one of them. (Arbitrarily, I choose the one that comes first alphabetically, `'HHHT'`.) This gets us down from 16 coin sequences in the belief state to six.\n", + "Second, I will print each coin sequence in its own column, so that they line up and it is easy to see when a particular coin sequence disappears from the belief state" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def show(moves):\n", + " \"For each move, print the move number, move, and belief state.\"\n", + " belief = initial_belief\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 join(items, sep=''): return sep.join(map(str, items))\n", + " \n", + "def canonical(belief): return sorted(set(min(rotations(coins)) for coins in belief))\n", + "\n", + "def show_line(i, move, belief, order=canonical(initial_belief)):\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} {}'.format(i, join(move), join(ordered_belief, ' ')))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 0 start HHHH HHHT HHTT HTHT HTTT TTTT\n", + " 1 0123 HHHH HHHT HHTT HTHT HTTT \n", + " 2 02 HHHH HHHT HHTT HTTT TTTT\n", + " 3 0123 HHHH HHHT HHTT HTTT \n", + " 4 01 HHHH HHHT HTHT HTTT TTTT\n", + " 5 0123 HHHH HHHT HTHT HTTT \n", + " 6 02 HHHH HHHT HTTT TTTT\n", + " 7 0123 HHHH HHHT HTTT \n", + " 8 0 HHHH HHTT HTHT TTTT\n", + " 9 0123 HHHH HHTT HTHT \n", + "10 02 HHHH HHTT TTTT\n", + "11 0123 HHHH HHTT \n", + "12 01 HHHH HTHT TTTT\n", + "13 0123 HHHH HTHT \n", + "14 02 HHHH TTTT\n", + "15 0123 HHHH \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`.\n", + "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 rid of the last possibility." ] } ],