Professional Documents
Culture Documents
CS1010S-Lec-06 Working With Sequences PDF
CS1010S-Lec-06 Working With Sequences PDF
Lecture 6
Working with Sequences
15th Feb 2023
PollEv.com/nitya
Midterm Exam
• Date. Wed, 1st March 2023
• Time. 16:10 to 17:30
• Venue. MPSH 1(A/B)
• One cheat sheet
- A4/letter size
- Printed or handwritten
- Multicolored
PollEv.com/nitya
PollEv.com/nitya
Midterm Exam
• Scope. Everything including today’s lecture!
• Python Expressions
• Solving Problems with Recursion/Iteration
- Order of Growth
• Higher Order Functions
• Data Abstraction
- Define new Abstract Data Type + Operations
PollEv.com/nitya
Only 20%
Don’t Stress
PollEv.com/nitya
No Tutorials & Recitations
• No Recitations on midterm week
• Tutors will still be at the lab on Mon/Tues
during tutorial times for consultation
PollEv.com/nitya
PollEv.com/nitya
Today’s Agenda
• Processing sequences
- Recursion & Iteration
PollEv.com/nitya
Key idea
• Decide on an internal representation of the
Abstract Data Type (ADT) Tuple!
• Write functions that operate on that new
ADT
Key insight: nobody needs to know your internal
representation to use your ADT
PollEv.com/nitya
Sequences
• Sequence is a collection of same type objects
• Sequential data is represented by tuples
• Get the first element of the sequence:
seq[0]
• Get the rest of the elements:
seq[1:]
• If a sequence is a tuple containing a single integer 4:
seq = (4,)
seq[0] à 4
seq[1:] à ()
PollEv.com/nitya
Reversing a Sequence
def reverse(seq):
if seq == ():
return () Recursive
else:
return reverse(seq[1:]) + (seq[0],)
PollEv.com/nitya
Reverse a sequence
• reverse((1, 2, 3, 4))
• reverse((2, 3, 4)) + (1, )
• reverse((3, 4)) + (2,) + (1, )
• reverse((4, )) + (3, ) + (2, ) + (1, )
• reverse(()) + (4, ) + (3, ) + (2, ) + (1, )
• () + (4, ) + (3, ) + (2, ) + (1, )
• (4, ) + (3, ) + (2, ) + (1, )
•
•
(4, 3) + (2, ) + (1, )
(4, 3, 2) + (1, )
Order of growth?
• (4, 3, 2, 1)
PollEv.com/nitya
Visualising Time and Space
• Order of growth
- Time : ! "!
- Space: ! "!
def reverse(seq):
if seq == ():
return ()
else:
return reverse(seq[1:]) + (seq[0],)
PollEv.com/nitya
Key Idea:
Handle the First Element
and then the Rest
Iterate down the sequence!
PollEv.com/nitya
Scaling a sequence
Suppose we want to scale all the elements of a sequence by some
factor
scale_seq((1, 2, 3, 4), 3) à (3, 6, 9, 12)
seq = (1,2,3,4)
(3,) + scale_seq((2,3,4),3)
(3,) + (6,) + scale_seq((3,4),3)
(3,) + (6,) + (9,) + scale_seq((4,),3)
(3,) + (6,) + (9,) + (12,) + scale_seq((),3)
(3,) + (6,) + (9,) + (12,) + ()
(3,) + (6,) + (9,) + (12,)
(3,) + (6,) + (9,12)
(3,) + (6,9,12) Time? !(#2)
(3,6,9,12)
Space? !(#2)
PollEv.com/nitya
Scaling a sequence (iterative)
scale_seq((1, 2, 3, 4), 3) à (3, 6, 9, 12)
Time? !(#2)
Space? !(#)
PollEv.com/nitya
Squaring a sequence
Given a sequence, we want to return a sequence of the squares of all
elements.
square_seq((1, 2, 3, 4)) à (1, 4, 9, 16)
def square_seq(seq):
if seq == ():
return ()
Time? !(#2)
else:
Space? !(#! )
return (seq[0] ** 2, ) +
square_seq(seq[1:])
def square_seq(seq):
if seq == (): Higher-order
return () function!!
else:
return (seq[0] ** 2,) +
square_seq(seq[1:])
PollEv.com/nitya
Mapping
Often, we want to perform the same operation on
every element of a sequence.
( a b c d )
!
(f(a) f(b) f(c) f(d) )
PollEv.com/nitya
Mapping
def map(fn, seq):
Note: map() is HOF since fn is a function
if seq == ():
return ()
else:
return (fn(seq[0]), ) + map(fn, seq[1:])
PollEv.com/nitya
Trees
PollEv.com/nitya
Trees
Trees are sequences of sequences and single elements
- This is possible because of the closure property: we can include a
sequence as an element of another sequence
- This allows us to build hierarchical structures, e.g., trees.
((1, 2), (3, 4), 5)
1 2 3 4
PollEv.com/nitya
Examples
((1, 2), 3, 4)
x = ((1, 2), 3, 4)
len(x) à 3
count_leaves(x) à 4 (1, 2) 3 4
len((x, x)) à 2
count_leaves((x, x)) à 8
PollEv.com/nitya
How would we count
the leaves?
RECURSION!
PollEv.com/nitya
Recurrence Relation
Observation:
# of leaves of tree
X = X X
# of leaves of # of leaves of
X X first sub-tree + rest of tree
PollEv.com/nitya
Recursion
In other words,
count_leaves(tree) =
count_leaves(tree[0]) +
count_leaves(tree[1:])
PollEv.com/nitya
Base Case:
If tree is empty
Zero!
PollEv.com/nitya
Another Base Case
Observe:
Possible for the head or tail to be a leaf!
Leaf Þ +1
PollEv.com/nitya
Summary
Strategy:
- Base cases:
- Empty tree
- Tree with a single node (leaf)
- Recursive step:
- Current + recursive(rest)
PollEv.com/nitya
Count Leaves
def count_leaves(tree):
if tree == ():
return 0
elif is_leaf(tree):
return 1
else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
PollEv.com/nitya
What are leaves
Leaf is any object that is not a sequence
Remember type() in Lecture 1:
>>> t = (1, 2, 3)
>>> type(t)
<class 'tuple'>
def is_leaf(item):
return type(item) != tuple
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
count_leaves on
fist subtree
+ count_leaves
on the rest
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
tree
def count_leaves(tree):
is_leaf false
if tree == ():
5 return 0
elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
elif is_leaf(tree):
tree return 1
is_leaf false 1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
tree + count_leaves(tree[1:])
is_leaf true 3 2 4
count_leaves
+1
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 tree 4
is_leaf true
count_leaves count_leaves
+1 +1
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
2 elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
count_leaves
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
2 elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
count_leaves + count_leaves
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
2 1 elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
count_leaves
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
2 1 elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
count_leaves + count_leaves
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
2 1 elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
count_leaves
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
1 if tree == ():
5 return 0
2 1 1 elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
2 1 1 elif is_leaf(tree):
return 1
1 + else:
1 return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
2 1 elif is_leaf(tree):
return 1
+ 1 else:
2 return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
PollEv.com/nitya
How count_leaves works
tree = ((3, 2), 1, (4,), 5)
def count_leaves(tree):
if tree == ():
5 return 0
2 + 3 elif is_leaf(tree):
return 1
1 else:
return count_leaves(tree[0])
+ count_leaves(tree[1:])
3 2 4
PollEv.com/nitya
How would we scale
each leaves by a factor?
RECURSION!
PollEv.com/nitya
Mapping over trees
Suppose we want to scale each leaf by a factor, i.e.
scale_tree(mytree, 10)
à (10, (20, (30, 40), 50), (60, 70))
PollEv.com/nitya
Strategy
• Since tree is a sequence of sequences, we can map
over each element in a tree.
• Each element is a subtree, which we recursively scale,
and return sequence of results.
• Base case: if tree is a leaf, multiply by factor
PollEv.com/nitya
Mapping over trees
def scale_tree(tree, factor):
def scale_func(subtree):
if is_leaf(subtree):
return factor * subtree
else:
return scale_tree(subtree, factor)
return map(scale_func, tree)
3 2 4
Suppose we do scale_tree(tree, 2)
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
Apply scale_func to each element
def scale_tree(tree, factor):
tree def scale_func(subtree):
if is_leaf(subtree):
return factor*subtree
5 else:
return scale_tree(subtree,
1 factor)
return map(scale_func, tree)
3 2 4
Is a leaf!
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
6 2 4
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
6 2 4
Is a leaf!
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
6 4 4
Is a leaf!
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
Apply scale_func to each element
def scale_tree(tree, factor):
def scale_func(subtree):
if is_leaf(subtree):
return factor*subtree
5 else:
return scale_tree(subtree,
1 factor)
return map(scale_func, tree)
6 4 4
Is a leaf!
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
Apply scale_func to each element
def scale_tree(tree, factor):
def scale_func(subtree):
if is_leaf(subtree):
return factor*subtree
5 else:
return scale_tree(subtree,
2 factor)
return map(scale_func, tree)
6 4 4
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
Apply scale_func to each element
def scale_tree(tree, factor):
def scale_func(subtree):
if is_leaf(subtree):
return factor*subtree
5 else:
return scale_tree(subtree,
2 factor)
return map(scale_func, tree)
6 4 4
Not a leaf!
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
Apply scale_func to each element
def scale_tree(tree, factor):
def scale_func(subtree):
if is_leaf(subtree):
return factor*subtree
5 else:
return scale_tree(subtree,
2 factor)
return map(scale_func, tree)
6 4 4
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
Apply scale_func to each element
def scale_tree(tree, factor):
def scale_func(subtree):
if is_leaf(subtree):
return factor*subtree
5 else:
return scale_tree(subtree,
2 factor)
return map(scale_func, tree)
6 4 4
Is a leaf!
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
Apply scale_func to each element
def scale_tree(tree, factor):
def scale_func(subtree):
if is_leaf(subtree):
return factor*subtree
5 else:
return scale_tree(subtree,
2 factor)
return map(scale_func, tree)
6 4 8
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
Apply scale_func to each element
def scale_tree(tree, factor):
def scale_func(subtree):
if is_leaf(subtree):
return factor*subtree
5 else:
6 4 8
PollEv.com/nitya
Let’s see what scale_tree does
tree = ((3, 2), 1, (4,), 5)
Done applying scale_func to each element
def scale_tree(tree, factor):
def scale_func(subtree):
if is_leaf(subtree):
return factor*subtree
10 else:
return scale_tree(subtree,
2 factor)
return map(scale_func, tree)
6 4 8
PollEv.com/nitya
scale_tree without using map
def scale_tree(tree,factor):
if tree == ():
return ()
elif is_leaf(tree):
return tree*factor
else:
return (scale_tree(tree[0],factor),) + scale_tree(tree[1:],factor)
Compare
def count_leaves(tree):
if tree == ():
return 0
elif is_leaf(tree):
return 1
else:
return count_leaves(tree[0]) + count_leaves(tree[1:])
PollEv.com/nitya
Key Idea:
Traverse tree with
recursion
Check for leaf!
PollEv.com/nitya
Sanity Check (QOTD)
How do you write a function copy_tree that takes a tree
and returns a copy of that tree?
def copy_tree(tree):
return tree # is NOT acceptable!
>>> t = (1, 2, 3)
>>> t_copy = copy_tree(t)
t == t_copy à True
t is t_copy à False
PollEv.com/nitya
Signal Processing View
PollEv.com/nitya
Listening to Music
• Signal goes through various stages of processing.
• Additional component can be inserted.
• Easy to change component.
• Components interface via signals.
PollEv.com/nitya
Benefits
1. Modularity: each component
independent of others; components may
be re-used.
2. Clarity: separates data from processes
3. Flexibility: new component can be added
PollEv.com/nitya
Example: Sum of squares of odd leaves
Given a tree, we want to add the squares of
only those leaves that contain odd number.
PollEv.com/nitya
Example: Sum of squares of odd leaves
def sum_odd_squares(tree):
if tree == ():
return 0
elif is_leaf(tree):
if tree % 2 == 0:
If leaf:
return 0 • If leaf is even -> return 0,
• Else square the leaf node and return
else:
return tree ** 2
else:
return sum_odd_squares(tree[0]) +
sum_odd_squares(tree[1:])
PollEv.com/nitya
Alternative Approach
View it as signal processing computation!
Enumerate Filter Map Accumulate
tree leaves odd? square +, 0
PollEv.com/nitya
Enumerating leaves
What does the following function do?
def enumerate_tree(tree):
if tree == ():
return ()
elif is_leaf(tree):
return (tree,)
else:
return enumerate_tree(tree[0]) +
enumerate_tree(tree[1:])
PollEv.com/nitya
Filtering a sequence
def filter(pred, seq):
if seq == ():
return () If predicate is true, include seq[0]
elif pred(seq[0]): and apply recursion on rest
return (seq[0],) + filter(pred, seq[1:])
else:
If predicate is false, do not include
return filter(pred, seq[1:])
seq[0] and apply recursion on rest
PollEv.com/nitya
Putting it together
def sum_odd_squares(tree):
accumulate(add, 0,
map(square,
filter(is_odd,
enumerate_tree(tree))))
PollEv.com/nitya
Putting it together
(1, 2, (3, 4))
enumerate_leaves
(1, 2, 3, 4)
filter odd?
(1, 3)
map square
(1, 9)
accumulate add , 0
10
PollEv.com/nitya
Another Example:
Tuple of even Fib
Want a list of even fib(k) for
all k up to given integer n.
PollEv.com/nitya
“Usual” Way
def even_fibs(n):
result = ()
for k in range(1, n + 1):
f = fib(k)
if is_even(f):
result = result + (f, )
return result
is_even = lambda x: x % 2 == 0
>>> even_fibs(30)
(2, 8, 34, 144, 610, 2584, 10946, 46368, 196418, 832040)
PollEv.com/nitya
Signal processing view
• Even fibs
Accumulate:
Enumerate: Map: Filter:
lambda x,y:
integers fib is_even
(x,y), ()
PollEv.com/nitya
Enumerate integers
def enumerate_interval(low, high):
return tuple(range(low,high+1))
enumerate_interval(2, 7)
à (2, 3, 4, 5, 6, 7)
PollEv.com/nitya
Even Fibs
def even_fibs(n):
accumulate(lambda x,y: (x,y), (),
filter(is_even,
map(fib,
enumerate_interval(1, n))))
Accumulate:
Enumerate: Map: Filter:
lambda x,y:
integers fib is_even
(x,y), ()
PollEv.com/nitya
Putting it together
(1, 10)
enumerate_interval
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
map fib
(1, 1, 2, 3, 5, 8, 13, 21, 34, 55)
filter even
(2, 8, 34)
accumulate (x,y) , ()
(2, (8, (34, ())))
PollEv.com/nitya
Signal Processing View
• Modular components:
- Enumerate, Filter, Map, Accumulate
- Each is independent of others.
- Modularity is a powerful strategy for controlling
complexity.
PollEv.com/nitya
Signal Processing View
• Build a library of components.
• Sequences used to interface between
components.
PollEv.com/nitya
Re-using components
• Want a sequence of squares of fib(k)
def list_fib_squares(n):
return accumulate(lambda x,y: (x, y), (),
map(square,
map(fib,
enumerate_interval(1, n))
PollEv.com/nitya
Other Uses
• Suppose we have a sequence of personnel records.
• Want to find salary of highest-paid programmer.
def salary_of_highest_paid_programmer(records):
return accumulate(max, 0,
map(salary,
filter(is_programmer, records)))
PollEv.com/nitya
Default Python map and filter functions
Returns an iterable instead of tuple, but you can force it into a tuple.
>>> a = (1,2,3,4,5)
>>> b = filter(lambda x: x%2 == 0, a)
>>> b
<filter object at 0x02EC4710>
PollEv.com/nitya
>>> for i in b:
print(i)
2
4
>>> c = tuple(b)
>>> c
()
PollEv.com/nitya
>>> b = filter(lambda x: x%2 == 0, a)
>>> b
<filter object at 0x02E42C10>
>>> c = tuple(b)
>>> c
(2, 4)
>>> for i in c:
print(i)
2
4
PollEv.com/nitya
Summary
• Data often comes in the form of sequences
- Easy to manipulate using recursion/iteration
- Can be nested
• Closure property allows us to build hierarchical
structures, e.g. trees, with tuples
- Can use recursion to traverse such structures
PollEv.com/nitya
Summary
• “Signal-processing” view of computation.
- Powerful way to organize computation.
- Sequences as interfaces
- Components: (i) Enumeration, (ii) Map, (iii) Filter, (iv)
Accumulate
PollEv.com/nitya