348 lines
11 KiB
Python
Executable File
348 lines
11 KiB
Python
Executable File
"""CSC111 Winter 2022 Prep 6: Programming Exercises
|
|
|
|
Instructions (READ THIS FIRST!)
|
|
===============================
|
|
|
|
This module contains the code for a set of classes used to represent expressions
|
|
that you would see in a Python program.
|
|
|
|
It includes the three classes Expr, Num, and BinOp covered in the prep readings.
|
|
Note that in addition to the initializer and evaluate methods, we've also
|
|
included a __str__ implementation for each class that shows the corresponding
|
|
Python expression that the tree represents.
|
|
|
|
Your task is to complete the implementations of three new classes:
|
|
|
|
1. Bool: a constant boolean (similar to Num).
|
|
2. BoolOp: a sequence of `and` or `or` expressions (similar to BinOp).
|
|
3. Compare: a sequence of `<` and `<=` expressions (for simplicity, we'll
|
|
ignore other forms of expressions like `>` and `==`).
|
|
|
|
Note that BoolOp and Compare are a bit more challenging than BinOp, because
|
|
both of them can have an *arbitrary number* of subtrees, rather than being
|
|
limited to exactly two subtrees. However, you can use the same recursive
|
|
"evaluate each subexpression recursively" idea as BinOp.
|
|
|
|
We have marked each place you need to write code with the word "TODO".
|
|
As you complete your work in this file, delete each TODO comment.
|
|
|
|
You may add additional doctests, but they will not be graded. You should test your work
|
|
carefully before submitting it!
|
|
|
|
Copyright and Usage Information
|
|
===============================
|
|
|
|
This file is provided solely for the personal and private use of students
|
|
taking CSC111 at the University of Toronto St. George campus. All forms of
|
|
distribution of this code, whether as given or with any changes, are
|
|
expressly prohibited. For more information on copyright for CSC111 materials,
|
|
please consult our Course Syllabus.
|
|
|
|
This file is Copyright (c) 2022 Mario Badr, David Liu, and Diane Horton.
|
|
"""
|
|
from __future__ import annotations
|
|
from typing import Any, Union
|
|
|
|
|
|
class Expr:
|
|
"""An abstract class representing a Python expression.
|
|
"""
|
|
|
|
def evaluate(self) -> Any:
|
|
"""Return the *value* of this expression.
|
|
|
|
The returned value should the result of how this expression would be
|
|
evaluated by the Python interpreter.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
class Num(Expr):
|
|
"""A numeric literal.
|
|
|
|
Instance Attributes:
|
|
- n: the value of the literal
|
|
"""
|
|
n: Union[int, float]
|
|
|
|
def __init__(self, number: Union[int, float]) -> None:
|
|
"""Initialize a new numeric literal."""
|
|
self.n = number
|
|
|
|
def evaluate(self) -> Any:
|
|
"""Return the *value* of this expression.
|
|
|
|
The returned value should the result of how this expression would be
|
|
evaluated by the Python interpreter.
|
|
|
|
>>> expr = Num(10.5)
|
|
>>> expr.evaluate()
|
|
10.5
|
|
"""
|
|
return self.n # Simply return the value itself!
|
|
|
|
def __str__(self) -> str:
|
|
"""Return a string representation of this expression.
|
|
|
|
One feature we'll stick with for all Expr subclasses here is that we'll
|
|
want to return a string that is valid Python code representing the same
|
|
expression.
|
|
|
|
>>> str(Num(5))
|
|
'5'
|
|
"""
|
|
return str(self.n)
|
|
|
|
|
|
class BinOp(Expr):
|
|
"""An arithmetic binary operation.
|
|
|
|
Instance Attributes:
|
|
- left: the left operand
|
|
- op: the name of the operator
|
|
- right: the right operand
|
|
|
|
Representation Invariants:
|
|
- self.op in {'+', '*'}
|
|
"""
|
|
left: Expr
|
|
op: str
|
|
right: Expr
|
|
|
|
def __init__(self, left: Expr, op: str, right: Expr) -> None:
|
|
"""Initialize a new binary operation expression.
|
|
|
|
Preconditions:
|
|
- op in {'+', '*'}
|
|
"""
|
|
self.left = left
|
|
self.op = op
|
|
self.right = right
|
|
|
|
def evaluate(self) -> Any:
|
|
"""Return the *value* of this expression.
|
|
|
|
The returned value should the result of how this expression would be
|
|
evaluated by the Python interpreter.
|
|
|
|
>>> expr = BinOp(Num(10.5), '+', Num(30))
|
|
>>> expr.evaluate()
|
|
40.5
|
|
"""
|
|
left_val = self.left.evaluate()
|
|
right_val = self.right.evaluate()
|
|
|
|
if self.op == '+':
|
|
return left_val + right_val
|
|
elif self.op == '*':
|
|
return left_val * right_val
|
|
else:
|
|
# We shouldn't reach this branch because of our representation invariant
|
|
raise ValueError(f'Invalid operator {self.op}')
|
|
|
|
def __str__(self) -> str:
|
|
"""Return a string representation of this expression.
|
|
"""
|
|
return f'({str(self.left)} {self.op} {str(self.right)})'
|
|
|
|
|
|
################################################################################
|
|
# Prep exercises
|
|
################################################################################
|
|
class Bool(Expr):
|
|
"""A boolean literal.
|
|
|
|
Instance Attributes:
|
|
- b: the value of the literal
|
|
"""
|
|
b: bool
|
|
|
|
def __init__(self, b: bool) -> None:
|
|
"""Initialize a new boolean literal."""
|
|
self.b = b
|
|
|
|
def evaluate(self) -> Any:
|
|
"""Return the *value* of this expression.
|
|
|
|
The returned value should the result of how this expression would be
|
|
evaluated by the Python interpreter.
|
|
|
|
>>> expr = Bool(True)
|
|
>>> expr.evaluate()
|
|
True
|
|
"""
|
|
return self.b
|
|
|
|
def __str__(self) -> str:
|
|
"""Return a string representation of this expression.
|
|
"""
|
|
return str(self.b)
|
|
|
|
|
|
class BoolOp(Expr):
|
|
"""A boolean operation.
|
|
|
|
Represents either a sequence of `and`s or a sequence of `or`s.
|
|
Unlike BinOp, this expression can contains more than two operands,
|
|
each separated by SAME operator:
|
|
|
|
True and False and True and False
|
|
True or False or True or False
|
|
|
|
Instance Attributes:
|
|
- op: the name of the boolean operation
|
|
- operands: a list of operands that the operation is applied to
|
|
|
|
Representation Invariants:
|
|
- self.op in {'and', 'or'}
|
|
- len(self.operands) >= 2
|
|
- every expression in self.operands evaluates to a boolean value
|
|
"""
|
|
op: str
|
|
operands: list[Expr]
|
|
|
|
def __init__(self, op: str, operands: list[Expr]) -> None:
|
|
"""Initialize a new boolean operation expression.
|
|
|
|
Preconditions:
|
|
- op in {'and', 'or'}
|
|
- len(operands) >= 2
|
|
- every expression in operands evaluates to a boolean value
|
|
"""
|
|
self.op = op
|
|
self.operands = operands
|
|
|
|
def evaluate(self) -> Any:
|
|
"""Return the *value* of this expression.
|
|
|
|
The returned value should the result of how this expression would be
|
|
evaluated by the Python interpreter.
|
|
|
|
>>> expr = BoolOp('and', [Bool(True), Bool(True), Bool(False)])
|
|
>>> expr.evaluate()
|
|
False
|
|
>>> expr = BoolOp('and', [Bool(True), Bool(True), BoolOp('or', [Bool(True), Bool(False)])])
|
|
>>> expr.evaluate()
|
|
True
|
|
"""
|
|
results = [e.evaluate() for e in self.operands]
|
|
if self.op == 'and':
|
|
return all(results)
|
|
elif self.op == 'or':
|
|
return any(results)
|
|
else:
|
|
raise ValueError(f'Cannot evaluate, {self.op} is not either "and" or "or"')
|
|
|
|
def __str__(self) -> str:
|
|
"""Return a string representation of this boolean expression.
|
|
|
|
>>> expr = BoolOp('and', [Bool(True), Bool(True), Bool(False)])
|
|
>>> str(expr)
|
|
'(True and True and False)'
|
|
"""
|
|
op_string = f' {self.op} '
|
|
return f'({op_string.join([str(v) for v in self.operands])})'
|
|
|
|
|
|
class Compare(Expr):
|
|
"""A sequence of comparison operations.
|
|
|
|
In Python, it is possible to chain together comparison operations:
|
|
x1 <= x2 < x3 <= x4
|
|
|
|
This is logically equivalent to the more explicit binary form:
|
|
(x1 <= x2) and (x2 <= x3) and (x3 <= x4),
|
|
except each expression (x1, x2, x3, x4) is only evaluated once.
|
|
|
|
Instance Attributes:
|
|
- left: The leftmost value being compared. (In the example above, this is `x1`.)
|
|
- comparisons: A list of tuples, where each tuple stores an operation and
|
|
expression. (In the example above, this is [(<=, x2), (<, x3), (<= x4)].)
|
|
|
|
Note: for the purpose of this prep, we'll only allow the comparison operators <= and <
|
|
for this class (see representation invariant below).
|
|
|
|
Representation Invariants:
|
|
- len(self.comparisons) >= 1
|
|
- all(comp[0] in {'<=', '<'} for comp in self.comparisons)
|
|
- self.left and every expression in self.comparisons evaluate to a number value
|
|
"""
|
|
left: Expr
|
|
comparisons: list[tuple[str, Expr]]
|
|
|
|
def __init__(self, left: Expr,
|
|
comparisons: list[tuple[str, Expr]]) -> None:
|
|
"""Initialize a new comparison expression.
|
|
|
|
Preconditions:
|
|
- len(comparisons) >= 1
|
|
- all(comp[0] in {'<=', '<'} for comp in comparisons)
|
|
- left and every expression in comparisons evaluate to a number value
|
|
"""
|
|
self.left = left
|
|
self.comparisons = comparisons
|
|
|
|
def evaluate(self) -> Any:
|
|
"""Return the *value* of this expression.
|
|
|
|
The returned value should the result of how this expression would be
|
|
evaluated by the Python interpreter.
|
|
|
|
>>> expr = Compare(Num(1), [
|
|
... ('<=', Num(2)),
|
|
... ('<', Num(4.5)),
|
|
... ('<=', Num(4.5))])
|
|
>>> expr.evaluate()
|
|
True
|
|
>>> expr = Compare(Num(1), [
|
|
... ('<=', Num(-2)),
|
|
... ('<', Num(4.5)),
|
|
... ('<=', Num(4.5))])
|
|
>>> expr.evaluate()
|
|
False
|
|
>>> expr = Compare(Num(1), [
|
|
... ('<=', Num(4.5)),
|
|
... ('<=', Num(2)),
|
|
... ('<', Num(4.5))])
|
|
>>> expr.evaluate()
|
|
False
|
|
"""
|
|
left = self.left.evaluate()
|
|
for c in self.comparisons:
|
|
v = c[1].evaluate()
|
|
if c[0] == '<' and left >= v:
|
|
return False
|
|
if c[0] == '<=' and left > v:
|
|
return False
|
|
left = v
|
|
return True
|
|
|
|
def __str__(self) -> str:
|
|
"""Return a string representation of this comparison expression.
|
|
|
|
>>> expr = Compare(Num(1), [
|
|
... ('<=', Num(2)),
|
|
... ('<', Num(4.5)),
|
|
... ('<=', Num(4.5))])
|
|
>>> str(expr)
|
|
'(1 <= 2 < 4.5 <= 4.5)'
|
|
"""
|
|
s = str(self.left)
|
|
for operator, subexpr in self.comparisons:
|
|
s += f' {operator} {str(subexpr)}'
|
|
return '(' + s + ')'
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import python_ta.contracts
|
|
python_ta.contracts.check_all_contracts()
|
|
|
|
import doctest
|
|
doctest.testmod()
|
|
|
|
import python_ta
|
|
python_ta.check_all(config={
|
|
'max-line-length': 100,
|
|
'disable': ['E1136']
|
|
})
|