Functional Programming#

Table of Contents

Introduction#

The code we have written so far in this class has either been procedural or object oriented. Today we’re going to learn about another programming paradigm: functional programming.

Functional programming is an approach to programming that focuses computations via modular, stateless, deterministic, goal oriented functions.

Procedural code is comprised of statements describing the exact steps to solve a particular problem and usually involve making modifications to global or external values along the way. In functional programming on the other hand, you rely on tools built into the language to decide how to go about solving the problem, which you provide with functions that describe the desired result. These tend to be more expression, and ideally return the processed data without changing the external state.

Functional programming is a concise and a powerful tool for data processing. The results are often less error prone and (once you get the hang of it) more readable than the procedural equivalent.

In this lesson you’ll learn the functional programming features provided by Python.

Part 1: Mapping#

Generating a new collection by applying the same transformation to every item in a container is called mapping.

Part 1.1: Procedural#

The procedural way to map an iterable is via the old standby, the for loop.

In this example we append to a list converted which contains all elements of the date list with str() applied.

Appending to converted is an example of what is meant when we say that the shared state has been changed.

date = ["Wednesday", "Nov", 4, 2021]

converted = []

for elm in date:
    converted.append(str(elm))

print(converted)
['Wednesday', 'Nov', '4', '2021']

Part 1.2: map()#

Python provides a built in map() function which does the same thing in a single function call.

Hint

map() returns a iterator object which is displayed as something like <map at 0x10f174b80>. Convert it to a list to see the contents.

map() takes two arguments: the function to apply then the iterator.

date = ["Wednesday", "Nov", 4, 2021]
converted = map(str, date)

list(converted)
['Wednesday', 'Nov', '4', '2021']

You can use any callable, such as lambda

days = [
    'Monday',
    'Tuesday',
    'Wednesday',
    'Thursday',
    'Friday',
    'Saturday',
    'Sunday'
]

list(map(lambda x: x[:3], days))
['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']

…a user defined function…

from fractions import Fraction

def to_dec(val):
    num = Fraction(str(val))
    return round(num.numerator / num.denominator, 2)

measurements = ["1/4", "2/5", "5/64"]
list(map(to_dec, measurements))
[0.25, 0.4, 0.08]

…or a type.

from pathlib import Path

files = [
    "README.md",
    "pyproject.toml",
    "bin/build",
]

list(map(Path, files))
[PosixPath('README.md'), PosixPath('pyproject.toml'), PosixPath('bin/build')]

You can even use type methods.

words = [
   'clarify',
   'whereby',
   'above',
   'strong',
   'able',
]

list(map(str.capitalize, words))
['Clarify', 'Whereby', 'Above', 'Strong', 'Able']

Part 1.2: Exercise#

Exercise 101 (Map)

Write a function normalize() that takes one argument text and returns a lowercased version with leading and trailing whitespace removed and spaces replace with underscores.

Map the following list of files list using both a for loop and map().

files = [
  "Oxford English Dictionary.txt",
  "ColorFAQ.txt",
  "custom.css",
  "weather.json",
]

Part 1.3: Multiple Args#

For a transformation callable that takes multiple arguments, you can provide multiple iterables to map().

One value from each iterable will be passed as an argument each iteration.

from operator import add

list(map(add, "abc", "123"))
['a1', 'b2', 'c3']

Part 1.3: Exercise#

Exercise 102 (Mapping Multiple Args)

Using map() apply the function operator.mul to multiply each of the following prices keys by their exchange rate value.

rates = {
    "USD": 1,
    "EUR": .86,
    "CAD": 1.24,
    "GBP": 0.73,
    "MXN": 20.83,
}

prices = {
  141: rates["EUR"],
  45: rates["USD"],
  47: rates["GBP"],
  155: rates["CAD"]
}

Part 2: Filter#

Generating a new collection containing only items that match a certain condition is called filtering.

Part 2.1: Procedural#

Here is how you would filter the old fashioned way, via a for loop.

numbers = [13, 88, 80, 58, 23, 33, 31, 28]

