In [1]:
import numpy as np

Lecture 11:

  • Learn about lambda functions

  • How to use map( ), filter( ), and reduce( )

  • Explore the joys of "List comprehension"

Lambda functions

You can spell any Greek letter and use it as a variable name EXCEPT for lambda. As we learned in Lecture 2, lambda is a reserved word. Why? Because lambda has a special meaning in Python; it is reserved for anonymous functions.

The syntax of a lambda function consists of a name =, followed by the word lambda followed by an argument list, a colon (:), and ending with an expression. Here is a simple example of an anonymous function that returns the product of the argument list:

In [2]:
f=lambda x,y : x*y

Let's dissect the statement.

  • f is a new kind of object that represents the function,

  • $x$ and $y$ are the arguments of the anonymous function,

  • and the expression $x*y$ is what is returned when the function is called.

We're familiar with the following syntax for a "normal" function:

In [3]:
def g(x, y):
    return x*y

Both $f$ and $g$ take the same arguments and return the same value. They are essentially the same function.

Let us verify this, by calling both functions with the arguments $x=2$ and $y=10$:

In [4]:
print (f(2,10))
print (g(2,10))

Yup. Both the lambda function $f$ and the 'regular' function $g$ defined with the keyword def are of the type: function

In [5]:
print (type(f))
print (type(g))
<class 'function'>
<class 'function'>

lambda functions should seem familiar. They follow the same syntax you use in math to define functions:

f(x) = x2 +5x + 9   So we could easily write this as a lambda function like this:


In [6]:
h = lambda x: x**2+5.*x+9

For a multivariate function (one with more than one argument), you need to list all the arguments after the reserved word lambda. For example, In math, you’d write the equation for the hypotenus of two sides, $a $ and $b$, as:   hypotenuse($a$, $b$) = $\sqrt{a^2+ b^2}$.

In Python it would be:

In [7]:
hypotenus = lambda a, b:  np.sqrt(a**2+b**2)
print (hypotenus(3,4))

Uses of lamda functions

You may be wondering why lambda functions are useful. The answer is that lambda functions are anonymous- you don't have to give them a name (although we did when we assigned the function to $f$ in the above examples). This comes in handy if you 1) write or use functions that take in other functions as arguments or 2) you just want a quickie one-off calculation.

For the first reason, examples of such functions that take lambda functions are map( ), reduce( ), and filter( ).

Anticipating your further questions, you can look at this useful blog post on the subject:

map( )

lambda is often used in conjunction with the function map( ).

map(func, seq) is a function that takes two arguments: a function, func, and a sequence, seq. func may be an ordinary function or an anonymous function defined in the first argument of map( ).
seq is one or more lists on which the function is performed. So map( ) returns a list generator with the results of whatever func did to the elements in seq.

Here is an example which converts kilometers to miles (1 km = (5/8) miles).

In [8]:
km_to_mi=map(lambda x:(5./8.)*x,[8,10,24])
print (km_to_mi) # see the list generator 
print (list(km_to_mi)) # see the list
<map object at 0x1049ca0b8>
[5.0, 6.25, 15.0]

The anonymous function was defined as the first argument of map( ). This lambda function takes a single variable x (in km), converts it to miles by multiplying by 5/8, and returns the value. The map( ) function then takes a sequence as the second argument, in this case, the sequence is a list with 8,10, and 24 as elements. map( ) converts each of the values in the list to miles by applying the anonymous function.

If our lambda function has TWO variables, e.g., $x,y$, we must pass map( ) TWO lists of the same length for seq:

In [9]:
map(lambda x,y : x*y,[2,3,4],[5,6,7]);

The values for $x$ get taken from the first list of numbers, while $y$ gets taken from the second list. map( ) returns a list with the product of the two input lists:

In [10]:
list(map(lambda x,y : x*y,[2,3,4],[5,6,7]))
[10, 18, 28]

Another way to use map( ) is to define the lists and functions ahead of time, then apply the map( ) to them as follows:

In [11]:
f=lambda x,y : x*y
# but let's see what it does with a print statement: 
print (list(map(f,a,b) ))
[10, 18, 28]

Well that was cool....

You can see that $x$ snags values from the first list, $a$ and $y$ uses values from the second list, $b$.

filter( )

filter(func, seq) is another example of a function that takes a function, func, and a sequence, seq as arguments. The function supplied to filter must return a boolean- either True or False. filter() then applies that function to all the values in the sequence and returns the values that are True. Let's walk through this step by step beginning with a function that returns True or False.

Remember that modulo is the remainder and in Python we can find the modulo of a given variable $x$ with, for example 2 by this syntax: $x\%2$ (spoken as 'x mod 2'). As an example of a boolean function, we can apply modulo 2 to test whether a number is even or odd. When you divide $x$ by 2, even numbered values of $x$ will return 0 (and odd numbers return 1). And, remember that 0 is False and 1 is True.

