We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Pattern Matching, f-Strings and Newer Syntax step 11 of 23
Simplify Arithmetic Expressions with Class Patterns
A calculator often stores an expression as a tree of small objects, so
(2 + x) * 3 becomes Mul(Add(Lit(2), Var("x")), Lit(3)). To simplify the
tree, you check each node's class and look at its children. A class pattern
does both. You have used int() and str(), and your own classes work the
same way.
The starter's type Expr = Lit | Var | Neg | Add | Mul uses the alias
syntax added in Python 3.12, which can name classes defined below it.
A class pattern checks the type and reads attributes
match expr:
case Add(left=Lit(0), right=other):
return other
This case matches when expr is an Add, as isinstance would check, and
its left attribute matches Lit(0). The capture pattern other stores its
right attribute. Python reads the attributes and never calls Add(...).
case Add(a, b): means the same as case Add(left=a, right=b):. Python
takes the names, in order, from Add.__match_args__, which @dataclass
sets to ("left", "right"). If you reorder a dataclass's fields, its
positional patterns change meaning.
Match two values at once
A comma after match makes the subject a tuple. Then one case covers a zero
on either side:
match simplify(left), simplify(right):
case (Lit(0), keep) | (keep, Lit(0)):
return keep
Each side of | must store values in the same names. Python calls this
binding a name. Here both sides bind keep, so the body can use it
whichever side matched. If the names differ, Python reports
SyntaxError: alternative patterns bind different names.
What to write
build(tokens) reads reverse Polish notation, where each operator follows
its operands, so ["2", "x", "+", "3", "*"] means (2 + x) * 3. Use a
stack. For "+" or "*", pop the right operand, then the left, and push an
Add or a Mul. For "~", pop one operand and push a Neg. For any other
token, push Lit(int(token)), or Var(token) if int raises ValueError.
The tokens are always valid. Do not change the list.
simplify(expr) returns Lit and Var unchanged. For other nodes, simplify
the children first, then apply the first rule that fits. If none fits,
rebuild the same kind of node from the simplified children. Add no other
rules.
| Node, after simplifying its children | Result |
|---|---|
Neg(Lit(n)) |
Lit(-n) |
Neg(Neg(x)) |
x |
Add(Lit(0), x) or Add(x, Lit(0)) |
x |
Add(Lit(a), Lit(b)) |
Lit(a + b) |
Mul with Lit(0) on either side |
Lit(0) |
Mul(Lit(1), x) or Mul(x, Lit(1)) |
x |
Mul(Lit(a), Lit(b)) |
Lit(a * b) |
render(expr) writes a Lit as str(value), a Var as its name, and a
Neg as "-" before its operand, so double negation renders as "--x".
It writes Add and Mul as f"({left} + {right})" and
f"({left} * {right})".
evaluate(expr, env) returns the int value, reading each Var from env.
solve(tokens, env) builds the tree, simplifies it, and returns both
renderings and the value of the simplified tree:
solve(["2", "3", "+", "4", "*"], {})
# {"original": "((2 + 3) * 4)", "simplified": "20", "value": 20}
A match that covers all five classes needs no fallback case, because
mypy --strict, which the grader runs, can see that every path returns.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.