Map, Reduce, Filter
Map, Reduce, Filter are paradigms of functional programming. WIth them, we can write simpler and shorter programs. They are applied on iterables.
Map
map(callable, iterable)
applies callable to every iterable.
Example: Add 1 to each element in the list.
1
2
| # Unpack map obj (applies the function on everything in the list)
x,y,z = map(lambda x: x+1, [1,2,3])
|
Reduce
reduce(callable, iterable)
can apply callable on iterable
, accumulatively, meaning in the below example, the lambda is applied on each item and the current accumulated result.
1
2
3
4
5
| from functools import reduce
ls = [1,2,3,4,5]
# see 18, the sum of ls
print(reduce(lambda x, y: x+y, ls))
|
max
, min
, sorted
takes in a function returns a “key” for sorting
1
2
3
| ls = ["str1", "str222", "str3333"]
max(ls, key=lambda x: len(x))
# see "str3333"
|
Filter
Filter, as its name suggests, returns a filter(predicate, iterable)
objects that can be casted into a “leaned down” iteration, after applying predicate
.
1
2
3
4
| di = {1:"1", 2:"2", 3:"3"}
# converts to [(1, "1"), (2, "2"), (3, "3")]
di_list = list(di.items())
print(list(filter(lambda x: x[0] < 3, di_list)))
|
Lambda Function
A lambda expression is a function without name.
1
2
3
| #input is x, return x+1
b = lambda x:x+1
print b(1)
|
partial
functools.partial
returns a wrapper with some args bound to given values.
1
2
3
4
5
6
7
8
| # 1 partial - we just need to bind the function with any keyworded args
from functools import partial
def func(a, b):
print("func: ", a, b)
func_w = partial(func, b = 12)
func_w(a = 13)
|
- Its equivalent implementation is
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| def rico_partial(func, *args, **kwargs):
# simplified version
# def wrapper(a):
# # kwargs here is a dict, need to unpack it
# return func(a, **kwargs)
# return wrapper
def wrapper(*extra_args, **extra_kwargs):
# need nonlocal since we are reusing args, and kwargs, which will be true local vars
nonlocal args, kwargs
# args here is a tuple already
args = list(args)
args.extend(extra_args)
kwargs = {**kwargs, **extra_kwargs}
return func(*args, **kwargs)
return wrapper
rico_func_w = rico_partial(func, b = 12)
rico_func_w(a=13)
|
cached, lru_cache
lru_cache
caches the result of up to a maximum number of recent calls. Once there are more functions than the max, the least called functions are discarded.
1
2
3
4
5
6
7
8
9
10
| from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_function(x):
print(f"Computing for {x}")
return x * 2
print(expensive_function(2)) # Computes and caches the result
print(expensive_function(2)) # Returns cached result
|
cache
came with Python 3.9
. It simply caches results of all functions. It’s equivalent to lru_cache(maxsize=None)
1
2
3
4
5
6
7
8
9
| from functools import cache # Python 3.9+
@cache
def expensive_function(x):
print(f"Computing for {x}")
return x * 2
print(expensive_function(2)) # Computes and caches the result
print(expensive_function(2)) # Returns cached result
|
Run Function Only Once
My favorite is
1
2
3
4
| def remove_results_dir():
if hasattr(remove_results_dir, "called") and os.path.exists(RESULTS_DIR):
...
remove_results_dir.called = True
|