{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Motivating Recursion: When Loops Get Tedious\n",
"\n",
"Recursion is an extremely powerful feature of programming languages, and it can appear intimidating, but it's worth learning. [One popular blog post from back in the day](https://www.joelonsoftware.com/2005/12/29/the-perils-of-javaschools-2/) even calls out recursion as one of the defining challenges of a computer-science education, such that companies should try to check for understanding when interviewing programmers.\n",
"\n",
"Let's try to demystify recursion with a recipe for thinking about how to apply it. First, some examples will help us see why recursive programs can be so much more compact and understandable. It's not hard to sum up the numbers in a list, using a loop."
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {},
"outputs": [],
"source": [
"def list_sum(L):\n",
" \"\"\"Return the sum of the elements in a list.\"\"\"\n",
"\n",
" total = 0\n",
" for x in L:\n",
" total += x\n",
" return total"
]
},
{
"cell_type": "code",
"execution_count": 94,
"metadata": {},
"outputs": [],
"source": [
"assert list_sum([]) == 0\n",
"assert list_sum([1]) == 1\n",
"assert list_sum([1, 2, 3]) == 6\n",
"assert list_sum([1, -1, 2, -2, 100, -100, 6]) == 6"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So far so good. That problem *is* a good match for loops. What about if we want to sum all the numbers in lists of lists of lists ... of numbers, for an arbitrary depth? (See the test cases below for help understanding what we are trying to do.) Now we have to work harder, essentially maintaining a classic data structure called a *stack*, which is essentially a list where we periodically add elements to the front or remove elements from the front -- we never \"skip into\" the list and change its middle or end."
]
},
{
"cell_type": "code",
"execution_count": 95,
"metadata": {},
"outputs": [],
"source": [
"def sum_all(L):\n",
" \"\"\"Return the sum of all elements in a list with arbitrary nesting.\"\"\"\n",
"\n",
" total = 0\n",
" while len(L) > 0:\n",
" if isinstance(L[0], list):\n",
" # Oh, the first value is a list? \n",
" # Rather then adding it to the total, let's smoosh it together with the input.\n",
" L = L[0] + L[1:]\n",
" else:\n",
" # The first value must be something we add directly into our total.\n",
" total += L[0]\n",
" del L[0] # Remove first list element.\n",
" return total"
]
},
{
"cell_type": "code",
"execution_count": 96,
"metadata": {},
"outputs": [],
"source": [
"assert sum_all([1, 2, 3]) == 6\n",
"assert sum_all([1, [2, 3], 4]) == 10\n",
"assert sum_all([1, 2, [3, [4, [[[5]]]], 6, [7, 8]], 9]) == 45"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That was mildly exhausting and twisty, but it can get worse! What if we want a *weighted* sum, where each number adds to the total in proportion to how deep it is in the nesting? We now use our stack in a fancier way, recording when a depth multiplier needs to be decreased."
]
},
{
"cell_type": "code",
"execution_count": 101,
"metadata": {},
"outputs": [],
"source": [
"def weighted_sum_all(L):\n",
" \"\"\"Return the sum of all elements in a list with arbitrary nesting, with a catch:\n",
" Every addition to the sum should be *multiplied* by the depth where it appears.\n",
" The first level of list has depth 1, then the next level has depth 2, etc.\"\"\"\n",
"\n",
" total = 0\n",
" multiplier = 1\n",
" while len(L) > 0:\n",
" if isinstance(L[0], list):\n",
" # Oh, the first value is a list? We got more nested, so bump multiplier.\n",
" multiplier += 1\n",
" # Rather then adding the nested list to the total, let's smoosh it together with the input.\n",
" # We also leave a reminder of when to decrease the multiplier again.\n",
" L = L[0] + ['decrease'] + L[1:]\n",
" elif L[0] == 'decrease':\n",
" multiplier -= 1\n",
" del L[0]\n",
" else:\n",
" # The first value must be something we add directly into our total.\n",
" total += multiplier * L[0]\n",
" del L[0]\n",
" return total"
]
},
{
"cell_type": "code",
"execution_count": 102,
"metadata": {},
"outputs": [],
"source": [
"assert weighted_sum_all([1]) == 1\n",
"assert weighted_sum_all([[1]]) == 2\n",
"assert weighted_sum_all([[[1]]]) == 3\n",
"assert weighted_sum_all([[1], 3]) == 5"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Let's Try That Again with Recursion\n",
"\n",
"Recursion doesn't *always* make programs nicer. For instance, it's debatable whether this recursive version of our first example has gotten more readable."
]
},
{
"cell_type": "code",
"execution_count": 103,
"metadata": {},
"outputs": [],
"source": [
"def list_sum(L):\n",
" \"\"\"Return the sum of the elements in a list.\"\"\"\n",
"\n",
" if len(L) == 0:\n",
" return 0\n",
" else:\n",
" return L[0] + list_sum(L[1:])"
]
},
{
"cell_type": "code",
"execution_count": 104,
"metadata": {},
"outputs": [],
"source": [
"assert list_sum([]) == 0\n",
"assert list_sum([1]) == 1\n",
"assert list_sum([1, 2, 3]) == 6\n",
"assert list_sum([1, -1, 2, -2, 100, -100, 6]) == 6"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Nonetheless, let's talk through a general recipe that can be followed to generate a definition like that one. Fundamentally, recursion is about *defining a thing in terms of itself*, which sounds like cheating at first! For instance, if we define a function that always calls itself with the same arguments, we run forever and never get an answer. By following some informal rules, we can avoid \"cheating\" and get useful self-referential (recursive) code.\n",
"\n",
"Here is the three-step recipe to write a recursive function, assuming you have already decided on what arguments the function takes and what it is supposed to *do*.\n",
"\n",
" 1. *Divide the input space* into at least two regions. In our list sum example, we split into empty lists and nonempty lists.\n",
" 2. *Identify some regions as **base cases** and solve them immediately* (without recursion). The base case of our last example was the empty-list case, where the sum is obviously zero.\n",
" 3. *Solve the remaining regions as **recursive cases**,* where each makes one or more calls to the same function *but where the arguments have gotten smaller*. In our sum example, we call `list_sum` recursively but on a *shorter* list, which counts as \"getting smaller.\"\n",
"\n",
"We can help understand recursion by instrumenting a function to print its arguments, along with an indicator of how deeply nested each call is."
]
},
{
"cell_type": "code",
"execution_count": 105,
"metadata": {},
"outputs": [],
"source": [
"def list_sum(L, indent=0):\n",
" \"\"\"Return the sum of the elements in a list.\"\"\"\n",
"\n",
" print(indent*'.', L)\n",
" if len(L) == 0:\n",
" return 0\n",
" else:\n",
" return L[0] + list_sum(L[1:], indent+1)"
]
},
{
"cell_type": "code",
"execution_count": 106,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" []\n",
" [1]\n",
". []\n",
" [1, 2, 3]\n",
". [2, 3]\n",
".. [3]\n",
"... []\n",
" [1, -1, 2, -2, 100, -100, 6]\n",
". [-1, 2, -2, 100, -100, 6]\n",
".. [2, -2, 100, -100, 6]\n",
"... [-2, 100, -100, 6]\n",
".... [100, -100, 6]\n",
"..... [-100, 6]\n",
"...... [6]\n",
"....... []\n"
]
}
],
"source": [
"assert list_sum([]) == 0\n",
"assert list_sum([1]) == 1\n",
"assert list_sum([1, 2, 3]) == 6\n",
"assert list_sum([1, -1, 2, -2, 100, -100, 6]) == 6"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's revisit summing up nested lists. Recursion gives us a nicer version where we don't need to maintain a stack ourselves."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"def sum_all(L):\n",
" \"\"\"Return the sum of all elements in a list with arbitrary nesting.\"\"\"\n",
"\n",
" if len(L) == 0:\n",
" return 0\n",
" elif isinstance(L[0], list):\n",
" # Oh, the first value is a list? \n",
" # We'll make *two* recursive calls for this case.\n",
" return sum_all(L[0]) + sum_all(L[1:])\n",
" else:\n",
" # The first value must be something we add directly into our total.\n",
" return L[0] + sum_all(L[1:])"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"assert sum_all([1, 2, 3]) == 6\n",
"assert sum_all([1, [2, 3], 4]) == 10\n",
"assert sum_all([1, 2, [3, [4, [[[5]]]], 6, [7, 8]], 9]) == 45"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Actually, behind the scenes, Python maintains a *call stack* for us, so we don't need to maintain it ourselves! It is common to diagram nested function calls as *frames* that pile up on top of each other, recording function names and arguments. We can also add printing instrumentation to make Python show us a sense of how nesting works."
]
},
{
"cell_type": "code",
"execution_count": 107,
"metadata": {},
"outputs": [],
"source": [
"def sum_all(L, indent=0):\n",
" \"\"\"Return the sum of all elements in a list with arbitrary nesting.\"\"\"\n",
"\n",
" print(indent*'.', L)\n",
" \n",
" if len(L) == 0:\n",
" return 0\n",
" elif isinstance(L[0], list):\n",
" # Oh, the first value is a list? \n",
" # We'll make *two* recursive calls for this case.\n",
" return sum_all(L[0], indent+1) + sum_all(L[1:], indent+1)\n",
" else:\n",
" # The first value must be something we add directly into our total.\n",
" return L[0] + sum_all(L[1:], indent+1)"
]
},
{
"cell_type": "code",
"execution_count": 108,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" [1, 2, 3]\n",
". [2, 3]\n",
".. [3]\n",
"... []\n",
" [1, [2, 3], 4]\n",
". [[2, 3], 4]\n",
".. [2, 3]\n",
"... [3]\n",
".... []\n",
".. [4]\n",
"... []\n",
" [1, 2, [3, [4, [[[5]]]], 6, [7, 8]], 9]\n",
". [2, [3, [4, [[[5]]]], 6, [7, 8]], 9]\n",
".. [[3, [4, [[[5]]]], 6, [7, 8]], 9]\n",
"... [3, [4, [[[5]]]], 6, [7, 8]]\n",
".... [[4, [[[5]]]], 6, [7, 8]]\n",
"..... [4, [[[5]]]]\n",
"...... [[[[5]]]]\n",
"....... [[[5]]]\n",
"........ [[5]]\n",
"......... [5]\n",
".......... []\n",
"......... []\n",
"........ []\n",
"....... []\n",
"..... [6, [7, 8]]\n",
"...... [[7, 8]]\n",
"....... [7, 8]\n",
"........ [8]\n",
"......... []\n",
"....... []\n",
"... [9]\n",
".... []\n"
]
}
],
"source": [
"assert sum_all([1, 2, 3]) == 6\n",
"assert sum_all([1, [2, 3], 4]) == 10\n",
"assert sum_all([1, 2, [3, [4, [[[5]]]], 6, [7, 8]], 9]) == 45"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, a word of caution: experts don't generally think in terms of call stacks and traces of function calls! Instead, they apply the three-step recipe and trust that the results will therefore be good. How did the recipe generate our last example?\n",
"\n",
" 1. *Divide the input space*: empty lists, lists with lists as first elements, lists with other first elements\n",
" 2. *Base cases*: empty lists\n",
" 3. *Recursive cases*: solved with calls whose argument lists are always shorter than the inputs\n",
"\n",
"Let's take a shot at weighted sums of nested lists. Here we follow the same input-space division, and we add an extra argument to track the weight of the top-level list. By passing different weights in different recursive calls, we get Python to keep track of a detail that we handled manually before with special `'decrease'` markers."
]
},
{
"cell_type": "code",
"execution_count": 111,
"metadata": {},
"outputs": [],
"source": [
"def weighted_sum_all(L, weight=1):\n",
" \"\"\"Return the sum of all elements in a list with arbitrary nesting, with a catch:\n",
" Every addition to the sum should be *multiplied* by the depth where it appears.\n",
" The first level of list has depth weight, then the next level has depth weigh+1, etc.\"\"\"\n",
"\n",
" if len(L) == 0:\n",
" return 0\n",
" elif isinstance(L[0], list):\n",
" return weighted_sum_all(L[0], weight+1) + weighted_sum_all(L[1:], weight)\n",
" else:\n",
" return weight * L[0] + weighted_sum_all(L[1:], weight)"
]
},
{
"cell_type": "code",
"execution_count": 113,
"metadata": {},
"outputs": [],
"source": [
"assert weighted_sum_all([1]) == 1\n",
"assert weighted_sum_all([[1]]) == 2\n",
"assert weighted_sum_all([[[1]]]) == 3\n",
"assert weighted_sum_all([[1], 3]) == 5"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Making Change\n",
"\n",
"Let's keep flexing our recursion muscles by applying the recipe in different variations. Consider the convenience-store cashier's standard problem of trying to give the customer some change, from a supply of coins that are on-hand. In more abstract terms, given a list of numbers (standing for the coins on-hand, therefore possibly including duplicates), we want to find a subset of them that add up to a particular target.\n",
"\n",
" 1. *Divide the input space*: zero amount, empty coin list, any other case\n",
" 2. *Base cases*: the first two\n",
" 3. *Recursive cases*: solved by calling self where the coins list has shrunk"
]
},
{
"cell_type": "code",
"execution_count": 114,
"metadata": {},
"outputs": [],
"source": [
"def make_change(amount, coins):\n",
" \"\"\"Let coins be a list of positive integer values for coins that are available for making change.\n",
" Try to find a way to use just those coins to add up to a total of amount.\n",
" If found, return a list of coins. Otherwise, return None.\n",
" \"\"\"\n",
" if amount == 0:\n",
" # No coins, but also no more change left to make! Easy solution.\n",
" return []\n",
" elif len(coins) == 0:\n",
" # We have some change left to make, but no coins are available.\n",
" return None\n",
" else:\n",
" # At least one coin remains. Consider the cases we use it or don't use it.\n",
" do_not_use_it = make_change(amount, coins[1:])\n",
" if do_not_use_it != None:\n",
" # Any solution that skips this coin is also legal if this coin was allowed, too.\n",
" return do_not_use_it\n",
" else:\n",
" use_it = make_change(amount - coins[0], coins[1:])\n",
" if use_it != None:\n",
" # Any solution that skips this coin can be converted back to a global solution by adding the coin.\n",
" # Note that we decreased amount in the recursive call, to ensure this trick works.\n",
" return [coins[0]] + use_it\n",
" else:\n",
" return None"
]
},
{
"cell_type": "code",
"execution_count": 115,
"metadata": {},
"outputs": [],
"source": [
"assert make_change(0, [1, 5, 10]) == []\n",
"assert make_change(15, [1, 5, 10]) == [5, 10]\n",
"assert make_change(11, [1, 5, 10]) == [1, 10]\n",
"assert make_change(13, [1, 5, 10]) == None"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's not get complacent. If we're not careful in applying the recursion recipe, we can easily get functions that run forever. Consider the case where we allow any coin in the list to be used any number of times."
]
},
{
"cell_type": "code",
"execution_count": 116,
"metadata": {},
"outputs": [],
"source": [
"def make_change(amount, coins):\n",
" \"\"\"Let coins be a list of positive integer values for coins that are available for making change.\n",
" Try to find a way to use just those coins to add up to a total of amount.\n",
" If found, return a list of coins. Otherwise, return None.\n",
" \"\"\"\n",
" if amount == 0:\n",
" # No coins, but also no more change left to make! Easy solution.\n",
" return []\n",
" # DANGER ZONE: What if we forgot the following base case?\n",
" elif amount < 0:\n",
" # No way to make negative change!\n",
" # If we skip this case, the recursion could continue forever (or at least for a very long time).\n",
" return None\n",
" elif len(coins) == 0:\n",
" # We have some change left to make, but no coins are available.\n",
" return None\n",
" else:\n",
" # At least one coin remains. Consider the cases we use it or don't use it.\n",
" do_not_use_it = make_change(amount, coins[1:])\n",
" if do_not_use_it != None:\n",
" # Any solution that skips this coin is also legal if this coin was allowed, too.\n",
" return do_not_use_it\n",
" else:\n",
" use_it = make_change(amount - coins[0], coins)\n",
" if use_it != None:\n",
" # Any solution that skips this coin can be converted back to a global solution by adding the coin.\n",
" # Note that we decreased amount in the recursive call, to ensure this trick works.\n",
" return [coins[0]] + use_it\n",
" else:\n",
" return None"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice that the second recursive call keeps the coin list the same. We run into trouble with the recipe.\n",
"\n",
" 1. *Divide the input space*: zero amount, empty coin list, any other case\n",
" 2. *Base cases*: the first two\n",
" 3. *Recursive cases*: solved by calling self where the coins list has shrunk... or, no, it hasn't (sometimes)!\n",
"\n",
"We could try to salvage the argument. For any given call, consider the sum of the amount and the number of coins. This sum does actually decrease on each recursive call! But the trouble is that *it can become negative*. When we convince ourselves of termination by assigning sizes to arguments and arguing that sizes decrease on recursion, sizes must always be enforced as *nonnegative*, so that zero serves as a safe boundary on recursion depth. (In theory, we would also be OK if we had, say, -365 as an enforced lower bound of the measure, but it's traditional to think in terms of zero.)\n",
"\n",
"Concretely, what went wrong when we applied the recipe incorrectly? We got stuck in an infinite digression of making the second recursive call, decreasing amount forever without changing coins. Note that adding the extra base case for negative amount fixes the problem, and hence the measure proposed in the last paragraph *does* work to argue for termination."
]
},
{
"cell_type": "code",
"execution_count": 118,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[]\n",
"[5, 10]\n",
"[1, 10]\n",
"[1, 1, 1, 10]\n",
"None\n"
]
}
],
"source": [
"print(make_change(0, [1, 5, 10]))\n",
"print(make_change(15, [1, 5, 10]))\n",
"print(make_change(11, [1, 5, 10]))\n",
"print(make_change(13, [1, 5, 10]))\n",
"print(make_change(7, [5, 10, 25]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Recursion and Trees\n",
"\n",
"Let's play with nested data structures a bit more.\n",
"\n",
"An abstract syntax tree (AST) is a convenient way of representing an arbitrary expression involving numbers and arithmetic operators. Here's a description of an AST in Backus-Naur Form (BNF), a famous way of describing computer languages:\n",
"\n",
"`ast :== integer | string | (operator, ast, ast)\n",
"operator :== \"+\" | \"*\" | …`\n",
"\n",
"Here are the rules for determining the value of an AST, given an *environment* (a dictionary mapping variable names to values):\n",
"\n",
"* If AST is an integer, return its value.\n",
"* If AST is a string, treat as a variable name, returning the variable's value.\n",
"* Otherwise, return the result of performing the operation on the values of the operands."
]
},
{
"cell_type": "code",
"execution_count": 121,
"metadata": {},
"outputs": [],
"source": [
"def eval_operator(operator, operand1, operand2):\n",
" \"\"\"Return the result of applying the given operator to the two numeric operands.\"\"\"\n",
"\n",
" if operator == '+':\n",
" return operand1 + operand2\n",
" elif operator == '-':\n",
" return operand1 - operand2\n",
" elif operator == '*':\n",
" return operand1 * operand2\n",
" elif operator == '/':\n",
" return operand1 / operand2\n",
" else:\n",
" raise ValueError\n",
"\n",
"def eval_ast(ast, env={}):\n",
" \"\"\"Return the value of the AST in the given environment (dictionary mapping variables to values).\"\"\"\n",
"\n",
" if isinstance(ast, tuple):\n",
" # If it's a tuple, it represents an operator. Unpack the pieces and recurse.\n",
" operator, operand1, operand2 = ast\n",
" return eval_operator(operator, eval_ast(operand1, env), eval_ast(operand2, env))\n",
" elif isinstance(ast, str):\n",
" # It's a variable, represented as the variable name (string)? Just look up in the environment.\n",
" return env[ast]\n",
" else:\n",
" # It's a constant? Then the AST is its own value.\n",
" return ast"
]
},
{
"cell_type": "code",
"execution_count": 122,
"metadata": {},
"outputs": [],
"source": [
"assert eval_ast(3) == 3\n",
"assert eval_ast('pi', {'pi': 3.141592653}) == 3.141592653\n",
"assert eval_ast(('*', 2, 21)) == 42\n",
" \n",
"# evaluate 3 + 47*5/pi - 13\n",
"ast = ('-',\n",
" ('+',\n",
" ('/',\n",
" ('*', 47, 5),\n",
" 'pi'),\n",
" 3),\n",
" 13)\n",
"assert eval_ast(ast, {'pi': 3.141592653}) == 64.80282326723406"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice that our same old recipe can guide the design of this method.\n",
"\n",
" 1. *Divide the input space*: operators, variables, constants\n",
" 2. *Base cases*: variables and constants\n",
" 3. *Recursive cases*: operators, making recursive calls only on smaller trees"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Merge Sort\n",
"\n",
"Let's end with an example of a classic sorting algorithm. First, assume we have two sorted lists. How we can efficiently combine them together into one sorted list, where the occurrence count of each element equals the counts from the two input lists? We can walk the two lists together from left to right, always picking the lowest unpicked element to add to an output list."
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {},
"outputs": [],
"source": [
"def merge(left, right):\n",
" \"\"\"Assumes left and right are sorted lists. Returns a single new \n",
" list built, in order, from the elements of left and right.\"\"\"\n",
"\n",
" result = []\n",
" i, j = 0, 0\n",
" # Loop while there are elements in both lists. \n",
" while i < len(left) and j < len(right):\n",
" # Copy smallest element to result.\n",
" if left[i] < right[j]:\n",
" result.append(left[i])\n",
" i += 1\n",
" else:\n",
" result.append(right[j])\n",
" j += 1\n",
"\n",
" # Copy over any remaining elements.\n",
" # Only one of the lists has any elements remaining!\n",
" result.extend(left[i:]) # Recall that extend adds all elements of the argument list.\n",
" result.extend(right[j:])\n",
" return result"
]
},
{
"cell_type": "code",
"execution_count": 123,
"metadata": {},
"outputs": [],
"source": [
"assert(merge([1, 2], [3, 4])) == [1, 2, 3, 4]\n",
"assert(merge([1, 3], [2, 4])) == [1, 2, 3, 4]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now the recipe helps us make a simple plan.\n",
"\n",
" 1. *Divide the input space*: lists of length below two and at least two\n",
" 2. *Base cases*: length below two (the list is already sorted)\n",
" 3. *Recursive cases*: divide the input list roughly in half, so that each \"half\" is shorter than the original and thus legal to sort recursively\n",
"\n",
"Merging is the perfect operation to combine results of recursive calls."
]
},
{
"cell_type": "code",
"execution_count": 125,
"metadata": {},
"outputs": [],
"source": [
"def sort(L):\n",
" \"\"\"Returns a new sorted list containing the same elements as L.\"\"\"\n",
"\n",
" if len(L) < 2:\n",
" return L[:] # Makes a fresh copy of L, just in case the caller mutates L later.\n",
" else:\n",
" # Split L into two lists of approximately equal lengths. (They might be off by one.)\n",
" middle = len(L)//2\n",
" left = L[:middle]\n",
" right = L[middle:]\n",
" print('SPLIT into', left, 'and', right)\n",
" \n",
" # Sort the two sides.\n",
" left = sort(left)\n",
" right = sort(right)\n",
" print('SORTED them to', left, 'and', right)\n",
" \n",
" # Merge the results.\n",
" merged = merge(left, right)\n",
" print('MERGED into', merged)\n",
" return merged"
]
},
{
"cell_type": "code",
"execution_count": 126,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"SPLIT into [23, 3, 45, 7] and [6, 11, 14, 12]\n",
"SPLIT into [23, 3] and [45, 7]\n",
"SPLIT into [23] and [3]\n",
"SORTED them to [23] and [3]\n",
"MERGED into [3, 23]\n",
"SPLIT into [45] and [7]\n",
"SORTED them to [45] and [7]\n",
"MERGED into [7, 45]\n",
"SORTED them to [3, 23] and [7, 45]\n",
"MERGED into [3, 7, 23, 45]\n",
"SPLIT into [6, 11] and [14, 12]\n",
"SPLIT into [6] and [11]\n",
"SORTED them to [6] and [11]\n",
"MERGED into [6, 11]\n",
"SPLIT into [14] and [12]\n",
"SORTED them to [14] and [12]\n",
"MERGED into [12, 14]\n",
"SORTED them to [6, 11] and [12, 14]\n",
"MERGED into [6, 11, 12, 14]\n",
"SORTED them to [3, 7, 23, 45] and [6, 11, 12, 14]\n",
"MERGED into [3, 6, 7, 11, 12, 14, 23, 45]\n"
]
}
],
"source": [
"assert sort([23, 3, 45, 7, 6, 11, 14, 12]) == [3, 6, 7, 11, 12, 14, 23, 45]"
]
}
],
"metadata": {
"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.6.7"
}
},
"nbformat": 4,
"nbformat_minor": 2
}