how lambda works with reduce

岁酱吖の 提交于 2021-01-23 04:52:04

问题


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


回答1:


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 lambdas 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



回答2:


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




回答3:


That reduce is just a function composer. It accepts an iterable of functions and combines them.

One important point about the reduce is that when you pass 2 arguments to your function (in this case acc, fn) based on how you are using them, at first iteration python uses the last two arguments in your iterable instead of them, and in next iterations it uses the result of latest calculation instead of second argument passed to the lamda or any function passed as the constructor to the reduce (in this case fn). Now For a better demonstration here are all the arguments passed to the reduce in distinction:

the function: lambda acc, fn: lambda *args: acc(fn(*args))

  • the use of second function is being able to pass the arguments to the inner function at each iteration.

iterable argument: fns

initial argument: lambda _: _

If initial is present, it is placed before the items of the sequence in the calculation, and serves as a default when the sequence is empty.

As you can see in passed function it calls the inner functions with arguments and passes it to the next function like fog in mathematics which is f(g(x)). But since you can pass an undefined number of arguments to reduce this function can compose multiple function.

Here is an example:

In [10]: compose(max, min)([[2, 4], [3, 5]])
Out[10]: 4

Also note that this is not a pythonic and recommended way of composing the functions at all. Because first of all it's not readable and easily understandable secondly it's using a lot of extra function calls which is not optimum for such task.



来源:https://stackoverflow.com/questions/45503434/how-lambda-works-with-reduce

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!