<<

Overview Higher-Order List Functions The big ideas for today’s lecture and the next lecture:

o In Python, functions are first-class values that can be map, filter, and reduce manipulated like other values (numbers, strings, booleans, lists, dictionaries, objects, etc.). They can be:

1. Named by variables 2. Passed as arguments to other functions 3. Returned as results from other functions 4. Stored in data structures (lists, dictionaries, objects)

CS111 o Functions that take other functions as arguments or return functions as results are called higher-order functions. Department of Computer Science Wellesley College o Higher-order list functions like map, filter, and reduce capture common list-manipulation patterns. They are much simpler than loops for performing many list manipulation tasks. 21-2

Some num-to-num functions names are just variables!

Here are some Python functions that take a single When we define a function, the function name is just numeric argument and return a numeric result. a variable whose value is the function object. We’ll call these num-to-num functions. In[5]: increment # no parens! Out[5]: def increment(n): return n+1 In[6]: double def double(w): return w*2 Out[6]:

def half(x): return x/2.0 In[7]: renamedDouble = double

def square(y): return y*y In[8]: renamedDouble(7) Out[8]: 14 def cube(z): return z*z*z

21-3 21-4 Functions as arguments: applyTo5 Functions as arguments: leastInput

We can define functions that take another function def leastInput(fcn, threshold) as a parameter. For example: '''Return the least nonnegative integer for which fcn returns an output greater than threshold''' i = 0 def applyTo5(f): Can pass a function while fcn(i) <= threshold: return f(5) as an argument! # loop until fcn output surpasses threshold i += 1 When we invoke functions like applyTo5, we pass the return i # least i for which fcn(i) > threshold function argument by using its name (without parens!) leastInput(square, 10)

applyTo5(increment) leastInput(square, 100) leastInput(cube, 10) applyTo5(double) leastInput(cube, 100) applyTo5(half) leastInput(double, 10) leastInput(double, 100) applyTo5(square) leastInput(half, 10)

applyTo5(cube) 21-5 leastInput(half, 100) 21-6

Functions as results: randomFunction Testing randomFunction def randomFunction(): def testRandomFunction(num): '''Returns one of five num-to-num functions''' fcn = randomNumToNumFunction() funs = [increment, double, half, square, cube] return fcn(num) # can store functions in a list! f = random.choice(funs) # can name function with a variable! In[12]: testRandomFunction(10) return f # can return function as a result! Out[12]: 20

In[9]: randomFunction() In[13]: testRandomFunction(10) Out[9]: Out[13]: 1000

In[10]: randomFunction() Out[10]: In[14]: testRandomFunction(10) Out[14]: 11 In[11]: randomFunction() Out[11]:

21-7 21-8 Simplifying randomFunction Higher-order functions

def randomFunction2(): A function is higher-order if it takes another function return random.choice([increment, double, as an argument or returns another function as a result. half, square, cube])

def testRandomFunction2(num): Here are examples of higher-order functions we’ve seen: return (randomFunction2())(num) o applyTo5 o leastInput o randomFunction

21-9 21-10

Functions are first-class values! The map function captures the mapping A first-class value is an entity with these properties: pattern

1. It can be named by a variable; Previously, we saw examples of the list mapping 2. It can be passed as an argument to a function; pattern, in which the output list is the same length as the input list, and each element of the output list 3. It can be returned as the result from a function; is the result of applying some function to the 4. It can be stored in a data structure corresponding element of the input list. (e.g., list, tuple, dictionary, object) We already knew the following were first-class values mapDouble( [8, 3, 6, 7, 2, 4] ) in Python: numbers, strings, booleans, None, *2 *2 *2 *2 *2 *2 lists, tuples, dictionaries, objects [16, 6, 12, 14, 4, 8] Today we learned functions are first-class values, too!

21-11 21-12 The map function captures the mapping The map function captures the mapping pattern pattern

Previously, we saw examples of the list mapping Python provides a map function that captures this pattern, in which the output list is the same length pattern. When invoked as map(function, as the input list, and each element of the output list inlist), it returns a new output list that’s the is the result of applying some function to the same length as inlist in which every element is the corresponding element of the input list. result of applying function to the corresponding element of inlist.

mapPluralize( [‘donut’, ‘muffin’, ‘bagel’] ) map(f, [e1, e2, …, en] ) +s +s +s f f f [‘donuts’, ‘muffins’, ‘bagels’] [f(e1), f(e2), …, f(en)]

21-13 21-14

map examples map examples

Suppose we define the following functions: In[15]: map(double, [8,3,6,7,2,4]) Out[15]: [16, 6, 12, 14, 4, 8]

def double(n): def pluralize(st): In[16]: map(pluralize, [‘donut’,‘muffin’,‘bagel’]) return 2*n return st+’s’ Out[16]: [‘donuts’, ‘muffins’, ‘bagels’]

Note that the first argument to map in these examples Then we can use map to apply these functions to is a one-argument function. It is just the function itself each element of a list: and not the result of applying the function to anything. In[15]: map(double, [8,3,6,7,2,4]) There’s a big difference between these two situations: Out[15]: [16, 6, 12, 14, 4, 8] In[17]: pluralize # just the function itself Out[17]: In[16]: map(pluralize, [‘donut’,‘muffin’,‘bagel’]) Out[16]: [‘donuts’, ‘muffins’, ‘bagels’] In[18]: pluralize(‘donut’) # result of applying function

21-15 Out[18]: ‘donuts’ 21-16 mapDouble and mapPluralize without loops Customized map function

