61a Mt2 Study Guide
61a Mt2 Study Guide
Rational implementation using functions: List comprehensions: List & dictionary mutation:
def rational(n, d): [<map exp> for <name> in <iter exp> if <filter exp>] >>> a = [10] >>> a = [10]
>>> b = a >>> b = [10]
def select(name): This Short version: [<map exp> for <name> in <iter exp>] >>> a == b >>> a == b
if name == 'n': function True True
A combined expression that evaluates to a list using this
return n represents
evaluation procedure:
>>> a.append(20) >>> b.append(20)
elif name == 'd': a rational 1. Add a new frame with the current frame as its parent
>>> a == b >>> a
number True [10]
return d 2. Create an empty result list that is the value of the >>> a >>> b
return select expression [10, 20] [10, 20]
def numer(x): 3. For each element in the iterable value of <iter exp>: >>> b >>> a == b
return x('n') Constructor is a A. Bind <name> to that element in the new frame from step 1 [10, 20] False
higher-order function B. If <filter exp> evaluates to a true value, then add
def denom(x): the value of <map exp> to the result list >>> nums = {'I': 1.0, 'V': 5, 'X': 10}
return x('d') Selector calls x >>> nums['X']
The result of calling repr on a value is >>> 12e12
10
Lists: what Python prints in an interactive session 12000000000000.0
>>> print(repr(12e12))
>>> nums['I'] = 1
>>> digits = [1, 8, 2, 8] The result of calling str on a value is 12000000000000.0 >>> nums['L'] = 50
>>> len(digits) what Python prints using the print function >>> nums
4 {'X': 10, 'L': 50, 'V': 5, 'I': 1}
digits >>> today = datetime.date(2019, 10, 13) >>> print(today)
>>> digits[3] >>> sum(nums.values())
2019-10-13
8 66
str and repr are both polymorphic; they apply to any object >>> dict([(3, 9), (4, 16), (5, 25)])
>>> [2, 7] + digits * 2 repr invokes a zero-argument method __repr__ on its argument {3: 9, 4: 16, 5: 25}
[2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
>>> today.__repr__() >>> today.__str__() >>> nums.get('A', 0)
>>> pairs = [[10, 20], [30, 40]] 'datetime.date(2019, 10, 13)' '2019-10-13' 0
>>> pairs[1] >>> nums.get('V', 0)
[30, 40] pairs Type dispatching: Look up a cross-type implementation of an 5
>>> pairs[1][0] operation based on the types of its arguments >>> {x: x*x for x in range(3,6)}
30 Type coercion: Look up a function for converting one type to {3: 9, 4: 16, 5: 25}
another, then apply a type-specific implementation.
Executing a for statement:
for <name> in <expression>: Functions that aggregate iterable arguments >>> sum([1, 2]) >>> any([False, True])
<suite> • sum(iterable[, start]) -> value sum of all values 3 True
1. Evaluate the header <expression>, • max(iterable[, key=func]) -> value largest value >>> sum([1, 2], 3) >>> any([])
which must yield an iterable value max(a, b, c, ...[, key=func]) -> value 6 False
(a list, tuple, iterator, etc.) min(iterable[, key=func]) -> value smallest value >>> sum([]) >>> max(1, 2)
min(a, b, c, ...[, key=func]) -> value 0 2
2. For each element in that sequence,
>>> all([False, True]) >>> max([1, 2])
in order: • all(iterable)-> bool whether all are true False 2
A. Bind <name> to that element in any(iterable) -> bool whether any is true >>> all([]) >>> max([1, -2], key=abs)
the current frame
True -2
B. Execute the <suite> Many built-in map(func, iterable):
Unpacking in a Python sequence Iterate over func(x) for x in iterable
A sequence of operations
for statement: fixed-length sequences filter(func, iterable): You can copy a list by calling the list
return Iterate over x in iterable if func(x) constructor or slicing the list from the
iterators that beginning to the end.
>>> pairs=[[1, 2], [2, 2], [3, 2], [4, 4]] zip(first_iter, second_iter):
compute results
>>> same_count = 0 Iterate over co-indexed (x, y) pairs
lazily
reversed(sequence): >>> suits = ['coin', 'string', 'myriad']
A name for each element in a
fixed-length sequence Iterate over x in a sequence in reverse order >>> suits.pop() Remove and return
'myriad' the last element
To view the list(iterable): >>> suits.remove('string')
>>> for x, y in pairs: Remove a value
contents of Create a list containing all x in iterable >>> suits.append('cup')
... if x == y:
an iterator, tuple(iterable): >>> suits.extend(['sword', 'club'])
... same_count = same_count + 1
place the >>> suits[2] = 'spade'
>>> same_count Create a tuple containing all x in iterable Add all
resulting >>> suits
2 sorted(iterable): values
elements into ['coin', 'cup', 'spade', 'club']
a container Create a sorted list containing x in iterable Replace a
..., -3, -2, -1, 0, 1, 2, 3, 4, ... >>> suits[0:2] = ['diamond']
slice with
n: 0, 1, 2, 3, 4, 5, 6, 7, 8, >>> suits values
def cascade(n): >>> cascade(123) fib(n): 0, 1, 1, 2, 3, 5, 8, 13, 21, ['diamond', 'spade', 'club']
if n < 10: 123 Add an element
12 def fib(n): >>> suits.insert(0, 'heart')
print(n) at an index
range(-2, 2) else: 1
if n == 0: >>> suits
return 0 ['heart', 'diamond', 'spade', 'club']
print(n) 12 elif n == 1:
Length: ending value - starting value 123 return 1
cascade(n//10)
Element selection: starting value + index print(n) else:
return fib(n-2) + fib(n-1) False values: >>> bool(0)
>>> list(range(-2, 2)) List constructor •Zero False
[-2, -1, 0, 1] •False >>> bool(1)
•None True
>>> list(range(4)) Range with a 0 •An empty string, >>> bool('')
[0, 1, 2, 3] starting value >>> withdraw = make_withdraw(100) list, dict, tuple False
>>> withdraw(25) >>> bool('0')
Membership: Slicing: 75 True
All other values
>>> digits = [1, 8, 2, 8] >>> digits[0:2] The parent >>> withdraw(25) are true values. >>> bool([])
>>> 2 in digits [1, 8] 50 False
frame contains
True >>> digits[1:] def make_withdraw(balance): >>> bool([[]])
the balance of
>>> 1828 not in digits [8, 2, 8] def withdraw(amount): True
withdraw
True Slicing creates a new object nonlocal balance >>> bool({})
if amount > balance: False
Identity: >>> bool(())
return 'No funds'
<exp0> is <exp1> Every call False
balance = balance - amount
evaluates to True if both <exp0> and decreases the >>> bool(lambda x: 0)
return balance
<exp1> evaluate to the same object same balance True
return withdraw
Equality:
<exp0> == <exp1> Status x = 2 Effect
evaluates to True if both <exp0> and •No nonlocal statement Create a new binding from name "x" to number 2
<exp1> evaluate to equal values •"x" is not bound locally in the first frame of the current environment
Identical objects are always equal values
>>> s = [3, 4, 5] >>> d = {'one': 1, 'two': 2, 'three': 3}
•No nonlocal statement Re-bind name "x" to object 2 in the first frame
iter(iterable): •"x" is bound locally of the current environment
Return an iterator >>> t = iter(s) >>> k = iter(d) >>> v = iter(d.values())
over the elements of >>> next(t) >>> next(k) >>> next(v) •nonlocal x Re-bind "x" to 2 in the first non-local frame of
an iterable value 3 'one' 1 •"x" is bound in a the current environment in which "x" is bound
next(iterator): >>> next(t) >>> next(k) >>> next(v) non-local frame
Return the next element 4 'two' 2
•nonlocal x
A generator function is a function that yields values instead of returning them. •"x" is not bound in SyntaxError: no binding for nonlocal 'x' found
>>> def plus_minus(x): >>> t = plus_minus(3) def a_then_b(a, b): a non-local frame
... yield x >>> next(t) yield from a •nonlocal x
... yield -x 3 yield from b •"x" is bound in a SyntaxError: name 'x' is parameter and nonlocal
>>> next(t) >>> list(a_then_b([3, 4], [5, 6])) non-local frame
-3 [3, 4, 5, 6]
•"x" also bound locally
CS 61A Midterm 2 Study Guide — Page 2
Root or Root Node Python object system:
Recursive description: Path Nodes
Idea: All bank accounts have a balance and an account holder;
•A tree has a root label Root label 3
and a list of branches Labels the Account class should add those attributes to each of its instances
Branch
•Each branch is a tree A new instance is >>> a = Account('Jim')
•A tree with zero branches created by calling a >>> a.holder
is called a leaf 1 2 class 'Jim'
Relative description: >>> a.balance An account instance
•Each location is a node 0 1 1 1 0
When a class is called: balance: 0 holder: 'Jim'
•Each node has a label 1.A new instance of that class is created:
•One node can be the Leaf
0 1 2.The __init__ method of the class is called with the new object as its first
parent/child of another argument (named self), along with any additional arguments provided in the
def tree(label, branches=[]): call expression.
for branch in branches: Verifies the class Account:
tree definition def __init__(self, account_holder):
assert is_tree(branch)
__init__ is called a self.balance = 0
return [label] + list(branches)
constructor self.holder = account_holder
def label(tree): def deposit(self, amount):
Creates a list from a
return tree[0] self.balance = self.balance + amount
sequence of branches
return self.balance
def branches(tree): 3 self should always be
Verifies that tree is def withdraw(self, amount):
return tree[1:] bound to an instance of
bound to a list if amount > self.balance:
the Account class or a
def is_tree(tree): return 'Insufficient funds'
subclass of Account
if type(tree) != list or len(tree) < 1: 1 2 self.balance = self.balance - amount
return self.balance
return False
for branch in branches(tree): 1 1 >>> type(Account.deposit)
if not is_tree(branch): >>> tree(3, [tree(1), Function call: all <class 'function'>
... tree(2, [tree(1), arguments within >>> type(a.deposit)
return False
... tree(1)])]) parentheses <class 'method'>
return True
[3, [1], [2, [1], [1]]]
def is_leaf(tree):
Method invocation: >>> Account.deposit(a, 5)
return not branches(tree) def fib_tree(n):
One object before 10
def leaves(t): if n == 0 or n == 1: >>> a.deposit(2)
the dot and other Call expression
"""The leaf values in t. return tree(n) 12
arguments within
>>> leaves(fib_tree(5)) else: parentheses
[1, 0, 1, 0, 1, 1, 0, 1] left = fib_tree(n-2), Dot expression
""" right = fib_tree(n-1)
if is_leaf(t): fib_n = label(left) + label(right)
return [label(t)] <expression> . <name>
return tree(fib_n, [left, right])
else: The <expression> can be any valid Python expression.
return sum([leaves(b) for b in branches(t)], []) The <name> must be a simple name.
class Tree: Evaluates to the value of the attribute looked up by <name> in the object
def __init__(self, label, branches=[]): Built-in isinstance that is the value of the <expression>.
self.label = label function: returns True if To evaluate a dot expression:
for branch in branches: branch has a class that 1. Evaluate the <expression> to the left of the dot, which yields
assert isinstance(branch, Tree) is or inherits from Tree the object of the dot expression
self.branches = list(branches) 2. <name> is matched against the instance attributes of that object;
if an attribute with that name exists, its value is returned
def is_leaf(self): def fib_tree(n): 3. If not, <name> is looked up in the class, which yields a class
return not self.branches if n == 0 or n == 1:
attribute value
return Tree(n)
else:
4. That value is returned unless it is a function, in which case a
def leaves(tree): bound method is returned instead
left = fib_Tree(n-2)
"The leaf values in a tree."
right = fib_Tree(n-1)
if tree.is_leaf(): Assignment statements with a dot expression on their left-hand side affect
fib_n = left.label+right.label
return [tree.label] return Tree(fib_n,[left, right]) attributes for the object of that dot expression
else: • If the object is an instance, then assignment sets an instance attribute
return sum([leaves(b) for b in tree.branches], []) • If the object is a class, then assignment sets a class attribute
class Link: Some zero Account class interest: 0.02 0.04 0.05
empty = () length sequence attributes (withdraw, deposit, __init__)
def __init__(self, first, rest=empty):
assert rest is Link.empty or isinstance(rest, Link) Instance balance: 0 Instance balance: 0
self.first = first Link instance Link instance attributes of holder: 'Jim' attributes of holder: 'Tom'
self.rest = rest jim_account interest: 0.08 tom_account
first: 4 first: 5
def __repr__(self): >>> jim_account = Account('Jim') >>> jim_account.interest = 0.08
if self.rest: rest: rest: >>> tom_account = Account('Tom') >>> jim_account.interest
rest = ', ' + repr(self.rest) >>> tom_account.interest 0.08
else: >>> s = Link(4, Link(5)) >>> tom_account.interest
0.02
rest = '' >>> s 0.04
>>> jim_account.interest
return 'Link('+repr(self.first)+rest+')' Link(4, Link(5)) >>> Account.interest = 0.05
0.02
>>> s.first >>> tom_account.interest
4 >>> Account.interest = 0.04
def __str__(self): 0.05
>>> s.rest >>> tom_account.interest
string = '<' >>> jim_account.interest
while self.rest is not Link.empty: Link(5) 0.04
>>> print(s) >>> jim_account.interest 0.08
string += str(self.first) + ' '
self = self.rest <4 5> 0.04
return string + str(self.first) + '>' >>> print(s.rest)
<5> class CheckingAccount(Account):
>>> s.rest.rest is Link.empty """A bank account that charges for withdrawals."""
True withdraw_fee = 1
Anatomy of a recursive function:
def sum_digits(n): interest = 0.01
• The def statement header is like any function "Sum the digits of positive integer n." def withdraw(self, amount):
• Conditional statements check for base cases
• Base cases are evaluated without recursive calls
if n < 10: return Account.withdraw(self, amount + self.withdraw_fee)
return n
• Recursive cases are evaluated with recursive calls else: or
all_but_last, last = n // 10, n % 10
return sum_digits(all_but_last) + last return super().withdraw( amount + self.withdraw_fee)