There's the last guy called reduce
but it's not here because it's like the odd guy out and requires a dedicated article to it
Let us map
Before we map, let us take a look at a beautiful for-loop in Python.
squares = []
for i in range (2, 10):
squares.append(i ** 2)
print(squares) # [4, 9, 16, 25, 36, 49, 64, 89]
The point of the above is to take the numbers 2 up to 10 (exclusive) and get the squares of them. With a map
function, we can easily achieve that with grace and mercy.
The map function, included in the global namespace by default is an elegant way to perform operations on an iterable, basically replacing an expressive for-loop, like so:
# returns an iterator of our results
squares = map(lambda x: x ** 2, range(2, 10))
# we can then get a list with
squares = list(squares)
The map
function accepts two arguments: a function and an iterable; and applies the function to each member of the iterable and spits the result as a generator. Generally, lambda functions are preferred as the function to be used due to its conciseness. We can with this define the internal implementation of a map
function crudely to be:
from typing import (
Any,
Callable,
Iterable,
Generator
)
def map(func: Callable, iterable: Iterable) -> Generator[Any, Any, Any]:
# the first `Any` is the type of the value being returned
# we can replace the `Generator` with an `Iterator`
# but we're not here to learn about types
for member in iterable:
yield func(member)
# so we call it like so:
map(func, iterable)
With this information, map
can (not every time) essentially replace your for-loops with a more concise definition that looks linear to the eye especially when you are performing simple operations. We can see more examples of map
like so:
# This is only for the purpose of demonstration
names = list(map(
lambda x: (input("Enter a name: ")).strip(),
range(10)
))
# displays names
print(names)
Performance with the usual list
wrapper
You may be concerned as to the performance implications of using a map and then wrapping the results in a list. Relieving your fears is the fact that map
returns a generator-like (an iterator) result which ensures results are yielded only on demand. So wrapping the result in a list would have little or no performance overhead.
And then filter
out
Filter on the other hand is a function with a similar structure with map
except that it executes the function against the iterable and only returns value that evaluates to True
. In this sense, it:
Filters out falsities and untrue things, revealing their true nature in an elegant display of absence.
A good way to use filter
is a to filter out odd numbers from a list:
even_numbers = list(filter(
lambda x: not (x % 2),
range(100)
))
print(even_numbers) # -> 0, 2, 4, 6, 8, 10, ..., 98
The inner working of filter
can be described crudely as:
from typing import (
Any,
Callable,
Iterable,
Generator,
Optional
)
def filter(func: Optional[Callable], iterable: Iterable) -> Generator[Any, Any, Any]:
for member in iterable:
result = func(member)
if result:
yield result
# so we call it like so:
filter(func, iterable)
You'll notice that the func
attribute is marked as optional. This is because you can pass None
to filter
, except that you'll get only values in the iterable that evaluate as True
.
And so therefore
Go out there and populate codebases like JS spawns till you get the kind of comment I got on a PR:
A list comprehension is better than all these
lambda
andmap
you're putting everywhere.
If you're fascinated by lambda functions, those lambda x: x ** 2
you see in the filter
and map
functions, you can read my article on lambda functions.