I was trying to understand how reduce
works through this website. The example they have mentioned is quite good and easy to understand.
http://book.pythontips.com/en/latest/map_filter.html#reduce
a = reduce((lambda x, y: x * y), [1, 2, 3, 4])
The above function will multiple each and every number in list and assign to a
.
However, I got totally stumped when I came across following function in project.
def compose(*fns):
return reduce(lambda acc, fn: lambda *args: acc(fn(*args)), fns, lambda _: _)
could someone help me breakdown this function to understand what it's suppose to do
Not very readable at first sight. Let's decompose:
First, def compose(*fns):
means that the compose function will receive an unknown number of arguments.
Next, let's decompose the reduce function:
reduce(
lambda acc, fn: lambda *args: acc(fn(*args)),
fns,
lambda _: _
)
As the doc indicates, reduce
takes 3 arguments:
def reduce(function, iterable, initializer=None):
So, in your case: the function
is lambda acc, fn: lambda *args: acc(fn(*args))
, fns
is the iterable
, and it will be initialize with lambda _: _
The initializer
indicates that the arguments of compose
will be functions. lambda _: _
is a "neutral element" for function (the same as "0" for addition or "1" for multiplication). I guess it's there essentially when fns is empty.
Now for the main part:
lambda acc, fn: lambda *args: acc(fn(*args))
this is a function that take two functions acc
and fn
and returns the lambda function lambda *args: acc(fn(*args))
.
Let's take an example:
>>> reduce((lambda acc, fn: acc ** fn), [1, 2, 3, 4])
1
>>> reduce((lambda acc, fn: fn ** acc ), [1, 2, 3, 4])
262144
Here acc and fn are not function, but integers. acc
is the "accumulated/reduced" so far, and fn
is the "next" step.
With functions, it will be the same acc
will be the "called functions" so far, and fn the next function.
So lambda acc, fn: lambda *args: acc(fn(*args))
will return a (lambda) function, that will return acc(fn(the_arguments))
.
reduce(lambda acc, fn: lambda *args: acc(fn(*args)), fns, lambda _: _)
will then returns a function consisting of applying each functions of fns
to its args, with identity by default (lambda _: _
).
Let's take an example:
>>> def square(x):
... return x**2
...
>>> def increment(x):
... return x+1
...
>>> def half(x):
... return x/2
...
>>> compose(square, increment, half)
<function <lambda> at 0x7f5321e13de8>
>>> g=compose(square, increment, half)
>>> g(5)
9
So, g(x) = square(increment(half(x)))
With Kasramvd's example:
compose(max, min)([[2, 4], [3, 5]])
is the same as:
max(min([[2, 4], [3, 5]]))
min([[2, 4], [3, 5]])
will returns [2,4]
and max([2,4])
is 4. Thus compose(max, min)([[2, 4], [3, 5]])=4
lambda
expressions can be difficult to follow, and especially so with this one, which returns a new function, also defined using lambda
.
Here is that same expression, but with some different line spacing:
def compose(*fns):
return reduce(lambda acc, fn: lambda *args: acc(fn(*args)),
fns,
lambda _: _)
Now I'll further expand this by exploding the lambda
s passed to reduce
as a regular def
statements:
def compose_2_fns(f, g):
# take 2 functions and return a new function that calls the first with the
# result of calling the second
def composed(*args):
return f(g(*args))
return composed
def _initial(x):
return x
def compose(*fns):
return reduce(compose_2_fns, fns, _initial)
Recall that reduce
works by giving it a method that takes 2 arguments, a sequence of objects (in this case, a sequence of functions), and an optional initial value.
reduce(reduce_fn, objs, first_obj)
If no initial value is given, reduce will take the first object in the sequence, as if you had called it like:
reduce(reduce_fn, objs[1:], objs[0])
Then the reduce function is called like this:
accumulator = first_obj
for obj in objs:
accumulator = reduce_fn(accumulator, obj)
return accumulator
So what your posted reduce
statement is doing is building up a big function by combining several smaller ones.
functions = (add_1, mult_5, add_3)
resulting_function -> lambda *args: add_1(mult_5(add_3(*args)))
So that:
resulting_function(2) -> (((2 + 3) * 5) + 1) -> 26
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With