2

I am confused by the behavior of the reduce function.

  • In the first example, I get the expected result: (1-1/2) * (1-1/3) = 1/3

    >>> reduce(lambda x, y: (1 - 1.0/x) * (1 - 1.0/y), [2,3])
    0.33333333333333337
    
  • In the second example, I do not get the expected result: (1-1/2) * (1-1/3) * (1-1/5) = 0.2666666

    >>> reduce(lambda x, y: (1 - 1.0/x) * (1 - 1.0/y), [2,3,5])
    -1.5999999999999996
    

Can someone please explain me what I am missing?

4
  • You understand that reduce() operates recursively, correct? Commented Jun 12, 2017 at 19:18
  • stackoverflow.com/questions/9108855/… Commented Jun 12, 2017 at 19:20
  • In your second example you have three inputs. you need an additional Commented Jun 12, 2017 at 19:21
  • but applying reduce on lambda x, y: x * y works on any size of list, right ? Commented Jun 12, 2017 at 19:21

4 Answers 4

11

What you need is a map and reduce:

>>> from functools import reduce
>>> yourlist = [2, 3]
>>> reduce(lambda x, y: x*y, map(lambda x: (1-1/x), yourlist))
0.33333333333333337

>>> yourlist = [2, 3, 5]
>>> reduce(lambda x, y: x*y, map(lambda x: (1-1/x), yourlist))
0.2666666666666667

Because map converts each item to the (1-1/item) and then the reduce multiplies all of them.

Additional remarks:

Instead of the lambda x, y: x * y you could also use the faster operator.mul, for example:

>>> import operator
>>> yourlist = [2, 3, 5]
>>> reduce(operator.mul, map(lambda x: (1-1/x), yourlist))
0.2666666666666667

Thanks @acushner for pointing this out (in the comments).

What went wrong in your function

In this case it's actually quite easy to see what doesn't work, just use a named function and add some prints:

def myfunc(x, y):
    print('x = {}'.format(x))
    print('y = {}'.format(y))
    res = (1 - 1.0/x) * (1 - 1.0/y)
    print('res = {}'.format(res))
    return res

reduce(myfunc, [2, 3])
# x = 2
# y = 3
# res = 0.33333333333333337

reduce(myfunc, [2, 3, 5])
# x = 2
# y = 3
# res = 0.33333333333333337
# x = 0.33333333333333337
# y = 5
# res = -1.5999999999999996

So it uses the last result as "next" x value. That's why it worked for the length-2-list case but for more elements it simply doesn't apply the formula you want.

Alternative

Instead of using map and reduce you could also use a simple for-loop. It's much easier to get them right and most of the times they are more readable (and in some cases it's faster than reduce).

prod = 1
for item in yourlist:
    prod *= 1 - 1 / item
print(prod)

Yes, instead of 1 line it's now 4 lines long but it's easy to understand what is happening (but there might be some edge cases in which that doesn't behave like reduce, for example for empty inputs).

But I generally prefer simple loops over complicated reduce-operations but as always YMMV. :)

Sign up to request clarification or add additional context in comments.

9 Comments

exactly. you could use operator.mul here as well
excellent thank you !! I now understand my mistake ... I need to wait 5 minutes before I can approve this as the answer !
@acushner Thanks for the suggestion. I added it to the answer.
operator.mul won't actually be any faster; it's just defined and available for import from the standard library.
@MSeifert Thank you! I've been quite disappointed since I first looked at operator.py a few months ago, since I always thought it was exposing built-in functions. I never saw the import _operator at the bottom, expecting to have seen it near the top instead. (Having it at the bottom makes sense, now that I give it a moment's thought.)
|
2

EDIT: I approve map/reduce answer above.

To understand why, read this:

https://docs.python.org/2/library/functions.html#reduce

Reduce recursively calls your function on each element of your list with 2 arguments: an accumulator (value of last call) and current element.

So you get:

(1 - 1.0/( (1 - 1.0/2) * (1 - 1.0/3) )) * (1 - 1.0/5)

With:

reduce(lambda acc, x: (1 - 1.0/acc) * (1 - 1.0/x), [2,3,5])
>>-1.5999999999999996

Comments

2

To add to why you get -1.5999999999999996 as your result and for completeness we can compute it using https://docs.python.org/2/library/functions.html#reduce as our guide:

The first iteration will be (which takes our first 2 iterator values of 2 and 3 as x and y):

(1 - 1.0 / 2) * (1 - 1.0 / 3)

which becomes:

0.5 * 0.6666666666666667

which yields:

0.33333333333333337.

We then use 0.33333333333333337 to move on to our next iteration which takes this result as x and our next iteration number of 5 as y:

Therefore, our second iteration will be:

(1 - 1.0 / 0.33333333333333337) * (1 - 1.0/5)

which becomes:

-1.9999999999999996 * 0.8

which yields:

-1.5999999999999996

Comments

-1

In your second example you have 3 inputs. You need:

reduce(lambda x, y: (1 - 1.0/x) * (1 - 1.0/y)* (1 - 1.0/z),...

1 Comment

Why? The python docs page has 5 inputs, where did z come from? docs.python.org/2/library/functions.html#reduce - The left argument, x, is the accumulated value and the right argument, y, is the update value from the iterable

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.