def mapDouble(nums): return map(double, nums) How does map work? To illustrate, we can define our own version of Python's map function as follows: def mapPluralize(strings): return map(pluralize, strings) def myMap(f, elts): result = [] for e in elts: In[19]: mapDouble([8,3,6,7,2,4]) result.append(f(e)) Out[19]: [16, 6, 12, 14, 4, 8] return result

In[20]: mapPluralize([‘donut’,‘muffin’,‘bagel’]) Out[20]: [‘donuts’, ‘muffins’, ‘bagels’] In[21]: myMap(double, [8,3,6,7,2,4]) Out[21]: [16,6,12,14,4,8]

21-17 21-18

Customized map function Customized map function

def myMap(f, elts): def myMap(f, elts): result = [] result = [] for e in elts: for e in elts: result.append(f(e)) result.append(f(e)) return result return result

All myMap does is capture the mapping pattern we've The definition of myMap depends critically on being seen before in one function so that we don't have to able to pass a function as a parameter. Not all repeat it over and over again. We write a standard programming languages permit this, but Python does. loop that accumulates a list result in myMap exactly Functions that take other functions as parameters once, and then we never need to write this loop again or return them as results are called higher-order for the mapping pattern. functions. Thus, map is an example of a higher- order list function, i.e., a higher order function that

21-19 manipulates lists. 21-20 Exercise Defining local functions to use with map

Using the map function, define a mapSquare In the previous examples, we defined a global function that takes a list of numbers and returns a function double to use within the mapDouble list of its squares: function definition. It would be nicer to define everything within one function, and Python lets us do In[22]: mapSquare([8,3,5]) this by defining the double function inside the Out[22]: [64,9,25] mapDouble function: Using the map function, define a mapUpper function that takes a list of strings and returns the result of def mapDouble(nums): uppercasing each string. Use the string .upper() # Locally define double function within mapDouble. method to uppercase strings. # Can only be used inside mapDouble and not outside def double(n): In[23]: mapUpper(['ant','bat','cat']) return 2*n return map(double, nums) Out[23]: ['ANT','BAT','CAT']

21-21 21-22

Defining local functions to use with map Defining local functions to use with map

Sometimes we must define the function used by map Sometimes we must define the function used by map locally within another function because it needs to locally within another function because it needs to refer to a parameter or some other piece of refer to a parameter or some other piece of information that is local to the enclosing function. information that is local to the enclosing function.

def mapScale(factor,nums): def mapPrefixes(string): # Can't define this outside of mapScale, because # Can't define this outside of mapPrefixes, because # it needs to refer to parameter named "factor" # it needs to refer to parameter named "string" def scale(n): def prefix(i): return factor*n return string[:i] return map(scale, nums) return map(prefix, range(len(string)+1))

In[24]: mapScale(3, [8,3,6,7,2,4]) Out[24]: [24,9,18,21,6,12] In[26]: mapPrefixes('program') Out[26]: In[25]: mapScale(10, [8,3,6,7,2,4]) [‘’,'p','pr','pro','prog','progr','progra','program'] Out[25]: [80,30,60,70,20,40] 21-23 21-24 Exercise The Filtering Pattern

Using the map function, define a mapPreConcat function that takes a string pre and a list of strings We've seen examples of the filtering pattern, in which and returns the result of concatenating pre in front an output list includes only those input elements for of every string in the list. which a certain predicate is True.

In[27]: mapPreConcat('com', ['puter','pile','mute']) filterEvens( [8, 3, 6, 7, 2, 4]) Out[27]: ['computer','compile','commute'] isEven isEven isEven isEven isEven isEven

True False True False True True [8, 6, 2, 4]

21-25 21-26

The filter function captures the Exercises filtering pattern Using the filter function, define a filterPositive function that takes a list of numbers and returns a list of The Python filter function captures this pattern. It its positive elements. takes a predicate function and a list and returns a list of In[30]: filterPositives([8,-3,6,7,-2,-4,5]) all items for which the predicate function returns True. Out[30]: [8, 6, 7, 5]

def isEven(n): Using the filter function, define a filterSameLength return n%2==0 function that takes a string st and a list of strings and In[28]: filter(isEven, [8,3,6,7,2,4]) returns all the strings in the list that have the same Out[28]: [8, 6, 2, 4] length as st. In[31]: filterSameLength('ant',['the','gray','cat','is','big']) def filterEven(nums): Out[25]: ['the','cat','big'] return filter(isEven, nums) In[32]: filterSameLength('purr',['the','gray','cat','is','big']) Out[26]: ['gray'] In [29]: filterEven([8,3,6,7,2,4]) Out[29]: [8, 6, 2, 4] In[33]: filterSameLength('q',['the','gray','cat','is','big']) 21-27 Out[33]: [] 21-28 Exercises The reduce function captures the accumulation pattern Using the reduce function, define a productList function that takes a list of numbers and returns the product of all the elements in the list. Python supplies a reduce function that can be used to In[38]: productList([4,5,2,3]) accumulate the elements of a list into a result that may Out[38]: 120 not be a list. Using the reduce function, define an areAllPositive In[34]: def plus(a,b): return a+b function that takes a list of numbers and returns True if In[35]: reduce(plus, [8,12,5,7,6,4], 0) all the elements are positive. Otherwise the function Out[35]: 42 returns False. In[39]: areAllPositive([4,5,2,3]) In[36]: reduce(plus, ['I','have','a','dream'], '') Out[36]: 'Ihaveadream' Out[39]: True In[40]: areAllPositive([4,5,-2,3]) In[37]: reduce(plus, [[8,2,5],[17],[],[6,3]], []) Out[40]: False Out[37]: [8,2,5,17,6,3] In[41]: areAllPositive([-4,5,2,-3]) 21-29 Out[41]: False 21-30