Skip to content

Commit 181b7ee

Browse files
committed
add map(), filter(), and reduce() examples
Closes #7. Adds `supermarket.py`, containing Product class and tuple of products.
1 parent ba9bf4d commit 181b7ee

File tree

11 files changed

+256
-0
lines changed

11 files changed

+256
-0
lines changed

06-map-filter-reduce/mfr1.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
"""
3+
The functions `map`, `filter`, and `reduce` are "paradigms of functional
4+
programming", "a style of programming in which the primary method of
5+
computation is the application of functions to arguments".
6+
7+
All three functions take as their first argument a function object, and as
8+
their second, an iterable object (such as a list). They also all return
9+
function objects. This makes them higher-order functions (in contrast to
10+
first-order functions).
11+
12+
Their purpose is to apply the function to each element in the iterable.
13+
14+
More info:
15+
1. https://ocw.tudelft.nl/courses/introduction-to-functional-programming/
16+
2. https://www.learnpython.org/en/Map%2C_Filter%2C_Reduce
17+
3. https://stackabuse.com/map-filter-and-reduce-in-python-with-examples/
18+
4. https://en.wikipedia.org/wiki/Higher-order_function
19+
20+
"""
21+
22+
# The first, `map()`, performs the function on the iterable and returns a
23+
# "one-to-one" iterable in the form of a map-type object.
24+
25+
pencil_case_contents = ['pencil', 'pen', 'fountain pen', 'felt pen',
26+
'ruler', 'eraser', 'highlighter', 'scissors']
27+
28+
# Let's use the built-in string function to capitalise each element in the list
29+
mapped = map(str.capitalize, pencil_case_contents)
30+
31+
# Note the lack of parentheses after "str.capitalize"; we're not running
32+
# that function, just passing its name to `map` so it can run it for us.
33+
34+
# Because `map()` returns a generator object, we cast it to a list for printing
35+
list_from_mapped = list(mapped)
36+
37+
print(list_from_mapped)
38+
39+
# Had we not cast it to a list and instead simply printed `mapped`, we'd
40+
# get the address in memory of the generator object itself, as you can see
41+
# by uncommenting the next line.
42+
43+
# print(mapped)

06-map-filter-reduce/mfr10.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
from functools import reduce
3+
from supermarket import shopping_list
4+
5+
# Create a list of prices and use `reduce()` to add them up
6+
prices = list(item.price for item in shopping_list)
7+
total = reduce(lambda x, y: x + y, prices)
8+
9+
print(f"The bill for this supermarket trip is ${total:.2f}", end="")
10+
11+
# Filter the shopping list to just those items on special
12+
items_on_special = filter(lambda item: item.on_special, shopping_list)
13+
14+
# Create a list of prices and use `reduce()` to add them up
15+
prices_of_items_on_special = list(item.price for item in items_on_special)
16+
specials_only_total = reduce(lambda x, y: x + y, prices_of_items_on_special)
17+
18+
savings = total - specials_only_total
19+
20+
print(" -- but if you just bought things that were on special, it'd be ", end="")
21+
print(f"${specials_only_total:.2f} (saving you ${savings:.2f}).")

06-map-filter-reduce/mfr2.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
# This time let's use our shopping list from `d7.py` and define a
3+
# custom function to use with `map()`
4+
5+
shopping_list = ["Tomatoes", "Bananas", "Crackers",
6+
"Sugar", "Icecream", "Bread", "Bananas", "Chocolate"]
7+
print(shopping_list)
8+
9+
10+
def starts_with_B(item):
11+
# Return `True` if the first character of the item is "B".
12+
return item[0] == "B"
13+
14+
15+
mapped = map(starts_with_B, shopping_list)
16+
17+
mapped_to_list = list(mapped)
18+
print(mapped_to_list)
19+
20+
# We end up with a "one-to-one" list of True and False values that is
21+
# "mapped" perfectly onto the original iterable/list.

06-map-filter-reduce/mfr3.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
"""
3+
In the previous exercise we defined our function fully, to the point where
4+
it would have been possible to call it outside of `map`.
5+
6+
But you don't need to do this; you can instead use what's known as a "lambda"
7+
or anonymous function. These are single-use, disposable functions.
8+
9+
Lambdas differ from normal Python methods/functions in that they have only one
10+
expression, can't contain any other statements, and return a function object.
11+
12+
The syntax is:
13+
lambda [argument]: [return expression / condition]
14+
"""
15+
16+
17+
shopping_list = ["Tomatoes", "Bananas", "Crackers",
18+
"Sugar", "Icecream", "Bread", "Bananas", "Chocolate"]
19+
print(shopping_list)
20+
21+
mapped = map(
22+
lambda item: item[0] == "B",
23+
shopping_list
24+
)
25+
26+
print(list(mapped))

