## Anonymous (lambda) functions, map, filter and reduce

We’ve seen how to define a function using the def keyword. This is the standard method of function definition in any case where more than a single expression is to be run. Occasionally, we wish to run a single expression as a function, and for this Python provides the anonymous or lambda function.

## Defining lambda functions

For example, suppose we want a function to calculate the hypotenuse of a right-angled triangle, given the other two sides. We could use the standard function definition to do this, as in

from math import *
def hypot(a, b):
return sqrt(a*a + b*b)

print(hypot(3, 4))

The output is 5.0, as we would hope. We can do the same thing using a lambda function, as in:

from math import *
hypot = lambda a, b: sqrt(a*a + b*b)

print(hypot(3, 4))

On line 2, we used the keyword lambda to define an anonymous function, which is then assigned to the variable hypot, which becomes the function’s name. We can then call this function in the usual way.

The general syntax for a lambda function is

lambda <arguments>: <expression>

Here, lambda is a keyword indicating that an anonymous function is being defined; it’s not the name of the function. At this stage, the function itself has no name (hence ‘anonymous’). Note that, unlike a function definition using def, the list of arguments is not enclosed in parentheses. The end of the argument list is denoted with the colon.

Following the colon, we must give one (and only one) expression which provides a value that the function returns. The expression must be just that, an expression, and not a statement that doesn’t provide a value to return. For example, we cannot say

lambda x, a: x = a

An assignment statement in Python does not return a value (unlike some other languages), so is not valid as an expression in a lambda function.

At this point, lambda functions may seem rather pointless. In the hypotenuse example above, we couldn’t use the function without assigning it to a variable (hypot) and thus giving it a name which we could then use to call the function. If the only way we can use an anonymous function is to give it a name, then what’s its use? In fact, there are ways of using anonymous functions without naming them.

## Multiway branches

Here’s one example of using lambda functions without naming them. The formulas for the areas of regular polygons with side and the circle with radius are

 triangle square pentagon hexagon septagon octagon nonagon decagon circle The following program allows users to enter the name of the shape and its edge length to calculate its area:

from math import *
area = {'triangle': lambda x: sqrt(3) * x * x / 4,
'square': lambda x: x * x,
'pentagon': lambda x: sqrt(5 * (5 + 2*sqrt(5))) * x * x / 4,
'hexagon': lambda x: 3 * sqrt(3) * x * x / 2,
'septagon': lambda x: 7 * x * x * cos(pi/7) / sin(pi/7) / 4,
'octagon': lambda x: 2 * (1 + sqrt(2)) * x * x,
'nonagon': lambda x: 9 * x * x * cos(pi/9) / sin(pi/9) / 4,
'decagon': lambda x: 5 * x * x * sqrt(5 + 2 * sqrt(5)) / 2,
'circle': lambda x: pi * x * x
}

while True:
figure = input('Enter figure and side length (or "quit"):')
if figure == 'quit':
break
figure = figure.split()
print('Area = ', area[figure](float(figure)))

The area object is a dictionary whose keys are the names of the shapes and whose values are lambda functions for calculating the areas. The user can input a line such as pentagon 12. This is split into a list with two elements on line 17. figure contains ‘pentagon’ which is the key passed to the dictionary on line 18. The object area[figure] is the value of area['pentagon'], which is the lambda function for calculating the pentagon’s area. The parenthses (float(figure)) after area[figure] passes the argument float(figure) to this function, which then returns the area of the pentagon. (The cotangent function isn’t available in the math library, so I’ve used the trig formula .)

The important point here is that a Python function is an object that can be passed around a program just like any other object. In this case, the values of the area dictionary are all functions, and thus querying this dictionary returns a function which can then be called by providing its arguments in parentheses. Note that we did not need to give any of the lambda functions names in order to call them.

It’s appropriate to use lambda functions only in cases where a single expression is to be evaluated, and where the code for the function looks better if it’s closer to where the function is to be used. If you find yourself trying to think of some convoluted trick to get a lambda function to work in a particular situation, you’re probably better off using the regular def function.

## The map() function

The built-in map() function takes two arguments: map(<function>,<list>). The map() function applies the <function> to each element of the <list> (We can also use a tuple as the second argument). To view the results, you need to cast the call to map to a list or tuple. For example:

from math import *
roots = list(map(sqrt, list(range(10))))
print(roots)

This applies the sqrt() function in the math module to each element of the list of ints from 0 to 9. The output is a list of these square roots.

Although map() can be used with built-in functions (such as sqrt() here) and with user-defined functions using def, it is commonly used with lambda functions. This is another case where it is not necessary to name a lambda function in order to use it. For example, if we wanted a list of squares, we could write:

squares = list(map(lambda x: x ** 2, list(range(10))))
print(squares)

The output is [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]. (We could do the same thing using a for loop, but this would take a bit more code.)

## The filter() function

The built-in filter() function also takes a function and a list as arguments, so its syntax is filter(<function>, <list>). This time, the <function> must be a boolean test that is performed on each member of <list>. filter() then returns a collection of elements of <list> for which the test evaluates to True. For example

