diff --git a/notes-and-examples/2026.05.13/homework.py b/notes-and-examples/2026.05.13/homework.py index e69de29..19d76c2 100644 --- a/notes-and-examples/2026.05.13/homework.py +++ b/notes-and-examples/2026.05.13/homework.py @@ -0,0 +1,49 @@ +# Also do the 2026.05.06 homework +# +# Implement insertion, removal and search in a binary search tree. +# Properties of binary search trees: +# - Each node has at most two children +# - The left subtree of a node contains only nodes with values less than the node's value +# - The right subtree of a node contains only nodes with values greater than the node's value +# +# Example of what such a tree would look like: +# 10 +# / \ +# 5 15 +# / \ \ +# 3 7 20 +# +# Example of an *invalid* tree: +# 10 +# / \ +# 5 15 +# / \ \ +# 3 7 20 +# \ +# 12 +# +# Don't worry about balancing the tree for now, we will cover that later + + +class Node: + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + +class BinarySearchTree: + def __init__(self): + self.root = None + + def insert(self, value): + # TODO + pass + + def remove(self, value): + # TODO + pass + + def search(self, value): + # TODO + pass diff --git a/notes-and-examples/2026.05.13/trees.py b/notes-and-examples/2026.05.13/trees.py new file mode 100644 index 0000000..ffadd5d --- /dev/null +++ b/notes-and-examples/2026.05.13/trees.py @@ -0,0 +1,121 @@ +# Example: expression tree + evaluation + +from enum import Enum + + +class TokenType(Enum): + NUMBER = 1 + OPERATOR = 2 + + +# either store an operator (+, -, *, /) or a number +class Token: + def __init__(self, type: TokenType, value: str): + self.type = type + self.value = value + + +# each individual element in the tree +class Node: + left = None + right = None + + def __init__(self, token: Token, left=None, right=None): + self.token = token + self.left = left + self.right = right + + +# holder for the tree +class ExpressionTree: + def __init__(self, root: Node | None): + self.root = root + + +def evaluate(tree: ExpressionTree) -> int: + return _evaluate(tree.root) + + +# evaluate a tree recursively starting from the node +def _evaluate(node: Node | None) -> int: + if node is None: + return 0 + + if node.token.type == TokenType.NUMBER: + return int(node.token.value) + + elif node.token.type == TokenType.OPERATOR: + if node.left is None or node.right is None: + raise ValueError(f"Operator {node.token.value} requires two operands") + + left = _evaluate(node.left) + right = _evaluate(node.right) + + if node.token.value == "+": + return left + right + elif node.token.value == "-": + return left - right + elif node.token.value == "*": + return left * right + elif node.token.value == "/": + return left // right + + raise ValueError(f"Unknown operator: {node.token.value}") + + +# test cases +test_cases = [ + (ExpressionTree(Node(Token(TokenType.NUMBER, "5"))), 5), + ( + ExpressionTree( + Node( + Token(TokenType.OPERATOR, "+"), + Node(Token(TokenType.NUMBER, "3")), + Node(Token(TokenType.NUMBER, "2")), + ) + ), + 5, + ), + ( + ExpressionTree( + Node( + Token(TokenType.OPERATOR, "+"), + Node(Token(TokenType.NUMBER, "3")), + Node( + Token(TokenType.OPERATOR, "*"), + Node(Token(TokenType.NUMBER, "2")), + Node(Token(TokenType.NUMBER, "3")), + ), + ) + ), + 9, + ), + ( + ExpressionTree( + Node( + Token(TokenType.OPERATOR, "+"), + Node( + Token(TokenType.OPERATOR, "-"), + Node(Token(TokenType.NUMBER, "3")), + Node(Token(TokenType.NUMBER, "2")), + ), + Node( + Token(TokenType.OPERATOR, "*"), + Node(Token(TokenType.NUMBER, "2")), + Node(Token(TokenType.NUMBER, "3")), + ), + ) + ), + 7, + ), +] + + +def test_evaluate() -> None: + for tree, expected in test_cases: + evaluation = evaluate(tree) == expected + print(f"Test case: evaluate({tree}) == {expected} -> {evaluation}") + + +if __name__ == "__main__": + test_evaluate()