Mental Checklist

 
# RECURSION - MENTAL CHECKLIST
# ----------------------------
# Recursion is hard for almost everyone at first.
# It requires a different way of thinking than loops. 
# When you see a recursion problem, always answer these questions:
#
# MEMORIZE THIS:
# --------------------------------------
# 1) What is the smallest posible input?
#       -> Base case
#
# 2) What should happen at that smallest input?
#       -> Return value
# 
# 3) How does the problem get smaller?
#       -> Recursive step
# 
# 4) What stays the same?
#       -> Structure
# 
# 5) What does the function return?
#       -> Never forget this
#
# KEY IDEA:
# ----------------------------------------
# Recursion is just stacked function calls.
# Nothing magical.
#
# countdown(3)
#   print(3)
#   countdown(2)
#     print(2)
#     countdown(2)
#       print(1)
#       countdown(1)
#         return
#
# TRACE ON PAPER
# -------------------------------
# Always trace recursion on paper.
#
# countdown(3)
# countdown(2)
# countdown(1)
# countdown(0)
# --------------------------------

def countdown(n):
    if n == 0:  # Base case
        return
    
    print(n)
    countdown(n-1)  # Smaller problem

countdown(3)

# ----------
# 3
# 2
# 1

Return Value

 
# RECURSION that RETURNS a value
# ------------------------------
# Problem: 
#       Sum all numbers from 1 to n
# Result: 
#       sum_to(4) = 1 + 2 + 3 + 4 = 10
#
# THE 5-STEP MENTAL CHECKLIST (APPLIED)
# -------------------------------------
# 1) What is the smallest posible input?
#       - n = 1
#
# 2) What should happen at that smallest input?
#       - return 1
#       - the sum of numbers up to 1 is 1
# 
# 3) How does the problem get smaller?
#       - Insteed of summing up to n, sum up to (n - 1)
# 
# 4) What stays the same?
#       - The idea: "sum numbers from 1 to n"
#       - The function logic does NOT change
# 
# 5) What does the function return?
#       - A number (the sum)
#       - Every recursive call MUST return a number
#
# TRACE THE EXECUTION (DO NOT SKIP)
# ---------------------------------
# Write this on paper:
#
# sum_to(4)
#    = 4 + sum(3)
#    = 4 + (3 + sum(2))
#    = 4 + (3 + (2 + sum(1)))
#    = 4 + (3 + (2 + 1))
#    = 10
# --------------------------------

def sum_to(n):
    if n == 1:                  # Base case
        return 1
    
    print(n)                    # 4, 3, 2
    return n + sum_to(n-1)      # Smaller problem

result = sum_to(4)
print(result)

# ---------------
# 4
# 3
# 2
# 10

BST Insert

 
# BINARY SEARCH TREE (BST) - RECURSIVE INSERT
# -------------------------------------------
# BST RULE (structure that never change):
#   - Left child < current node value
#   - Right child > current node value
#
# Question 1: Smallest input?
#   -> Node is none
# 
# Question 2: What happens then?
#   -> Create end RETURN a new node
# 
# Question 3: How does it get smaller?
#   -> We move done ONE level (left or right)
#
# Question 4: What stays the same?
#   -> The BST ordering rule: left < node < right
#
# Question 5: What does the function return?
#   -> A Tree node (the root of this subtree)
#   -> Every recursive call MUST return a value
# ---------------------------------------------

class Tree:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def insert(node, value, depth=0):

    # STEP 1: SMALLEST possible input -> node is None
    # STEP 2: Create and RETURN a new Tree node
    # --------------
    if node is None:
        return Tree(value)  # Base case
    
    # STEP 3: RECURSION
    # Decide whether to go LEFT or RIGHT
    # The problem gets smaller because we move to a subtree
    # --------------------
    if value < node.value:
        node.left = insert(node.left, value, depth + 1)   # Insert into the left subtree
    
    if value > node.value:
        node.right = insert(node.right, value, depth + 1)  # Insert into the right subtree

    # STEP 4: STRUCTURE STAYS THE SAME
    # STEP 5: RETURN VALUE
    # ---------
    return node

tree = insert(None, 50)
tree = insert(tree, 25)
tree = insert(tree, 33)
tree = insert(tree, 31)

