Mastering Expression Evaluation and Tree Walking with Polymorphism

In the realm of programming, understanding expression evaluation and the ability to manipulate binary trees are vital skills that can elevate your development capabilities. One fascinating method to implement these concepts is through the use of polymorphism, especially within object-oriented programming (OOP).

This blog post will explore the classic interview question inspired by Steve Yegge’s observations, where candidates are challenged to convert an arithmetic expression (e.g., the string “2 + (2)”) into an expression tree. We will walk through the problem step by step, explaining how to process these trees using polymorphism, and providing insights into the code.

The Problem: From Expression to Tree

Understanding the Basics

At its core, the task is to represent and evaluate arithmetic expressions as binary trees:

  • Leaf Nodes: These are the numbers.
  • Internal Nodes: These are the operators (e.g., +, -, *, /).

Evaluating such expressions involves “walking” through the tree structure. If you’re faced with this problem, here’s how you can get started:

  1. Convert the Expression: Determining how to transform a string expression into a tree structure.
  2. Evaluate the Expression: Walking through the tree to compute the final value.

Why Use Polymorphism?

Many programming candidates often struggle with the best way to execute these tasks. The simplest methods, such as using a switch statement or cascaded if-else structures, can become unwieldy and hard to manage.

Polymorphism allows you to define a common interface for a group of related classes, leading to a more flexible and manageable design. When faced with different operations, polymorphism enables you to invoke the correct method without needing to know the details of the implementation of the operation.

The Solution: Implementing Polymorphic Trees in Python

Let’s delve into the code to better understand how polymorphism operates within this context.

The Code Explanation

#!/usr/bin/python

class Node:
    """Base class, you should not process one of these."""
    def process(self):
        raise('you should not be processing a node')  # Abstract

class BinaryNode(Node):
    """Base class for binary nodes."""
    def __init__(self, _left, _right):
        self.left = _left
        self.right = _right
    def process(self):
        raise('you should not be processing a binarynode')  # Abstract

class Plus(BinaryNode):
    def process(self):
        return self.left.process() + self.right.process()

class Minus(BinaryNode):
    def process(self):
        return self.left.process() - self.right.process()

class Mul(BinaryNode):
    def process(self):
        return self.left.process() * self.right.process()

class Div(BinaryNode):
    def process(self):
        return self.left.process() / self.right.process()

class Num(Node):
    def __init__(self, _value):
        self.value = _value
    def process(self):
        return self.value

# Test cases for demonstration
def demo(n):
    print(n.process())

demo(Num(2))                                    # Output: 2
demo(Plus(Num(2), Num(5)))                      # Output: 7 (2 + 5)
demo(Plus(Mul(Num(2), Num(3)), Div(Num(10), Num(5))))  # Output: 8 ((2 * 3) + (10 / 5))

Breakdown of the Code

  • Abstract Base Class: Node

    • This serves as a base class for all nodes.
    • The process() method is designed to be overridden but not directly executed on Node.
  • Abstract Base Class: BinaryNode

    • Inherits from Node and represents binary operators.
    • Contains child nodes for the left and right sub-expressions.
  • Concrete Classes for Operations

    • Plus, Minus, Mul, and Div classes inherit from BinaryNode and implement the process() method to evaluate the expressions.
  • Leaf Node Class: Num

    • Represents numeric values and simply returns the stored value when process() is invoked.

Final Thoughts

Polymorphism offers a powerful way to implement expression evaluation in programming. By adhering to this structure, developers can create clear, organized, and flexible code that allows for easy additions and modifications.

In conclusion, mastering the transformation from an arithmetic expression string to an expression tree using polymorphism opens up a plethora of possibilities. This design pattern not only simplifies coding processes but also enhances the maintainability of your code.

Now that you’ve grasped the concept, try implementing your own variations and explore the vast world of programming!