even = []

for num in numbers:
    if num % 2 == 0:
        even.append(num)

even
[88, 80, 58, 28]

Part 2.2: filter()#

Python provides a built in filter() function.

filter() takes two arguments: a boolean function that serves as the filtering condition followed by the iterable to filter.

def is_even(num):
    return num % 2 == 0

numbers = [13, 88, 80, 58, 23, 33, 31, 28]

list(filter(is_even, numbers))
[88, 80, 58, 28]

As with map() you can use any kind of callable as the filtering function.

animals = ["ox", "Tiger", "rabbit", "Dragon"]

list(filter(str.istitle, animals))
['Tiger', 'Dragon']

Keep in mind, the result is evaluated in a boolean context, meaning that falsy values will be filtered out.

numbers = [55, 38, 168, 71, 123, 31, 118, 15]

list(filter(lambda x: x//100, numbers))
[168, 123, 118]

Part 2.3: Truthy filtering#

Often you just want to filter a list for non-empty or truthy values.

If you pass None as the first argument to filter(), it will return only truthy values. In this example, all blank lines will be filtered out.

poem = """
"The sun was shining on the sea,
      Shining with all his might:

He did his very best to make
      The billows smooth and bright —

And this was odd, because it was
      The middle of the night.
"""

list(filter(None, poem.splitlines()))
['"The sun was shining on the sea,',
 '      Shining with all his might:',
 'He did his very best to make',
 '      The billows smooth and bright —',
 'And this was odd, because it was',
 '      The middle of the night.']

Part 3: Reducing#

Aggregation operations, or those that apply the same operation, cumulatively, to each element in a collection to arrive at a single value is called reducing.

Part 3.1: Procedural#

Here’s an example of how you would reduce via a for loop.

In this example, we add each number in the numbers list to the previous total to arrive at the sum of all numbers in the sequence.

numbers = [9, 7, 5, 1, 1]

total = 0

for num in numbers:
    total = total + num

print(total)
23

Part 3.2: reduce()#

Python provides the reduce() function from the functools module.

It takes two arguments: the function to apply followed by the iterable to apply it to.

In this example, we use the operator.add function to calculate the sum of all numbers.

from functools import reduce
from operator import add

numbers = [9, 7, 5, 1, 1]

total = reduce(add, numbers)

print(total)
23

Any function used in reduce() must take two arguments:

  • result – the accumulated value of all previous operations

  • current – the current sequence element

It must return a single value which will be the result value for the next iteration, or returned by reduce() after the last element.

def func(res, cur):
    """Signature for `reduce()` functions."""
    # calculate value here
    value = ...
    # returned value will be the
    # next iterations res
    return value

In this example we re-implement the add() function, using the parameter name running_total for the result and number for the current element.

def add(running_total, number):
    value = running_total + number
    return value

total = reduce(add, numbers)

print(total)
23

Part 3.2: Exercise#

Exercise 103 (Reduce)

Make a list assigned to the variable letters with all the letters in the word of the day.

Concatonate all letters using both a for loop and the functools.reduce() function.

Part 3.3: Initial value#

reduce() determines the initial value of res by calling the type of the first element of the iterable. For example, if your iterable is [1, 2, 3], the initial value of res will be int() or 0.

If you need a different initial value, you can pass it as the optional third argument.

In this example we are converting a tuple of integers into strings then concatenating them.

reduce() would choose 0 as the initial value, but we can override that by passing an empty string ("") as the third argument.

numbers = [1, 2, 3]

reduce(
    lambda res, cur: res + str(cur),
    numbers,
    ""
)
'123'

Here is a more complicated example that removes all punctuation characters from a string by iterating over a string containing the punctuation characters, and using the string to strip as the initial value.

import string
from functools import reduce

filename = "hello_world.py"

reduce(
    lambda text, char: text.replace(char, ""),
    string.punctuation,
    filename
)
'helloworldpy'

Part 3.3: Exercise#

Exercise 104 (Initializer)

Use reduce() to count the number of vowels in a string.

Challenges#

This section contains a series of more challenging exercises using map(), filter() or range().

Fibonacci sequence#

Exercise 105 (Fibonacci)

Use reduce() to generate a list containing the first 10 numbers of the fibonacci sequence in which each successive number is the result of adding the preceeding two.

Example output

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

RGB to Hex Color Code#

Exercise 106 (RGB to Hex)

Use reduce() to convert a iterable of three RGB color values from 0-255 to a six character string containing the equivalant hex color code. (More info.)

Note: The solution is not a mathmatical equation.

Need a hint?

Use string formatting to convert each number to its hex equivalant.

Example

Given

RGB = (34, 34, 171)

Output

'2222AB'

Hex to RGB Color Values#

Exercise 107 (Hex to RGB)

Use reduce() to convert a six character hex color code to list of the equivalant RGB color values from 0-255. (More info.)

Note: The solution is not a mathmatical equation.

Need a hint?
  • Use the range() function and a slice to exract each two digit hex number from the six digit string.

  • You can convert a hex number to a base 10 integer (the normal kind) using the int() constructor by including the optional second argument.

Example

Given

hex_code = "00ffff"

Output

(0, 255, 255)

Get values for multiple keys#

Exercise 108 (Multiple Dictionary Keys)

Given a dictionary mapping letters to their unicode codepoint, use reduce() go return a list containing the values for an iterable of keys.

Example

Given

letters = {
  'a': 97,
  'b': 98,
  'c': 99,
  'd': 100,
  'e': 101,
 }

 keys = ['c', 'a', 't']

Output

[99, 97, None]

Reference#

Glossary#

Functional Programming#

callable#

Any object that can be called such as a function, method, or type.

map#

A functional programming function that applies a function to every element of an iterable and returns a collection of results.

See also: map.

filter#

A functional programming function that applies a predicate function to every element of an iterable and returns a collection that contains the elements for which the predicate evaluated to True.

See also: filter.

reduce#

A functional programming function that applies a function to an every element of an iterable and returns a single cumulative value.

See also: reduce.

predicate#

A function that returns a boolean value of some condition.

See also: predicate.

aggregation#

Grouping together multiple values to calculate a single summary value.

procedural programming#

Code comprised of a list of instructions to tell the computer what to do step by step. Code is organized into functions (proceedures).

See also: procedural programming.

object oriented programming#

An approach to programing focused on encapsulating data and behavior into objects. Code is organized into classes, objects and methods.

See also: object oriented programming.

functional programming#

An approach to programming that focuses computations via modular, isolated, deterministic, goal oriented functions.

See also: functional programming.

deterministic#

When an algorithm, given particular input, will always produce the same output regardless of any external factors. In contrast to nondeterministic.

See also: deterministic.

nondeterministic#

When an algorithm may exhibit different behavior for the same input. In contrast to deterministic.

See also: nondeterministic.

modular#

A software design technique that emphasizes separating the functionality of a program into independent, interchangeable modules, such that each contains everything necessary to execute only one aspect of the desired functionality.

See also: modular.

imperative#
imperative programming#

An approach to programming that involves describing the exact steps the program should take via a series of statements that make updates to the shared state. In contrast to declarative programming.

See also: imperative programming.

declarative#
declarative programming#

An approach to programming that involves describing what the program should do via expressions and tools built into the language rather than describing how to do it via a series of statements. In contrast to imperative programming.

See also: declarative programming.

state#

The information retained throughout a programs runtime–for example the data stored in variables or loaded in from a file.

See also: state.

statefulness#
stateful#

A term describing software, a software component (like a function), or a protocol that references information from previous operations and/or makes changes to the information available for future operations. In contrast to statelessness.

statelessness#
stateless#

A term describing software, a software component (like a function), or a protocol that does not reference information from previous operations nor have any side effects such as saving state for future operations. Instead each operation starts from scratch based only on the input and the only thing effected is the returned output. In contrast to statefulness.

See Also#

Summary#

  • map() is used to transform elements

  • filter() is used to select elements

  • reduce() is used for aggregation