06-map-filter-reduce/mfr4.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
shopping_list = ["Tomatoes", "Bananas", "Crackers",
3+
"Sugar", "Icecream", "Bread", "Bananas", "Chocolate"]
4+
print(shopping_list)
5+
6+
# This time let's have our function return the value of matching elements
7+
mapped = map(lambda item: item if item[0] == "B" else None, shopping_list)
8+
9+
print(list(mapped))

06-map-filter-reduce/mfr5.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
"""
3+
While `map()` creates a one-to-one generator object, `filter()` requires
4+
that each element in the iterable meet a condition before it is added
5+
to the generator object to be returned.
6+
7+
In lambda expressions, there's an implied "return" keyword after the colon,
8+
and, with the lambda in `filter()`, there is an implied conditional statement.
9+
"""
10+
11+
shopping_list = ["Tomatoes", "Bananas", "Crackers",
12+
"Sugar", "Icecream", "Bread", "Bananas", "Chocolate"]
13+
print(shopping_list)
14+
15+
filtered = filter(lambda item: item[0] == "B", shopping_list)
16+
17+
18+
def starts_with_B(item):
19+
# Lambda written out in full:
20+
if item[0] == "B":
21+
return item
22+
else:
23+
return None
24+
25+
26+
print(list(filtered))

06-map-filter-reduce/mfr6.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
# Let's use the 'collections' module to make a Class called "Product"
3+
# to contain our shopping-list items and some metadata about each of them.
4+
5+
import collections
6+
7+
Product = collections.namedtuple(
8+
'Product',
9+
[
10+
'name',
11+
'brand',
12+
'price',
13+
'price_measure',
14+
'aisle',
15+
'on_special'
16+
]
17+
)
18+
19+
shopping_list = [
20+
Product("Tomatoes", None, .38, "kg", "A", False),
21+
Product("Bananas", None, .67, "kg", "C", False),
22+
Product("Crackers", "Griffin's", 2.35, "box", "F", True),
23+
Product("White sugar", "Chelsea", 5.5, "1.5kg", "D", True),
24+
Product("Vanilla icecream", "Tip Top", 6.5, "2L", "X", True),
25+
Product("Toast bread", "Vogel's", 3.89, "loaf", "A", False),
26+
Product("Chocolate", "Whittaker's", 5.49, "block", "R", True)
27+
]
28+
29+
# You can index into lists and print and element by calling it by name
30+
first_item_name = shopping_list[0].name
31+
print(f"The first item in the list is {first_item_name.lower()}.")
32+
33+
last_item_brand = shopping_list[-1].brand
34+
print(f"The brand name of the last item in the list is {last_item_brand}.")

06-map-filter-reduce/mfr7.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
from supermarket import shopping_list
3+
4+
items_on_special = list(
5+
filter(
6+
lambda item: item.on_special,
7+
shopping_list)
8+
)
9+
10+
print("On special this week:")
11+
for item in items_on_special:
12+
print(f"{item.brand} {item.name.lower()} (${item.price:.2f} / {item.price_measure})")

06-map-filter-reduce/mfr8.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
from supermarket import shopping_list
3+
4+
list_by_aisle = sorted(shopping_list, key=lambda item: item.aisle)
5+
6+
print("To go through the supermaket efficiently, you should buy things in this order: ", end="")
7+
8+
# Create a boolean to check if we're at the last item in the (zero-indexed) list
9+
list_length = len(list_by_aisle) - 1
10+
11+
for position, item in enumerate(list_by_aisle):
12+
13+
# Print out the item name (in lowercase) with no new line
14+
print(item.name.lower(), end="")
15+
16+
# If we're not at the end, print a semicolon and a space, also with no new line
17+
if not position == list_length:
18+
print("; ", end="")
19+
20+
print(".")

06-map-filter-reduce/mfr9.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""
2+
So, `map()` returns a one-to-one generator object, and `filter()` returns
3+
a generator object containing only those elements in the iterable that met
4+
a condition.
5+
6+
The final tool in the trio, `reduce()`, cumulatively performs a function on
7+
each element in an iterable such that only one value is returned.
8+
"""
9+
10+
# `reduce()` used to be built-in but now has to be imported
11+
from functools import reduce
12+
13+
num_list = [1, 3.14159, 6, 93, 102]
14+
total = reduce(lambda x, y: x + y, num_list)
15+
16+
print(total)
17+
18+
# Ultimately adding numbers together like this is not a great use of `reduce()`
19+
# Because built-ins like "sum" and the "add" module in "operator" exist.
20+
# See https://stackoverflow.com/a/33772246/10267529 for details.

0 commit comments

Comments
 (0)