From: Neil Smith Date: Fri, 8 Sep 2017 14:37:16 +0000 (+0100) Subject: Added worked example for day 9 X-Git-Url: https://git.njae.me.uk/?a=commitdiff_plain;h=5db44dbd3a7bb57e6e8e7ff934d1095ae77f062c;p=ou-summer-of-code-2017.git Added worked example for day 9 --- diff --git a/09-resolving-the-bill/resolving-the-bill-solution.ipynb b/09-resolving-the-bill/resolving-the-bill-solution.ipynb index dc846ae..33bb1f8 100644 --- a/09-resolving-the-bill/resolving-the-bill-solution.ipynb +++ b/09-resolving-the-bill/resolving-the-bill-solution.ipynb @@ -61,24 +61,9 @@ "Given that your bill is at line 0, your friend's is on line 1, and the list of bills is still in [09-bills.txt](09-bills.txt), **which line is a mixture of your bill and your friends's bill?** (There's only one such line.)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Given two strings a and b and a target c, could c be formed form some interleaving/merge of a and b?\n", - "\n", - "For example,\n", - "Given:\n", - "s1 = \"aabcc\",\n", - "s2 = \"dbbca\",\n", - "\n", - "When s3 = \"aadbbcbcac\", return true.\n", - "When s3 = \"aadbbbaccc\", return false." - ] - }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": { "collapsed": true }, @@ -90,7 +75,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -99,7 +84,7 @@ "148" ] }, - "execution_count": 3, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -112,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -126,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -137,6 +122,130 @@ " for i in sorted(set([k[0] for k in table])))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Worked example: part 1\n", + "Ah, the problem that didn't pan out. \n", + "\n", + "This was _meant_ to be an exercise in dynamic programming, another technique taught in M269. However, I mucked up both the problem specification in part 1 and the test data in part 2, so that other, simpler, approaches gave the correct answers. \n", + "\n", + "Part 1 was meant to be a variant on the greatest-common subsequence problem, but making it _whole_ subsequence checking meant there was no need for dynamic programming. Part 2 did require something like dynamic programming for the general case, but the test data didn't force examination of all the cases, so a simpler algorithm that would gave false positives didn't return any while using this data set. \n", + "\n", + "So, part 1. We want to see if $s_1$ is a subsequence of $s_2$. The simple way is to walk along $s_2$, character by character, keeping track of how much of $s_1$ is a subsequence up to this point. I use the ppinter _i_ as the position in the next character to check in $s_1$. If, when we've finished, _i_ points beyond the end of $s_1$, $s_1$ is a subsequence of $s_2$.\n", + "\n", + "For instance, if we want to see if `abc` is a subseqence of `babaca`, we can see that:\n", + "* ø is a subsequence of `b` (_i_ == 0)\n", + "* `a` is a subsequence of `ba` (_i_ == 1)\n", + "* `ab` is a subsequence of `bab` (_i_ == 2)\n", + "* `ab` is a subsequence of `baba` (_i_ == 2)\n", + "* `abc` is a subsequence of `babac` (_i_ == 3)\n", + "* `abc` is a subsequence of `babaca` (_i_ == 3)\n", + "\n", + "That's implemented as `is_subseq_simple`. The `is_subseq_simple_shortcut` does the same, but bails out of the loop as soon as it's determined that $s_1$ is a subsequence of $s_2$; working from the end of $s_2$ means the checks are for _i_ < 0 rather than using the length of $s_1$.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The idea of the problem was dynamic programming. This comes in useful when considering the _longest common subsequence_ problem, were we have to identify the how much of $s_1$ can be found as subsequence in $s_2$.\n", + "\n", + "A recursive solution to the subsequence problem looks at the last character of each of $s_1$ and $s_2$. \n", + "\n", + "> If they're different, $s_1$ is only a subsequence of $s_2$ if $s_1$ is also a subsequence of all but the last character of $s_2$ (i.e. `abc` is a subsequence of `babaca` iff `abc` is a subsequence of `babac`). \n", + "\n", + "> If the last two characters are the same, $s_1$ is a subsequence of $s_2$ if $s_1$ is a subsequence of all but the last character of $s_2$, or all but the last character of $s_1$ is a subsequence of all but the last character of $s_2$ (i.e. `abc` is a subsequence of `babac` if `ab` is a subsequence of `baba` or `abc` is a subsequence of `baba`)\n", + "\n", + "> There are two base cases. If $s_1$ is empty, return True. If length($s_1$) > length($s_2$), return False.\n", + "\n", + "The problem with this definition is that it can do a lot of repeated work (see the image below). The complexity is $O(2^{\\text{length of } s_2})$. The dynamic programmic approach comes at the problem from the other angle. \n", + "\n", + "\"Finding\n", + "\n", + "The way I think about it is that the recursive solution would be very efficient if there was some magic lookup table we could consult, which would give the answers to the subproblems. We can build that lookup table, starting with very short fragments of $s_1$ and $s_2$, building up the table, and using previous results in the table to fill in each cell.\n", + "\n", + "In this problem, we build a table such that the cell at row _i_ and column _j_ contains True if the first _i_ characters of $s_1$ are a subsequence of the first _j_ characters of $s_2$. (Note a complication due to Python's zero-based indexing of strings and lists. The third character of $s_1$ is referred to in Python as `s1[2]`.)\n", + "\n", + "Going back to the recursive description, we can see that:\n", + "\n", + "> All cells in the top row (_i_ == 0) contain True.\n", + "\n", + "> All cells in the left column (_j_ == 0) contain False (apart from _i_ == _j_ == 0, which is True).\n", + "\n", + "> If the _i_-1 th character of $s_1$ is different from the _j_-1 th character of $s_2$, this cell (at position (_i_, _j_) ) contains the same value as the cell at (_i_, _j_ - 1) i.e. the cell to the left.\n", + "\n", + "> If the _i_-1 th character of $s_1$ is the same as the _j_-1 th character of $s_2$, this cell (at position (_i_, _j_) ) contains True if either cell at (_i_, _j_ - 1) (i.e. the cell to the left) contains True, or the cell at (_i_ - 1, _j_ - 1) (i.e. the cell diagonally above and to the left) contains True.\n", + "\n", + "As each cell in the table only references the cells above and to the left, we can fill out the table row by row, going from left to right, and know we will always have the information needed to complete each cell when we get to it.\n", + "\n", + "And that's dynamic programming. As we're filling out a table, the complexity is $O({\\text{length of } s_1} \\times {\\text{length of } s_2})$ or roughly $O\\left((\\text{length of } s_2)^2 \\right)$\n", + "\n", + "The tables below show worked examples for seeing if `acba` is a subsequnce of `aaccabab` (it is) and `cdabcaca` (it isn't).\n", + "\n", + "For the first example, we fill out the first row of the table with True (by definition).\n", + "\n", + "For the second row (with _i_ = 1), we want to see if `a` is a subsequence of different prefixes is `aaccabab`. The cell with _j_ = 0 is False, by definition. For the cell at (_i_ = 1, _j_ = 1), the characters at $s_1$[0] and $s_2$[0] are the same, so this cell is True if the cell to the left is True (it isn't) or the cell above and to the left is True (it is). So cell (1, 1) is True, and the rest of that row is filled out to True.\n", + "\n", + "For the third row (with _i_ = 2), we want to see if `ac` is a subsequence of different prefixes is `aaccabab`. The cell with _j_ = 0 and _j_ = 1 are False, by definition. For the cell at (_i_ = 2, _j_ = 2), the characters at $s_1$[1] and $s_2$[1] are different, so this cell is True is the cell to the left is True; it isn't, so this cell contains False. For the cell at (_i_ = 2, _j_ = 3), the characters at $s_1$[1] and $s_2$[2] are the same, so this cell is True if the cell to the left is True (it isn't) or the cell above and to the left is True (it is). So cell (2, 3) is True, and the rest of that row is filled out to True.\n", + "\n", + "You can continue filling out the table in the same way.\n", + "\n", + "When the table is complete, the bottom right cell contains True, which means that `acba` is a subsequnce of `aaccabab`\n", + "\n", + "| |
0|a
1|a
a
2|a
a
c
3|a
a
c
c
4|a
a
c
c
a
5|a
a
c
c
a
b
6|a
a
c
c
a
b
a
7|a
a
c
c
a
b
a
b
8|\n", + "|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n", + "|0
|T|T|T|T|T|T|T|T|T|\n", + "|1
a|.|T|T|T|T|T|T|T|T|\n", + "|2
ac|.|.|.|T|T|T|T|T|T|\n", + "|3
acb|.|.|.|.|.|.|T|T|T|\n", + "|4
acba|.|.|.|.|.|.|.|T|T|\n", + "\n", + "\n", + "| |
0|c
1|c
d
2|c
d
a
3|c
d
a
b
4|c
d
a
b
c
5|c
d
a
b
c
a
6|c
d
a
b
c
a
c
7|c
d
a
b
c
a
c
a
8|\n", + "|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n", + "|0
|T|T|T|T|T|T|T|T|T|\n", + "|1
a|.|.|.|T|T|T|T|T|T|\n", + "|2
ac|.|.|.|.|.|T|T|T|T|\n", + "|3
acb|.|.|.|.|.|.|.|.|.|\n", + "|4
acba|.|.|.|.|.|.|.|.|.|\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Worked example: part 2\n", + "This was a harder task, but the test data I provided didn't require the general case to be solved. \n", + "\n", + "The task was to return if $s_1$ and $s_2$ could be interleaved to form $s_3$. That's a stronger condition than just saying that both $s_1$ and $s_2$ are subsequences of $s_3$. For instance, `aba` and `aca` are both subsequences of `abbcca`, but there's no way of interleaving `aba` and `aca` to form `abbcca` (the interleaved sequence should have four `a`s, one `b`, and one `c`).\n", + "\n", + "For the test data provided, there was only one string which had both $s_1$ and $s_2$ as subsequences. I should have given other distractors in the test data, where $s_1$ and $s_2$ were both subsequences but the distactor wasn't formed from the interleaving.\n", + "\n", + "Anyway, the solution I was hoping for was another dynamic programming one. \n", + "\n", + "A recursive solution to the problem (can $s_1$ and $s_2$ be interleaved to form $s_3$?) looks like:\n", + "\n", + "> If the last characters of $s_1$ and $s_3$ are the same, $s_1$ and $s_2$ be interleaved to form $s_3$ if `butlast`($s_1$) and $s_2$ can be interleaved to form `butlast`($s_3$).\n", + "\n", + "> If the last characters of $s_2$ and $s_3$ are the same, $s_1$ and $s_2$ be interleaved to form $s_3$ if $s_1$ and `butlast`($s_2$) can be interleaved to form `butlast`($s_3$).\n", + "\n", + "> If the last characters of $s_1$ and $s_2$ and $s_3$ are all the same, check both of the conditions above, returning True if either is True.\n", + "\n", + "> If the last characters of $s_1$ and $s_2$ and $s_3$ are all different, return False.\n", + "\n", + "> There are three base cases. If $s_1$ is empty, return $s_2$ == $s_3$. If $s_2$ is empty, return $s_1$ == $s_3$. If $s_1$ + $s_2$ is longer than $s_3$, return False.\n", + "\n", + "This gives us the ammunition to build the dynamic programming table. The cell at (_i_, _j_) will contain True if the first _i_ characters of $s_1$ can be interleaved with the first _j_ characters of $s_2$ to form the first _i_ + _j_ characters of $s_3$.\n", + "\n", + "All cells in the table initially contain False.\n", + "\n", + "When filling out the table, you either look at the cell to the left (if the last characters of $s_1$ and $s_3$ are the same) or the cell above (if the last characters of $s_2$ and $s_3$ are the same). " + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -144,6 +253,22 @@ "## Part 1" ] }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def is_subseq_simple(s1, s2):\n", + " i = 0\n", + " for c in s2:\n", + " if i < len(s1) and s1[i] == c:\n", + " i += 1\n", + " return i == len(s1)" + ] + }, { "cell_type": "code", "execution_count": 6, @@ -151,6 +276,23 @@ "collapsed": true }, "outputs": [], + "source": [ + "def is_subseq_simple_shortcut(s1, s2):\n", + " i = len(s1) - 1\n", + " for c in reversed(s2):\n", + " if s1[i] == c:\n", + " i -= 1\n", + " if i < 0: break\n", + " return i < 0" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": true + }, + "outputs": [], "source": [ "def show_backtrace_s(bps):\n", " i = max([0] + [k[0] for k in bps])\n", @@ -170,7 +312,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -190,24 +332,7 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "def is_subseq_greedy(s1, s2):\n", - " i = j = 0\n", - " while i < len(s1) and j < len(s2):\n", - " if s1[i] == s2[j]:\n", - " i += 1\n", - " j += 1\n", - " return i == len(s1)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": { "collapsed": true }, @@ -262,7 +387,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": { "collapsed": true }, @@ -313,7 +438,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -322,7 +447,7 @@ "22" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -335,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -344,7 +469,7 @@ "22" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -357,7 +482,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -366,7 +491,7 @@ "22" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -374,19 +499,41 @@ "source": [ "sum(1 for s in bills\n", " if s != 0\n", - " if is_subseq_greedy(bills[0], bills[s]))" + " if is_subseq_simple(bills[0], bills[s]))" ] }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "22" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum(1 for s in bills\n", + " if s != 0\n", + " if is_subseq_simple_shortcut(bills[0], bills[s]))" + ] + }, + { + "cell_type": "code", + "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "100 loops, best of 3: 13.1 ms per loop\n" + "100 loops, best of 3: 6.11 ms per loop\n" ] } ], @@ -394,19 +541,39 @@ "%%timeit\n", "sum(1 for s in bills\n", " if s != 0\n", - " if is_subseq_greedy(bills[0], bills[s]))" + " if is_subseq_simple(bills[0], bills[s]))" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "1 loop, best of 3: 7.88 s per loop\n" + "100 loops, best of 3: 4.24 ms per loop\n" + ] + } + ], + "source": [ + "%%timeit\n", + "sum(1 for s in bills\n", + " if s != 0\n", + " if is_subseq_simple_shortcut(bills[0], bills[s]))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 loop, best of 3: 7.75 s per loop\n" ] } ], @@ -419,7 +586,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 21, "metadata": { "scrolled": true }, @@ -451,7 +618,7 @@ " 146]" ] }, - "execution_count": 15, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -464,15 +631,15 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 148 ms, sys: 4 ms, total: 152 ms\n", - "Wall time: 150 ms\n" + "CPU times: user 140 ms, sys: 4 ms, total: 144 ms\n", + "Wall time: 144 ms\n" ] }, { @@ -481,7 +648,7 @@ "(True, False)" ] }, - "execution_count": 16, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -492,7 +659,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 23, "metadata": { "collapsed": true }, @@ -503,7 +670,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -730,7 +897,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 25, "metadata": { "collapsed": true }, @@ -758,7 +925,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 26, "metadata": { "collapsed": true }, @@ -834,7 +1001,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 27, "metadata": { "collapsed": true }, @@ -907,7 +1074,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 28, "metadata": { "collapsed": true }, @@ -987,7 +1154,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 29, "metadata": { "collapsed": true }, @@ -1011,7 +1178,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -1020,7 +1187,7 @@ "[30]" ] }, - "execution_count": 24, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -1032,7 +1199,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 31, "metadata": {}, "outputs": [ { @@ -1041,7 +1208,7 @@ "[30]" ] }, - "execution_count": 25, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -1053,7 +1220,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 32, "metadata": {}, "outputs": [ { @@ -1062,7 +1229,7 @@ "[30]" ] }, - "execution_count": 26, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -1074,7 +1241,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -1083,20 +1250,20 @@ "[30]" ] }, - "execution_count": 27, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[s for s in bills\n", - " if is_subseq(bills[0], bills[s])\n", - " if is_subseq(bills[1], bills[s])]" + " if is_subseq_simple(bills[0], bills[s])\n", + " if is_subseq_simple(bills[1], bills[s])]" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 34, "metadata": {}, "outputs": [ { @@ -1313,7 +1480,7 @@ "True" ] }, - "execution_count": 28, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -1327,7 +1494,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 35, "metadata": {}, "outputs": [ { @@ -1336,7 +1503,7 @@ "[30]" ] }, - "execution_count": 32, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -1348,14 +1515,14 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 36, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "1 loop, best of 3: 3.12 s per loop\n" + "1 loop, best of 3: 3.04 s per loop\n" ] } ], @@ -1367,14 +1534,14 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "1 loop, best of 3: 971 ms per loop\n" + "1 loop, best of 3: 960 ms per loop\n" ] } ], @@ -1386,14 +1553,14 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "1 loop, best of 3: 1.56 s per loop\n" + "1 loop, best of 3: 1.47 s per loop\n" ] } ], @@ -1405,14 +1572,14 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "1000 loops, best of 3: 510 µs per loop\n" + "1000 loops, best of 3: 511 µs per loop\n" ] } ], @@ -1439,7 +1606,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 40, "metadata": {}, "outputs": [ { @@ -1448,7 +1615,7 @@ "(4.223305555555555, 4, 13, 23.899999999999636)" ] }, - "execution_count": 37, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -1464,7 +1631,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 41, "metadata": {}, "outputs": [ { @@ -1473,7 +1640,7 @@ "(11.13438888888889, 11, 8, 3.8000000000029104)" ] }, - "execution_count": 38, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" } @@ -1489,7 +1656,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 42, "metadata": {}, "outputs": [ { @@ -1498,7 +1665,7 @@ "(15.36486111111111, 15, 21, 53.5)" ] }, - "execution_count": 39, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } @@ -1523,7 +1690,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 43, "metadata": { "collapsed": true }, @@ -1537,7 +1704,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 44, "metadata": { "collapsed": true }, @@ -1561,7 +1728,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 45, "metadata": {}, "outputs": [ { @@ -1585,7 +1752,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 46, "metadata": {}, "outputs": [ { @@ -1606,7 +1773,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 47, "metadata": {}, "outputs": [ { @@ -1642,7 +1809,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 48, "metadata": {}, "outputs": [ { @@ -1666,7 +1833,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 49, "metadata": {}, "outputs": [ { @@ -1726,7 +1893,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2+" + "version": "3.5.3" } }, "nbformat": 4,