Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
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