In [12]:
print ('modulo of 2 divided by 2: ',2%2)
# and you can see that modulo is handy for keeping values between 0 and 360
print ('modulo of 400 divided by  360: ',400%360) 
modulo of 2 divided by 2:  0
modulo of 400 divided by  360:  40

Now let's create an anonymous function that tests whether numbers are even or odd by the value they return. As you just learned, if modulo returns 0 then the remainder is 0 and the original value was even, whereas, if it returns 1, then the original value was odd:

In [13]:
f= lambda x: x % 2

print (f(2))
print (f(3))
print (f(4))
print (f(5))

We can add the relational operator == and return True or False instead of 0 or 1:

In [14]:
f= lambda x: x % 2 == 0

print (f(2))
print (f(3))
print (f(4))
print (f(5))

Now, we can use filter( ) and the function we defined to find the even values in a sequence. Similar to map( ), filter ( ) applies the function to every value in the list, but filter ( ) will only return the values that evaluate to True. The output of filter( ) is a list generator, not itself a list, but we can turn it into a list. For example:

In [15]:
f= lambda x: x % 2 == 0 # tests if a number is even or odd
mylist = list(range(20))
list(filter(f, mylist)) # returns only the even ones
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

reduce( )

reduce( ) is another function that regularly uses a lambda function. Like map( ) and filter( ), reduce(func, seq) takes two arguments: a function and a sequence. With reduce( ), the function is applied to sequential pairs of elements in the list until the list is reduced to a single value, which is then returned. In detail, the function is first performed on the first two elements. The result then replaces the first element and the second is removed. The same function is again applied to the first two elements of the new list, replacing them with the single value from the function, and so on until only a single value remains.

reduce( ) is no longer standard in Python, so it must be imported with the command:

import reduce( ) from functools.

So let's do that.

In [16]:
from functools import reduce 

Let's try an example. We could use reduce( ) to return the factorial of a number $n$. Remember that the factorial is "the product of an integer and all the positive integers below it".
So, we can use our lambda function defined above, which returns the product of two numbers. If we use the reduce function and make the lambda operate on a list of numbers from 1 to $n$, we will get the desired product at the end.

In [17]:
reduce(lambda x,y:x*y,range(1,n+1)) # performs the lambda function  sequentially on the list

We can compare our function with the Numpy version, **np.math.factorial)

In [18]:


List comprehensions

Another succinct way to iterate over sequences and apply different operations, is through List, Dictionary, and Set comprehensions.

A List comprehension is a convenient way of applying an operation to a collection of objects. It takes this basic form:

[expression for element in collection if condition]

Here is an example that takes a list of strings, looks for those with lengths greater than 5 and returns the upper case version using the string.upper( ) method for strings:

In [19]:
mtList=['Andes','Mt. Everest','Mauna Loa','SP Mountain']
[s.upper() for s in mtList if len(s)>5]

[Fun fact: you can get the lower case equivalents with the method string.lower( ).]

Note that you could achieve the same result (the upper case list of all volcanoes with names having more than 5 characters) using our old friend the for loop:

In [20]:
another_list = []
for s in mtList:

Or (challenge!) by using filter( ) and map( ) and an anonymous function:

Each of these three approaches performs similarly, but the list comprehension is the most succinct.

Dictionary Comprehension

Dictionary comprehensions are similar to list comprehensions, but they generate key-value pairs instead of lists. Dictionary comprehensions follow the format:

{key:value for variable in collection if condition}

The following Dictionary comprehension generates a dictionary with a word from mtList as the key and the length of the word as the value

In [21]:
mtList=['Andes','Mt. Everest','Mauna Loa','SP Mountain'] # to remind you what mylist was
{s:len(s) for s in mtList} # dictionary comprehension with mylist
{'Andes': 5, 'Mt. Everest': 11, 'Mauna Loa': 9, 'SP Mountain': 11}

Notice the {key:value, key:value} structure of the output is a dictionary.

Set comprehension

A Set comprehension, returns a set and follows this format:

{expression for value in collection if condition}

The following Set comprehension creates a set composed of the lengths of the words in mylist

In [22]:
{len(s) for s in mtList}
{5, 9, 11}

You can tell that a set was returned because it is in curly braces with no keys.

Complicated comprehensions

List, Dictionary, and Set comprehensions can also replace complicated, nested loops. Here's an example that generates a list of x,y,z triplets if the values obey Pythagorus' rules for right triangles. Chew on it, until you get it:

In [23]:
[(x,y,z) for x in range(1,30) \
    for y in range(x,30) for z in range(y,30) \
    if x**2 + y**2 == z**2]
[(3, 4, 5),
 (5, 12, 13),
 (6, 8, 10),
 (7, 24, 25),
 (8, 15, 17),
 (9, 12, 15),
 (10, 24, 26),
 (12, 16, 20),
 (15, 20, 25),
 (20, 21, 29)]