about summary refs log blame commit diff
path: root/users/wpcarro/scratch/data_structures_and_algorithms/second-largest-item-bst.py
blob: bc167d975a7b3ed40e3a944dce2ca90fb6836e41 (plain) (tree)


















































































































































































                                                                                
import unittest
from collections import deque


################################################################################
# Implementation
################################################################################
def is_leaf(node):
    return node.left is None and node.right is None


def find_largest(node):
    current = node
    while current.right is not None:
        current = current.right
    return current.value


def find_second_largest(node):
    history = deque()
    current = node

    while current.right:
        history.append(current)
        current = current.right

    if current.left:
        return find_largest(current.left)
    elif history:
        return history.pop().value
    else:
        raise TypeError


def find_second_largest_backup(node):
    history = deque()
    current = node

    # traverse -> largest
    while current.right:
        history.append(current)
        current = current.right

    if current.left:
        return find_largest(current.left)
    elif history:
        return history.pop().value
    else:
        raise ArgumentError


# Write a iterative version to avoid consuming memory with the call stack.
# Commenting out the recursive code for now.
def find_second_largest_backup(node):
    if node.left is None and node.right is None:
        raise ArgumentError

    elif node.right is None and is_leaf(node.left):
        return node.left.value

    # recursion
    # elif node.right is None:
    #     return find_largest(node.left)

    # iterative version
    elif node.right is None:
        current = node.left
        while current.right is not None:
            current = current.right
        return current.value

    # recursion
    # TODO: Remove recursion from here.
    elif not is_leaf(node.right):
        return find_second_largest(node.right)

    # could do an else here, but let's be more assertive.
    elif is_leaf(node.right):
        return node.value

    else:
        raise ArgumentError


################################################################################
# Tests
################################################################################
class Test(unittest.TestCase):
    class BinaryTreeNode(object):
        def __init__(self, value):
            self.value = value
            self.left = None
            self.right = None

        def insert_left(self, value):
            self.left = Test.BinaryTreeNode(value)
            return self.left

        def insert_right(self, value):
            self.right = Test.BinaryTreeNode(value)
            return self.right

    def test_full_tree(self):
        tree = Test.BinaryTreeNode(50)
        left = tree.insert_left(30)
        right = tree.insert_right(70)
        left.insert_left(10)
        left.insert_right(40)
        right.insert_left(60)
        right.insert_right(80)
        actual = find_second_largest(tree)
        expected = 70
        self.assertEqual(actual, expected)

    def test_largest_has_a_left_child(self):
        tree = Test.BinaryTreeNode(50)
        left = tree.insert_left(30)
        right = tree.insert_right(70)
        left.insert_left(10)
        left.insert_right(40)
        right.insert_left(60)
        actual = find_second_largest(tree)
        expected = 60
        self.assertEqual(actual, expected)

    def test_largest_has_a_left_subtree(self):
        tree = Test.BinaryTreeNode(50)
        left = tree.insert_left(30)
        right = tree.insert_right(70)
        left.insert_left(10)
        left.insert_right(40)
        right_left = right.insert_left(60)
        right_left_left = right_left.insert_left(55)
        right_left.insert_right(65)
        right_left_left.insert_right(58)
        actual = find_second_largest(tree)
        expected = 65
        self.assertEqual(actual, expected)

    def test_second_largest_is_root_node(self):
        tree = Test.BinaryTreeNode(50)
        left = tree.insert_left(30)
        tree.insert_right(70)
        left.insert_left(10)
        left.insert_right(40)
        actual = find_second_largest(tree)
        expected = 50
        self.assertEqual(actual, expected)

    def test_descending_linked_list(self):
        tree = Test.BinaryTreeNode(50)
        left = tree.insert_left(40)
        left_left = left.insert_left(30)
        left_left_left = left_left.insert_left(20)
        left_left_left.insert_left(10)
        actual = find_second_largest(tree)
        expected = 40
        self.assertEqual(actual, expected)

    def test_ascending_linked_list(self):
        tree = Test.BinaryTreeNode(50)
        right = tree.insert_right(60)
        right_right = right.insert_right(70)
        right_right.insert_right(80)
        actual = find_second_largest(tree)
        expected = 70
        self.assertEqual(actual, expected)

    def test_error_when_tree_has_one_node(self):
        tree = Test.BinaryTreeNode(50)
        with self.assertRaises(Exception):
            find_second_largest(tree)

    def test_error_when_tree_is_empty(self):
        with self.assertRaises(Exception):
            find_second_largest(None)


unittest.main(verbosity=2)