diff --git a/Coin Flip.ipynb b/Coin Flip.ipynb
index 2175f8e..84fe89c 100644
--- a/Coin Flip.ipynb
+++ b/Coin Flip.ipynb
@@ -6,19 +6,19 @@
"source": [
"# The Devil and the Coin Flip Game\n",
"\n",
- ">You're playing a game with the devil, with your soul at stake. You're sitting at a circular table which has 4 coins, arranged in a diamond, at the 12, 3, 6, and 9 o'clock positions. We'll number those as positions 0, 1, 2, and 3, respectively. You are blindfolded, and can never see the coins or the table.\n",
+ ">You're playing a game with the devil, with your soul at stake. You're sitting at a circular table which has 4 coins, arranged in a diamond, at the 12, 3, 6, and 9 o'clock positions. You are blindfolded, and can never see the coins or the table.\n",
"\n",
- ">Your goal is to get all 4 coins showing heads, by telling the devil the position number(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. The game ends when 4 heads come up and you win; until then you keep making moves.\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 to flip positions 0 and 2 (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 locations, which are the two other coins from the ones you specified. You won't know which of these actions the devil took.\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",
"\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",
+ "> 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",
"# Analysis\n",
"\n",
"The player, being blindfolded, does not know the true state of the coins. So the player should represent what is known: 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 24 = 16 possibilities in the belief state. \n",
"\n",
- "Each possible move, such as `{0, 2}`, which means \"flip the 12 o'clock and 6 o'clock positions,\" updates the belief state as follows: for every coin sequence in the current belief state, rotate it in every possible way, and then flip the appropriate coins. Collect all these results together to form the new belief state. To solve the game, I need to come up with a sequence of moves that ends in a belief state consisting of just `{'HHHH'}`. I want it to be a shortest possible sequence, so a breadth-first search seems right. The search space will be tiny, so compute time will be trivial; the only issue is specifying the domain correctly. (To increase the chance of getting it correct, I won't try to do anything fancy, such as noticing that some sequences are rotational variants of other sequences.)\n",
+ "A move updates the belief state as follows: for every coin sequence in the current belief state, rotate it in every possible way, and then flip the coins specified by the position(s) in the move. Collect all these results together to form the new belief state. Solving the game means coming up with a sequence of moves that ends in a belief state consisting of just `{'HHHH'}`. It must be a shortest possible sequence, so a breadth-first search seems right. The search space will be tiny, so compute time will be trivial; the only issue is specifying the domain correctly. To increase the chance of getting it correct, I won't try to do anything fancy, such as noticing that some coin sequences are rotational variants of other sequences.\n",
"\n",
"\n",
"# Implementation Choices\n",
@@ -27,7 +27,8 @@
"\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",
- "- `initial_belief`: The set of all possible coin sequences.\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",
+ "- `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",
"- `update`: The function `update(belief, move)` retuns 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."
@@ -37,7 +38,7 @@
"cell_type": "code",
"execution_count": 1,
"metadata": {
- "collapsed": true
+ "collapsed": false
},
"outputs": [],
"source": [
@@ -45,21 +46,21 @@
"from itertools import product, combinations\n",
"import random\n",
"\n",
- "Coins = ''.join # Function to make a 4-element Coin Sequence, such as 'HHHT'\n",
+ "Coins = ''.join # A coin sequence, such as 'HHHT'.\n",
+ "Belief = frozenset # A belief state is a set of possible coin sequences.\n",
+ "all_coins = Belief(map(Coins, product('HT', repeat=4)))\n",
"\n",
- "Belief = frozenset\n",
+ "def rotations(coins) -> [Coins]: \n",
+ " \"A list of all possible rotations of a coin sequence.\"\n",
+ " return [coins[r:] + coins[:r] for r in range(4)]\n",
"\n",
- "initial_belief = Belief(map(Coins, product('HT', repeat=4)))\n",
- "\n",
- "def rotations(coins): return {coins[r:] + coins[:r] for r in range(4)}\n",
- "\n",
- "def update(belief, move):\n",
+ "def update(belief, move) -> Belief:\n",
" \"Update belief: consider all possible rotations, then flip.\"\n",
- " return Belief((flip(c, move) if c != 'HHHH' else c)\n",
+ " return Belief((c if c == 'HHHH' else flip(c, move))\n",
" for coins in belief\n",
" for c in rotations(coins))\n",
"\n",
- "def flip(coins, move):\n",
+ "def flip(coins, move) -> Coins:\n",
" \"Flip the coins in the positions specified by the move.\"\n",
" coins = list(coins) # Need a mutable sequence\n",
" for i in move:\n",
@@ -71,7 +72,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Let's try out some of the functions to see if they look right:"
+ "Let's try out these functions:"
]
},
{
@@ -106,7 +107,7 @@
{
"data": {
"text/plain": [
- "{'HHHT', 'HHTH', 'HTHH', 'THHH'}"
+ "['HHHT', 'HHTH', 'HTHH', 'THHH']"
]
},
"execution_count": 3,
@@ -152,7 +153,14 @@
}
],
"source": [
- "initial_belief"
+ "all_coins"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We see there are 16 coin sequences in the `all_coins` belief state. Now if we update this belief state by flipping all 4 positions, we should get a new belief state where we have eliminated the possibility of 4 tails, leaving 15 possible coin sequences:"
]
},
{
@@ -188,21 +196,14 @@
}
],
"source": [
- "update(initial_belief, {0, 1, 2, 3})"
+ "update(all_coins, {0, 1, 2, 3})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "That says that if we flip all 4 coins, we eliminate the possibility of 4 tails, cutting the possibilities down from 16 to 15."
- ]
- },
- {
- "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 positions; these are the possible moves:"
]
},
{
@@ -264,12 +265,11 @@
"# 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",
- "find a path that leads to the goal belief state `{'HHHH'}` (meaning that the only possibility is 4 heads).\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",
"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.\n",
- " "
+ "the states we have already explored, so that we don't have to revisit them."
]
},
{
@@ -280,21 +280,28 @@
},
"outputs": [],
"source": [
- "def search(start=initial_belief):\n",
- " \"Breadth-first search from starting belief state using moves.\"\n",
+ "def search():\n",
+ " \"Breadth-first search from all_coins to all heads.\"\n",
" explored = set()\n",
- " q = deque([Node([], start)])\n",
- " while q:\n",
- " (path, belief) = q.popleft()\n",
+ " queue = deque([Node([], all_coins)])\n",
+ " while queue:\n",
+ " (path, belief) = queue.popleft()\n",
" if belief == {'HHHH'}:\n",
" return path\n",
" for move in powerset(range(4)):\n",
" belief2 = update(belief, move)\n",
" if belief2 not in explored:\n",
" explored.add(belief2)\n",
- " q.append(Node(path + [move], belief2))\n",
+ " queue.append(Node(path + [move], belief2))\n",
" \n",
- "def Node(path, belief): return (path, belief)"
+ "def Node(path, belief) -> tuple: return (path, belief)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# A Solution"
]
},
{
@@ -337,11 +344,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "That's a 15-move sequence that is guaranteed to lead to a win. \n",
+ "That's a 15-move sequence that is guaranteed to lead to a win. You can stop reading here if all you want is the answer to the puzzle. Or you can continue on ...\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:"
+ "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:"
]
},
{
@@ -352,11 +359,10 @@
},
"outputs": [],
"source": [
- "def random_devil(moves):\n",
- " \"A random devil responds to moves; return the number of moves until win, or None.\"\n",
- " coins = random.choice(list(initial_belief))\n",
- " if coins == 'HHHH':\n",
- " return 0\n",
+ "def random_devil(coins, moves) -> int or None:\n",
+ " \"\"\"A random devil responds to moves starting from coins; \n",
+ " 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",
" if coins == 'HHHH': \n",
@@ -367,7 +373,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "There are 16 coin sequences so let's call `random_play` 16,000 times, and count the results:"
+ "I will let the `random_devil` play 1000 times from each possible starting coin sequence:"
]
},
{
@@ -380,22 +386,22 @@
{
"data": {
"text/plain": [
- "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})"
+ "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})"
]
},
"execution_count": 11,
@@ -406,20 +412,23 @@
"source": [
"moves = search()\n",
"\n",
- "Counter(random_devil(moves) for _ in range(16000))"
+ "Counter(random_devil(coins, moves) \n",
+ " for coins in all_coins\n",
+ " for _ in range(1000))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "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",
+ "This says that the player won all 16,000 times. (If the player had 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 exactly 1,000 results for each of the move counts from 0 to 15. Can you explain that?\n",
"\n",
- "# Visualizing the Solution\n",
+ "# Canonical Coin Sequences and Moves\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"
+ "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."
]
},
{
@@ -430,23 +439,14 @@
},
"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",
+ "def Belief(coin_collection): \n",
+ " \"A set of all the coin sequences in this collection, canonicalized.\"\n",
+ " return frozenset(min(rotations(c)) for c in coin_collection)\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, ' ')))"
+ "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]"
]
},
{
@@ -455,27 +455,109 @@
"metadata": {
"collapsed": false
},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "frozenset({'HHHH', 'HHHT', 'HHTT', 'HTHT', 'HTTT', 'TTTT'})"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Belief(all_coins)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{0, 1, 2, 3}, {0, 1, 2}, {0, 1}, {0, 2}, {0}]"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "canonical_moves(_)"
+ ]
+ },
+ {
+ "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",
+ "\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",
+ "\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."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "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 ' ')\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\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"
+ " 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"
]
}
],
@@ -487,8 +569,601 @@
"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."
+ "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",
+ "\n",
+ "def rotations(coins) -> [Coins]: \n",
+ " \"A list of all possible rotations of a coin sequence.\"\n",
+ " return [coins[r:] + coins[:r] for r in range(len(coins))]\n",
+ " \n",
+ "def update(belief, move) -> Belief:\n",
+ " \"Update belief: consider all possible rotations, then flip.\"\n",
+ " return Belief((flip(c, move) if 'T' in c else c)\n",
+ " 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)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First verify that we didn't break `search`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{0, 1, 2, 3},\n",
+ " {0, 2},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 2},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1, 2},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 2},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 2},\n",
+ " {0, 1, 2, 3}]"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "search(4)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now test the new definitions:"
+ ]
+ },
+ {
+ "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": {
+ "text/plain": [
+ "frozenset({'HHHH', 'HHHT', 'HHTT', 'HTHT', 'HTTT', 'TTTT'})"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "canonical_coins(4)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "frozenset({'HHHHH',\n",
+ " 'HHHHT',\n",
+ " 'HHHTT',\n",
+ " 'HHTHT',\n",
+ " 'HHTTT',\n",
+ " 'HTHTT',\n",
+ " 'HTTTT',\n",
+ " 'TTTTT'})"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "canonical_coins(5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "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": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "canonical_moves(_)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{1: 2, 2: 3, 3: 4, 4: 6, 5: 8, 6: 14, 7: 20, 8: 36, 9: 60, 10: 108}"
+ ]
+ },
+ "execution_count": 23,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "{N: len(canonical_coins(N))\n",
+ " for N in range(1, 11)}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "On the one hand this is encouraging; there are only 108 canonical coin sequences of length 10, far less than the 1024 non-canonical squences. On the other hand, it is discouraging; since we are searching over the belief states, that would be 2108 ≌ 1032 belief states, which is infeasible. However, we should be able to easily handle up to N=7, because 220 is only a million.\n",
+ "\n",
+ "# Solutions for 1 to 7 Coins"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{1: [{0}],\n",
+ " 2: [{0, 1}, {0}, {0, 1}],\n",
+ " 3: None,\n",
+ " 4: [{0, 1, 2, 3},\n",
+ " {0, 2},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 2},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1, 2},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 2},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 2},\n",
+ " {0, 1, 2, 3}],\n",
+ " 5: None,\n",
+ " 6: None,\n",
+ " 7: None}"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "{N: search(N) for N in range(1, 8)}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "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*N* - 1. \n",
+ "\n",
+ "# Solution for 8 Coins\n",
+ "\n",
+ "Even though there are 236 = 68 billion belief states fo N = 8, I hope we can find a solution early in the search process. Let's try:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPU times: user 1min 47s, sys: 731 ms, total: 1min 48s\n",
+ "Wall time: 1min 50s\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "255"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "%time solution8 = search(8)\n",
+ "len(solution8)"
+ ]
+ },
+ {
+ "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:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 3},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 2, 4, 5, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 1, 4, 5},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7},\n",
+ " {0, 2, 4, 6},\n",
+ " {0, 1, 2, 3, 4, 5, 6, 7}]"
+ ]
+ },
+ "execution_count": 26,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "solution8"
]
}
],