[+] Finish Prep6
This commit is contained in:
Executable
+335
@@ -0,0 +1,335 @@
|
||||
"""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
|
||||
"""
|
||||
left = self.left.evaluate()
|
||||
return all((left < c[1].evaluate() if c[0] == '<' else left <= c[1].evaluate())
|
||||
for c in self.comparisons)
|
||||
|
||||
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']
|
||||
})
|
||||
Reference in New Issue
Block a user