def print_tree(node, depth=0):
    if node is None:
        return
    
    print_tree(node.right, depth + 1)
    print("  " * depth + str(node.value))
    print_tree(node.left, depth + 1)

print_tree(tree)

# The tree is printed sideways
# ----------------------------
# 50
#     33
#       31
#   25

BST Populate

 
# BINERY SEARCH TREE - POPULATE
# ------------------------------
# Problem:
#   Insert data into BST (each node in the correct position)
#
# Example:
#   Input: [4, 2, 3, 1, 7, 6]  
#   Expected Output: [4 2 1 3 7 6]
#
# Goal:
#   Insert values into a BST so that:
#   - left subtree  < node
#   - right subtree > node
#
# Key idea:
#   - We WALK DOWN the tree using recursion
#   - We INSERT only when we find a None child
#   - We RETURN existing nodes to preserve the tree
# -------------------------------------------------

items = [4, 2, 3, 1, 7, 6]  

class Node:
    def __init__(self, val):
        self.info = val
        self.left = None
        self.right = None

class BinarySearchTree:
    def __init__(self):
        self.root = None

    def insert(self, val):

        # BOOTSTRAP CASE:
        # -------------------------------------------------------
        # If the tree is empty, the first value becomes the root.
        # No recursion needed here.
        # -------------------------------------------------------
        if self.root is None:
            self.root = Node(val)
        else:      
            # Otherwise, start walking the tree from the root                         
            self.populate(self.root, val)
    
    def populate(self, node, val):

        # MOVE DOWN: LEFT SUBTREE
        # -----------------------
        if val < node.info:

            # BASE CASE (ACTUAL INSERTION POINT)
            # -------------------------------------
            # We found an empty spot -> insert here
            # -------------------------------------
            if node.left is None:           
                node.left = Node(val)

            # RECURSIVE STEP: 
            # -------------------------------------
            # Left child exists -> keep walking down
            # ---------------------------------------
            else:
                node.left = self.populate(node.left, val)

        # MOVE DOWN: RIGHT SUBTREE
        # ------------------------
        if val > node.info:
            if node.right is None:          
                node.right = Node(val)      
            else:
                node.right = self.populate(node.right, val)

        return node
        

# BUILD THE TREE
# --------------
tree = BinarySearchTree()

for x in items:
    tree.insert(x)

# PREORDER TRAVERSAL OUTPUT
# -------------------------
def printTree(root):
    if root is None:
        return
    print(root.info, end=" ")
    printTree(root.left) 
    printTree(root.right) 

printTree(tree.root)  
# ------------------
# 4 2 1 3 7 6


# -------------------------------
# TESTING
# --------------------------------
def accumulate(root, output=None):  # No mutable default argument (recommended)
    if output is None:
        output = []

    if root is None:
        return
    
    output.append(root.info)        # Insert value
    accumulate(root.left, output)   # Recursion
    accumulate(root.right, output)  # Recursion

    return output

def populate(input):
    tree = BinarySearchTree()
    for x in input:
        tree.insert(x)
    return tree

# TEST / BALANCED INPUT
input = [4, 2, 3, 1, 7, 6]
tree = populate(input)
output = accumulate(tree.root)
assert  output == [4, 2, 1, 3, 7, 6]

# TEST / NEGATIVE VALUES
input = [0, -10, 10, -20, -5, 5, 20]
tree = populate(input)
output = accumulate(tree.root)
assert output == [0, -10, -20, -5, 10, 5, 20]

# TEST / DUPLICATES
input = [4, 2, 4, 2, 3]
tree = populate(input)
output = accumulate(tree.root)
assert output == [4, 2, 3]

# TEST / RANDOM INSERTION ORDER
input = [8, 3, 10, 1, 6, 14, 4, 7, 13]
tree = populate(input)
output = accumulate(tree.root)
assert output == [8, 3, 1, 6, 4, 7, 10, 14, 13]

input = [9, 7, 8, 5, 6, 4, 3, 1]
tree = populate(input)
output = accumulate(tree.root)
assert output == [9, 7, 5, 4, 3, 1, 6, 8]
                   
print("Tests passed")




References: