{"cells":[{"cell_type":"markdown","metadata":{},"source":"On Recursion\n============\n\n"},{"cell_type":"markdown","metadata":{},"source":["## Agenda\n\n"]},{"cell_type":"markdown","metadata":{},"source":["1. Recursion\n - stopping recursion: simplification & base cases\n\n2. Recursive \"shapes\":\n - Linear (single) recursion:\n - Factorial\n - Addition\n - Binary search\n \n - Tree (multiple) recursion: *divide and conquer*\n - Fibonacci numbers\n - Tower of Hanoi\n - Merge sort\n - Making change\n\n3. The Call Stack and Stack Frames\n - simulating recursion\n - debugging with `pdb` and `%debug`\n\n"]},{"cell_type":"markdown","metadata":{},"source":["## 1. Recursion\n\n"]},{"cell_type":"markdown","metadata":{},"source":["Recursive functions, directly or indirectly, call themselves.\n\nRecursive solutions are applicable when a problem can be broken down\ninto more easily solved sub-problems that resemble the original, and\nwhose solutions can then be combined.\n\nE.g., computing the combined price of a bunch of nested shopping bags of\nitems:\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["class Bag:\n def __init__(self, price, *contents):\n self.price = price\n self.contents = contents"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["bag1 = Bag(10)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["bag2 = Bag(5, Bag(3))"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["bag3 = Bag(5, Bag(4, Bag(3)), Bag(2))"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["bag4 = Bag(0, Bag(5), Bag(10), Bag(3, Bag(2), Bag(100)), Bag(9, Bag(2, Bag(25))))"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def price(bag):\n pass"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["price(bag1)"]},{"cell_type":"markdown","metadata":{},"source":["### Stopping recursion: simplification & base case(s)\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["import sys\nsys.setrecursionlimit(200)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def silly_rec(n):\n print(n)\n pass"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["silly_rec(1)"]},{"cell_type":"markdown","metadata":{},"source":["## 1. Recursive \"shapes\"\n\n"]},{"cell_type":"markdown","metadata":{},"source":["### Linear recursion\n\n"]},{"cell_type":"markdown","metadata":{},"source":["#### Example: Factorial\n\n"]},{"cell_type":"markdown","metadata":{},"source":["$n! = \\begin{cases} 1 & \\text{if}\\ n=0 \\\\ n \\cdot (n-1)! & \\text{if}\\ n>0 \\end{cases}$\n\ni.e., $n! = n \\cdot (n-1) \\cdot (n-2) \\cdots 3 \\cdot 2 \\cdot 1$\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def rec_factorial(n):\n print('n = ', n)\n pass\n\nrec_factorial(10)"]},{"cell_type":"markdown","metadata":{},"source":["#### Example: Addition of two positive numbers $m$, $n$\n\n"]},{"cell_type":"markdown","metadata":{},"source":["$m + n = \\begin{cases} m & \\text{if}\\ n=0 \\\\ (m + 1) + (n - 1) & \\text{if}\\ n > 0 \\end{cases}$\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def add(m, n):\n print('m, n = ', (m, n))\n pass"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["add(5, 0)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["add(5, 1)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["add(5, 5)"]},{"cell_type":"markdown","metadata":{},"source":["#### Example: Binary search\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def bin_search(x, lst):\n pass"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["bin_search(20, list(range(100)))"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["bin_search(-1, list(range(100)))"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["bin_search(50.5, list(range(100)))"]},{"cell_type":"markdown","metadata":{},"source":["### Tree recursion\n\n"]},{"cell_type":"markdown","metadata":{},"source":["#### Example: Fibonacci numbers\n\n"]},{"cell_type":"markdown","metadata":{},"source":["$fib(n) = \\begin{cases} 0 & \\text{if}\\ n=0 \\\\ 1 & \\text{if}\\ n=1 \\\\ fib(n-1) + fib(n-2) & \\text{otherwise} \\end{cases}$\n\ni.e., 0, 1, 1, 2, 3, 5, 8, 13, 21, …\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def rec_fib(n):\n print('n = ', n)\n pass\n\nrec_fib(5)"]},{"cell_type":"markdown","metadata":{},"source":["#### Example: Tower of Hanoi\n\n"]},{"cell_type":"markdown","metadata":{},"source":["Setup: three rods, with one or more discs of different sizes all stacked\non one rod, smallest (top) to largest (bottom). E.g.,\n\n || || ||\n == || ||\n ==== || ||\n ====== || ||\n ------------------------------------\n\nGoal: move all the discs, one by one, to another rod, with the rules\nbeing that (1) only smaller discs can be stacked on larger ones and (2)\nonly the top disc in a stack can be moved to another rod.\n\nFor three discs, as shown above, we would carry out the following\nsequence to move the stack to the rightmost rod. The rods are\nabbreviated L (left), M (middle), R (right):\n\n1. Move the small disc (0) from L to R\n2. Move the medium disc (1) from L to M\n3. Move 0 from R to M (R is empty)\n4. Move the large disc (2) from L to R\n5. Move 0 from M to L\n6. Move 1 from M to R\n7. Move 0 from L to R (done)\n\nCan you come up with the sequence needed to move a stack of 4 discs from\none rod to another? 5 discs? An arbitrary number of discs?\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["height = 3\ntowers = [[] for _ in range(3)]\ntowers[0] = list(range(height, 0, -1))\n\ndef move(frm, to):\n towers[to].append(towers[frm].pop(-1))\n display()\n\ndef hanoi(frm, to, using, levels):\n pass"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["towers"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["from time import sleep\nfrom IPython.display import clear_output\n\ndef display():\n clear_output(True)\n print('{:^12}'.format('||') * 3)\n for level in range(height, 0, -1):\n for t in towers:\n try:\n print('{:^12}'.format('==' * t[level-1]), end='')\n except IndexError:\n print('{:^12}'.format('||'), end='')\n print()\n print('-' * 36)\n sleep(1)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["display()"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["hanoi(0, 2, 1, 3)"]},{"cell_type":"markdown","metadata":{},"source":["#### Example: Mergesort\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def merge(l1, l2): # O(N), where N is the number of elements in the two lists\n merged = []\n i1 = i2 = 0\n while i1 < len(l1) or i2 < len(l2):\n if i2 == len(l2) or (i1 < len(l1)\n and l1[i1] < l2[i2]):\n merged.append(l1[i1])\n i1 += 1\n else:\n merged.append(l2[i2])\n i2 += 1\n return merged"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["l1 = [1, 5, 9]\nl2 = [2, 6, 8, 11]\nmerge(l1, l2)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def mergesort(lst):\n pass"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["import random\nlst = list(range(10))\nrandom.shuffle(lst)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["lst"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["mergesort(lst)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def insertion_sort(lst):\n for i in range(1, len(lst)):\n for j in range(i, 0, -1):\n if lst[j-1] > lst[j]:\n lst[j-1], lst[j] = lst[j], lst[j-1] # swap\n else:\n break"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["class Heap:\n def __init__(self):\n self.data = []\n\n @staticmethod\n def _parent(idx):\n return (idx-1)//2\n\n @staticmethod\n def _left(idx):\n return idx*2+1\n\n @staticmethod\n def _right(idx):\n return idx*2+2\n\n def _heapify(self, idx=0):\n while True:\n l = Heap._left(idx)\n r = Heap._right(idx)\n maxidx = idx\n if l < len(self) and self.data[l] > self.data[idx]:\n maxidx = l\n if r < len(self) and self.data[r] > self.data[maxidx]:\n maxidx = r\n if maxidx != idx:\n self.data[idx], self.data[maxidx] = self.data[maxidx], self.data[idx]\n idx = maxidx\n else:\n break\n\n def add(self, x):\n self.data.append(x)\n i = len(self.data) - 1\n p = Heap._parent(i)\n while i > 0 and self.data[p] < self.data[i]:\n self.data[p], self.data[i] = self.data[i], self.data[p]\n i = p\n p = Heap._parent(i)\n\n def max(self):\n return self.data[0]\n\n def pop_max(self):\n ret = self.data[0]\n self.data[0] = self.data[len(self.data)-1]\n del self.data[len(self.data)-1]\n self._heapify()\n return ret\n\n def __bool__(self):\n return len(self.data) > 0\n\n def __len__(self):\n return len(self.data)\n\n\ndef heapsort(iterable):\n heap = Heap()\n for x in iterable:\n heap.add(x)\n sorted_lst = []\n while heap:\n sorted_lst.append(heap.pop_max())\n sorted_lst.reverse()\n return sorted_lst"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["import timeit\nimport random\ninsertionsort_times = []\nheapsort_times = []\nmergesort_times = []\nfor size in range(100, 3000, 100):\n insertionsort_times.append(timeit.timeit(stmt='insertion_sort(lst)',\n setup='import random ; from __main__ import insertion_sort ; '\n 'lst = [random.random() for _ in range({})]'.format(size),\n number=1))\n heapsort_times.append(timeit.timeit(stmt='heapsort(lst)',\n setup='import random ; from __main__ import heapsort ; '\n 'lst = [random.random() for _ in range({})]'.format(size),\n number=1))\n mergesort_times.append(timeit.timeit(stmt='mergesort(lst)'.format(size),\n setup='import random ; from __main__ import mergesort ; '\n 'lst = [random.random() for _ in range({})]'.format(size),\n number=1))"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["%matplotlib inline\nimport matplotlib.pyplot as plt\nplt.plot(insertionsort_times, 'ro')\nplt.plot(heapsort_times, 'b^')\nplt.plot(mergesort_times, 'gs')\nplt.show()"]},{"cell_type":"markdown","metadata":{},"source":["#### Example: Making Change\n\n"]},{"cell_type":"markdown","metadata":{},"source":["Question: how many different ways are there of making up a specified\namount of money, given a list of available denominations?\n\nE.g., how many ways of making 10 cents, given 1c, 5c, 10c, 25c coins?\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def change(amount, denoms):\n pass"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["change(5, (1, 5, 10, 25))"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["change(10, (1, 5, 10, 25))"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["change(1000, (1, 5, 10, 25))"]},{"cell_type":"markdown","metadata":{},"source":["## 1. The Call Stack\n\n"]},{"cell_type":"markdown","metadata":{},"source":["### Simulating recursive `factorial`\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["class Stack(list):\n push = list.append\n pop = lambda self: list.pop(self, -1)\n peek = lambda self: self[-1]\n empty = lambda self: len(self) == 0"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["call_stack = Stack()\n\ndef call(arg):\n call_stack.push('')\n call_stack.push(('arg', arg))\n\ndef get_arg():\n return call_stack.peek()[-1]\n\ndef save_local(name, val):\n call_stack.push(('local', name, val))\n\ndef restore_local():\n return call_stack.pop()[2]\n\ndef return_with(val):\n while call_stack.pop() != '':\n pass\n call_stack.push(('ret', val))\n\ndef last_return_val():\n return call_stack.pop()[-1]"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["call(10) # initial call\nwhile True: # recursive calls\n n = get_arg()\n if n == 1:\n return_with(1)\n break\n else:\n save_local('n', n)\n call(n-1)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["call_stack"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["ret = last_return_val()\nn = restore_local()\nreturn_with(n * ret)\ncall_stack"]},{"cell_type":"markdown","metadata":{},"source":["### Debugging with `pdb` and `%debug`\n\n"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["import sys\nsys.setrecursionlimit(100)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def rec_factorial(n):\n if n <= 1: # detect base case\n raise Exception('base case!')\n else:\n return n * rec_factorial(n-1)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["rec_factorial(10)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["%debug\n# commands to try:\n# help, where, args, p n, up, u 10, down, d 10, l, up 100, u, d (& enter to repeat)"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["def bin_search(x, lst):\n if len(lst) == 0:\n return False\n else:\n print('lo, hi = ', (lst[0], lst[-1]))\n mid = len(lst) // 2\n if x == lst[mid]:\n import pdb ; pdb.set_trace()\n return True\n elif x < lst[mid]:\n return bin_search(x, lst[:mid])\n else:\n return bin_search(x, lst[mid+1:])"]},{"cell_type":"code","execution_count":1,"metadata":{},"outputs":[],"source":["bin_search(20, list(range(100)))"]}],"metadata":{"org":null,"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.5.2"}},"nbformat":4,"nbformat_minor":0}