Madhavan Mukund



Programming and Data Structures with Python
Aug–Nov 2024

Assignment 2

2 October, 2024, due 13 October 2024


Arithmetic expressions

We work with arithmetic expressions over integers involving the operations addition (+), subtraction (-) and multiplication (*).

Arithmetic expressions are inherently ambiguous. For instance 1+2*3 could mean (1+2)*3 = 9 or 1+(2*3) = 7. Parentheses are one way to disambiguate expressions. Typically, we define a precedence among operators (the BODMAS rule from school arithmetic) to give a unique interpretation in the absence of parentheses.

Another way to write expressions unambiguously is to use expression trees. Here are the trees corresponding to the two interpretations of 1+2*3. Each integer is a leaf node and each operation is the root of a binary tree with its two arguments as children.

         *           +
        / \         / \
       +   3       1   *
      / \             / \
     1   2           2   3

     (1+2)*3       1+(2*3)
    

We can represent such a tree using a dictionary. A leaf node has a single key 'value', mapped to an integer. A non-leaf node has three keys: 'op' has a value from the set {'+','-','*'}, while 'arg1' and 'arg2' contain nested dictionaries corresponding to the expressions in the left and right subtrees.

Here are some expressions with their corresponding dictionary representations.

Expression Dictionary representation
99 {'value':99}
(7+8) {'op': '+', 'arg1': {'value':7}, 'arg2': {'value':8}}
(7+8)-(8*9)
{'op':'-',
  'arg1': {'op': '+', 'arg1': {'value':7}, 'arg2': {'value':8}},
  'arg2': {'op': '*', 'arg1': {'value':8}, 'arg2': {'value':9}}
}
(7+8)*9
{'op':'*',
  'arg1': {'op': '+', 'arg1': {'value':7}, 'arg2': {'value':8}}
  'arg2': {'value':9}}
}
7*(8+9)
{'op': '*',
  'arg1': {'value':7}
  'arg2': {'op': '+', 'arg1': {'value':8}, 'arg2': {'value':9}}
}

Problems

Write the following functions in Python. (Hint: Exploit the inductive structure of expressions to write recursive functions.)

  1. validate(expr) returns True if its input is a valid dictionary representation of an expression and returns False otherwise.

  2. evaluate(expr) takes as input a dictionary representation of an expression. The function returns the value of the expresssion as an int if the input is a valid expression and returns None otherwise.

  3. display(expr) takes as input a dictionary representation of an expression. The function returns a string representation of the expression if the input is a valid expression, and returns None otherwise.

    If the expression is an integer, the function should return the integer as a string. For non-trivial expressions, the function should return a string with enclosing parentheses — so 'e1 op e2' will be returned as '(e1 op e2)'.

    Examples:
    • display({'value':99}) returns '99'

    • display({'op':'+', 'arg1':{'value':7}, 'arg2':{'value':8}}) returns '(7+8)'

    • display({'op':'-', 'arg1': {'op':'+', 'arg1':{'value':7}, 'arg2':{'value':8}}, 'arg2': {'op':'*', 'arg1':{'value':8}, 'arg2':{'value':9}}}) returns '((7+8)-(8*9))'


How to submit

  • Submit your solution through Moodle as a single Python notebook with solutions to all the questions.

  • Add documentation to explain at a high level what your code is doing.

  • Show some sample executions.