Download Anonymous Functions - CS111

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
Overview of today’s lecture
o  Expressing anonymous functions with lambda
notation.
Anonymous Functions
o  Lots of practice with higher-order functions,
especially the higher-order list operators
map, filter, and reduce. This will be helpful
for PS11.
CS111 Computer Programming
Department of Computer Science
Wellesley College
23-2
Review: the list mapping pattern
Review: higher-order functions
Functions that take other functions as parameters or
return them as results are called higher-order functions.
In the list mapping pattern:
For example, the functions double and isEven are
not higher order, but map and filter are higher-order
•  each element of the output list is the result of
applying some function to the corresponding element
of the input list.
def double(num):
return 2*num
mapDouble( [8,
*2
def isEven(n):
return (n%2)==0
[16,
3,
*2
6,
*2
7,
*2
6, 12, 14,
2,
*2
4,
4] )
*2
8]
mapPluralize( [‘donut’, ‘muffin’, ‘bagel’] )
In [1]: map(double, [8,3,6,7,2,4])
Out[1]: [16, 6, 12, 14, 4, 8]
In [2]: filter(isEven, [8,3,6,7,2,4])
Out[2]: [8, 6, 2, 4]
•  the output list is the same length as the input list;
+s
23-3
+s
+s
[‘donuts’, ‘muffins’, ‘bagels’]
23-4
lambda notation creates anonymous functions
map captures the list mapping pattern
Python provides a map function that captures this
pattern. When invoked as map(function, elements),
it returns a new output list that’s the same length as
elements in which every element is the result of
applying function to the corresponding element of
elements.
map(f, [e1,
f
e2,
…,
f
It is often inconvenient to define a named function just
in order to pass it as the functional argument to higherorder functions like map and filter.
Python provides lambda notation for creating an
anonymous function, which can be used directly with
map and filter without introducing named functions.
In [3]: map(lambda num: num*2, [8,3,6,7,2,4])
Out[3]: [16, 6, 12, 14, 4, 8]
en] )
In [4]: filter(lambda n: (n%2)==0, [8,3,6,7,2,4])
Out[4]: [8, 6, 2, 4]
f
[f(e1), f(e2), …, f(en)]
23-5
Anatomy of a lambda expression
lambda creates anonymous functions
A lambda expression has the form:
A function defined with def
lambda param: bodyExpression
Keyword meaning
“I am a function”
parameter name
of this function
expression for result of this function.
It does not use an explicit return!
lambda num: num*2
I am a function
that takes an
argument named num
23-6
def cube(x):
return x*x*x
In [5]: (lambda x: x*x)(4)
Out[5]: 16
In [3]: cube(4)
Out[3]: 64
In [6]: square = lambda x: x*x
In [4]: cube.__name__
Out[4]: ’cube’
and returns the result
of doubling it
A function defined with lambda
In [7]: square(5)
Out[7]: 25
In [8]: square.__name__
Out[8]: '<lambda>'
lambda n: (n%2)==0
I am a function
that takes an
argument named n
and returns a boolean that
indicates whether it’s even
23-7
23-8
Why lambda?
Your turn: map examples using lambda
In the 1930s and 40s, Alonzo Church developed a
model of computation called the lambda calculus.
In [5]: map(??, [8,3,6,7,2,4])
Out[5]: [64, 9, 36, 49, 4, 16]
It is a programming language with only three
kinds of expressions:
In [6]: map(??,['donut','muffin','bagel'])
Out[6]: ['donuts','muffins','bagels']
•  variables, e.g. x
•  functions expressed in lambda notation,
e.g. the identity function λ x . x
In [7]: map(??, ['donut','muffin','bagel'])
Out[7]: [’t',’n',’l']
•  function application, e.g. (λ x . x) y
Remarkably, this simple language
can express any computable
program – even though it has no
built-in numbers, arithmetic,
booleans, conditionals, lists, loops,
or recursion! (Take CS235 & CS251)
In [8]: map(??,[’can',’foo',’wiki'])
Out[8]: [’cancan',’foofoo',’wikiwiki']
In [9]: map(??, [8,3,6,7,2,4])
Out[9]: [True, False, True, True, False, False]
23-9
Eliminating inner definitions with lambda
In [10]: map(??, [’a',’be',’cat’,’dodo'])
Out[10]: [17, 17, 17, 17]
23-10
Your turn: avoiding inner definitions w/lambda
def mapPreConcat(prefix, strings):
return map(???, ???)
def mapScale(factor, nums):
def scaleBy(n):
return factor*n
return map(scaleBy, nums)
In [13]: mapPreConcat('com', ['puter','pile','mute'])
Out[13]: ['computer','compile','commute’]
In [11]: mapScale(3, [8,3,6,7,2,4])
Out[11]: [24,9,18,21,6,12]
def mapPrefixes(string):
return map(???, ???)
In [12]: mapScale(10, [8,3,6,7,2,4])
Out[12]: [80,30,60,70,20,40]
In [14]: mapPrefixes('cat')
Out[14]: ['c','ca','cat']
In [15]: mapPrefixes('program')
Out[15]: ['p','pr','pro','prog','progr’,
'progra','program']
# version without an inner definition
def mapScale(factor, nums):
return map(lambda n: factor*n, nums)
23-11
23-12
negatePredicate revisited
Limitations of lambda
From last lecture:
def negatePredicate(predicate):
def negatePredicateApply(x):
return not predicate(x)
return negatePredicateApply
lambda bodies must be a single expression.
They can’t be statement (such as print statements, if
statements) or sequences of statements.
For these, map still needs helper functions!
def printDouble(num):
print num, ‘->’, 2*num
return num
def isEven(n)
return n%2 == 0
isOdd = negatePredicate(isEven)
A simpler definition of negatePredicate using lambda:
def negatePredicate(predicate):
return lambda x: not predicate(x)
In [19]: map(printDouble, [8,3,5])
8 -> 16
3 -> 6
5 -> 10
Out[19]: [8, 3, 5]
def myAbs(num):
if num < 0:
return –num
else:
return num
In [20]: map(myAbs, [-7,2,-6])
Out[20]: [7, 2, 6]
21-13
21-14
Mapping over multiple lists
Using map on non-list sequences
The map function allows any number of list arguments
as long as the supplied function takes a number of
arguments that's the same as the number of lists.
The map function can be used on any sequence, but
always returns a list.
parameter may be a tuple!
In [17]: map(lambda s: s.upper(), 'foo')
Out[17]: ['F', 'O', 'O']
In [13]: map(lambda a,b: a+b, [8,3,5], [10,20,30])
Out[13]: [18, 23, 35]
In [18]: map(lambda s: s.upper(), ('ant','bat','cat')
Out[18]: ['ANT', 'BAT', 'CAT']
For mapping over each letter of a string, we can get a string
from the resulting list of strings by using the join method.
In [19]: ''.join(map(lambda s: s.upper(), 'foo'))
Out[19]: 'FOO'
In [14]: map(lambda a,b: a*b, [8,3,5], [10,20,30])
Out[14]: ??
In [15]: map(lambda a,b: (a,b), [8,3,5], [10,20,30])
Out[15]: ??
In [16]: map(lambda a,b,c: a*b+c, [8,3], [10,20], [7,2])
Out[16]: ??
23-15
23-16
Our own map function
Mapping over multiple Lists
When mapping over multiple lists, all the lists must have
the same length; if not, an exception will be raised.
In [17]: map(lambda a,b: a+b, [8,3,5,6], [10,20,30])
----------------------------------------------------------------TypeError
Traceback (most recent call
last)
<ipython-input-93-e73284726> in <module>()
----> 1 map(lambda a,b: a+b, [8,3,5,6], [10,20,30])
How does map work? To illustrate, we can define our
own version of Python's map function as follows:
def myMap(f, elts):
result = []
for e in elts:
result.append(f(e))
return result
(This version does not support multiple list arguments.)
In[21]: myMap(double, [8,3,6,7,2,4])
Out[21]: [16,6,12,14,4,8]
TypeError: unsupported operand type(s) for +: 'int' and
'NoneType'
23-17
Our own map function
21-18
Review: the list filtering pattern
In the list filtering pattern, the output list
includes only those input elements for which a
certain predicate is True.
def myMap(f, elts):
result = []
for e in elts:
result.append(f(e))
return result
filterEvens( [8,
myMap captures list the mapping pattern we've seen
before in a single function so that we don't have to
keep implementing the same loop over and over again.
3,
6,
7,
2,
4])
isEven isEven isEven isEven isEven isEven
True
[8,
False
True
6,
False
True
True
2,
4]
We write a standard loop that accumulates a list
result in myMap exactly once, and then we never need
to write this loop again for the mapping pattern!
21-19
23-20
filter captures the list filtering pattern
Python provides a filter function that captures this
pattern. When invoked as filter(pred, elts),
it returns a new output list contains only those elements
in elts for which the predicate pred returns True
(in the same relative order).
filter(pred, [e1,
[
e2,
…,
en])
pred
pred
pred
bool1
bool2
booln
keep e1
if bool1
is True;
omit e1
if bool1
is False
keep e2
if bool2
is True;
omit e2
if bool2
is False
keep en
if booln
is True;
omit en
if booln
is False
Your turn: filter examples using lambda
In [29]: filter(??, [8,-3,6,7,-2,-4,5])
Out[29]: [8, 6, 7, 5]
In [30]: filter(??, ['a', 'be', 'at', 'bat', ’ant’,
‘cat’, ‘dog’, ‘aardvark’])
Out[30]: ['a', 'at', 'any, ‘aardvark’']
]
23-21
23-22
Using filter on non-list sequences
Your turn: filterSameLength
The output of filter is the same type of sequence
as the input.
def filterSameLength(s, strings):
In [25]: filterSameLength('ant',['the','gray','cat','is','big'])
Out[25]: ['the','cat','big']
In [26]: filterSameLength('purr',['the','gray','cat','is','big'])
Out[26]: ['gray']
In [27]: filterSameLength('q',['the','gray','cat','is','big'])
Out[27]: []
23-23
In [40]: filter(lambda c: c not in 'aeiouy',
'facetiously')
Out[40]: 'fctsl’
In [41]: filter(lambda n: (n%2)==0,
(1,2,3,4,5))
Out[41]: (2, 4) # A tuple, not a list
23-24
The list accumulation pattern
Our own filter function
In the list accumulation pattern, the elements of
an input list are combined into a result using a
binary operator applied left-to-right.
How would we define our own filter function?
def myFilter(pred, elts):
sum( [8,
3,
6,
7])
+
11
+
17
+
24
21-25
reduce captures the list accumulation pattern
23-26
Python provides a reduce function that captures this
pattern. When invoked as reduce(combiner, elements),
it returns the result of combining the elements of the list
from left to right using the binary function combiner.
reduce(comb, [e1,
e2,
…,
reduce examples
In [57]: reduce(lambda a,b:a+b, [7,2,3])
Out[57]: 12
In [58]: reduce(lambda a,b:a*b, [7,2,3])
Out[58]: 42
en] )
In [59]: reduce(lambda sentenceSoFar,word: \
sentenceSoFar + ' ' + word,
['I', 'have', 'a', 'dream'])
Out[59]: 'I have a dream’
comb
comb
result
23-27
23-28
reduce corner cases
reduce with an initial result
In [64]: reduce(lambda a,b:a*b, [7])
Out[64]: 7
In [65]: reduce(lambda a,b:a*b, [])
--------------------------------------------------------------------------TypeError
Traceback (most recent call last)<ipythoninput-65-985b28648a76> in <module>()
----> 1 reduce(lambda a,b:a*b, [])
Sometimes it’s necessary to provide reduce with an
initial result for the accumulation process. It’s always
necessary if we want to accumulate over an empy list!
reduce(comb, [e1,
e2,
…,
en], init)
init
comb
TypeError: reduce() of empty sequence with no initial
value
optional initial
In [66]: reduce(lambda a,b:a*b, [], 1)
Out[66]: 1
In [67]: reduce(lambda a,b:a+b, [7], 10)
Out[67]: 17
value for
accumulating
result
comb
comb
result
23-29
Our own reduce function
23-30
Your turn: reduce examples
def myReduce(combine, elts, initialResult=None):
'''Returns result of combining elements in elts from left to right, starting with initialResult
(if it is supplied), or elts[0] (if it is not supplied)'''
if initialResult != None:
result = initialResult
values = elts
elif len(elts) == 0:
raise TypeError('myReduce on empty sequence with
no initialResult')
else:
result = elts[0]
values = elts[1:]
for val in values:
result = combine(result, val)
return result
21-31
In [78]: reduce(??, [[8,2,5],[17],[],[6,3]], ??)
Out[78]: [8, 2, 5, 17, 6, 3]
In [79]: reduce(??, ['I', 'have', 'a', 'dream'], ??)
Out[79]: 11 # sum of string lengths
# Simpler approach:
# sum(map(len, ['I', 'have', 'a', 'dream']))
In [80]: reduce(??, [7, 215, 36], ??)
Out[80]: '721536’
In [81]: reduce(??, [8, 3, 7, 6, 4], ??)
Out[81]: True # Are all elements positive?
23-32
Does this look familiar?
chunkWords in action
def chunkWords(words, chunkLen):
def chunkFun((chunks, currentChunk), word):
print (chunks, currentChunk) # for debugging
if currentChunk == '':
space = ''
else:
space = ' '
nextChunk = currentChunk + space + word
if len(nextChunk) > chunkLen:
return (chunks + [currentChunk], word)
else:
return (chunks, nextChunk)
(chunks, remainder) = \
reduce(chunkFun, words, ([], ''))
print (chunks, remainder) # for debugging
if remainder != '':
return chunks + [remainder]
else:
return chunks
23-33
Functional programming
Functional programming is a style of programming in which programs
are composed out of pieces making heavy use of functions in
conjunction with operators like map, filter, reduce, and zip.
In contrast to imperative programming, functional programming avoids
loops, mutable data, and assignments that change variable values.
import string
def stringToCSVWords(st):
# Functional style: no loops, assignments, mutable lists
return reduce(lambda csv,word: csv + ',' + word,
map(lambda w: filter(lambda c: c in string.lowercase,
w.lower()),
st.split()))
In [86]: stringToCSVWords('Help! I need somebody.')
Out[86]: 'help,i,need,somebody’
In [87]: stringToCSVWords('You say "Yes", I say "No".')
Out[87]: 'you,say,yes,i,say,no'
23-35
In [85]: chunkWords(['I', 'do', 'not', 'like', 'green',
'eggs', 'and', 'ham'], 10)
([], '')
([], 'I')
([], 'I do')
([], 'I do not')
(['I do not'], 'like')
(['I do not'], 'like green')
(['I do not', 'like green'], 'eggs')
(['I do not', 'like green'], 'eggs and')
(['I do not', 'like green', 'eggs and'], 'ham')
Out[85]: ['I do not', 'like green', 'eggs and', 'ham']
23-34
Related documents