odds = tuple(filter(lambda x: x % 2 != 0, list(range(10))))
print(odds)

The list of ints from 0 to 9 is tested with the condition x % 2 != 0. If the condition is True, the int is odd and is included in the output; if False, the int is even and excluded. The output is (1, 3, 5, 7, 9). I’ve cast the output of filter() to a tuple rather than a list just to show that either is possible.

We can combine map() and filter(). For example, suppose we wanted a list of the odd squares of the ints from 0 to 9. We could write:

oddSquares = list(filter(lambda x: x % 2 != 0, list(map(lambda x: x ** 2, list(range(10))))))
print(oddSquares)

The list using map() produces the squares of the ints from 0 to 9, as before, and this list is fed into the filter() which selects the odd numbers. Due to the proliferation of parenthses, it might be better to split this up into two separate calls, however, but it serves to illustrate the uses of filter() and map().

## The reduce() function

Our final example of a function that is useful with lambda functions is the reduce() function. Unlike map() and filter(), reduce() is not a built-in function, so you need to import it from the functools module.

reduce() takes the same two arguments as map() and filter(): a function and a list or tuple, so its syntax is reduce(<function>, <list>). This time, the <function> must take exactly two arguments, and the elements of the list are fed pairwise into the function. For example, we can use reduce() to sum up all the elements in a tuple, as follows:

from functools import reduce
sum = reduce(lambda a, b: a + b, tuple(range(1, 101)))
print(sum)

This applies a + b successively to each pair of elements in the list. Thus it starts with 1 + 2 = 3, then applies the result to the next element, so we have 3 + 3 = 6, then 6 + 4 = 10 and so on. The result here is the sum of the integers from 1 to 100, which is 5050.

## Exercise

Look up on the web the formulas for the volumes of the five regular Platonic solids (tetrahedron, cube, octahedron, dodecahedron and icosahedron). Write a program in which these volume formulas are stored as lambda functions in a dictionary with keys describing the corresponding solid.

The program should then ask the user for a number of solids to generate. Each solid should be chosen at random from one of the five types, and should have a side length chosen at random in the interval [0, 10]. Create a list of all the generated solids, where the data for each solid is stored as a tuple within the list.

Once you have the complete list of solids, use lambda functions together with the map(), filter() and reduce() functions, as appropriate, to calculate the following:

• The number of solids of each of the five types
• The total volume of the solids in each of the five types
• The total volume of all the solids

The volumes are:

 tetrahedron cube octahedron dodecahedron icosahedron One version of the program which answers the question is:

from math import *
from random import *
from functools import reduce
volume = {
'tetrahedron': lambda x: x ** 3 / (6 * sqrt(2)),
'cube': lambda x: x ** 3,
'octahedron': lambda x: sqrt(2) * x ** 3 / 3,
'dodecahedron': lambda x: (15 + 7*sqrt(5)) * x ** 3 / 4,
'icosahedron': lambda x: 5 * (3 + sqrt(5)) * x ** 3 / 12
}
solids = list(volume.keys())
maxSide = 10

while True:
numSolids = input('How many solids (or "quit")?')
if numSolids == 'quit':
break

numSolids = int(numSolids)
solidList = []
for i in range(numSolids):
solidType = solids[randint(0, len(solids) - 1)]
sideLength = random() * maxSide
solidList += [tuple((solidType, volume[solidType](sideLength)))]

solidTypes = {}
totalVolume = 0
for t in solids:
solidTypes[t] = list(filter(lambda x: x == t, solidList))
numType = len(solidTypes[t])
print('Number of ' + t + 's:', numType)
if numType > 0:
volType = reduce(lambda a, b: a + b, list(map(lambda x: x, solidTypes[t])))
print('Volume of ' + t + 's:', volType)
totalVolume += volType

print('Total volume =', totalVolume)


The volume dictionary uses the same technique as the area dictionary in the previous example. In order to choose a solid type at random, we need a list of the types, which is given on line 11 as a list of the keys in the volume dictionary.

The list of tuples representing the randomly chosen solids is generated in lines 19 through 24. The volume is calculated on line 24 by retrieving the correct lambda function from the volume dictionary and calling it with sideLength as the argument.

To calculate the number of each type of solid, we initialize a dictionary called solidTypes on line 26. The for loop on line 28 loops over each solid type (remember this is the list of keys created on line 11). We filter out those solids in the master list (solidList) by using the filter() function which acts on each tuple from solidList. The  element of each tuple stores the solid’s type, so we filter out those solids whose type matches the value t that we’re currently processing. This creates a list of solids of that particular type, which is stored in solidTypes[t]. The number of solids of that type is then the length of this list, calculated on line 30.

If there is more than one solid of a given type (checked on line 32), we use the map() function to create a list of volumes for that type of solid. The volume of a solid is stored as the  element in its tuple, so the lambda function within the map() function on line 33 selects that element, thus creating a list of volumes for that element. We then pass this list to the reduce() function, also on line 33, which sums up the volumes for that solid type.

This site uses Akismet to reduce spam. Learn how your comment data is processed.