1

I'm just learning Python, and do not understand the behavior I am getting from my reduce function. I have seen many examples where you can use reduce to perform an equivalent function to sum when you want to multiply:

f = [2,3,4]
reduce(lambda x,y: x*y,f)

That gives me the value I expect. But I need to multiply by all the reciprocals instead. I thought I could do this:

reduce(lambda x,y: 1/x * 1/y, f)

But it comes out as 1.5 instead of some much smaller decimal answer. What am I doing wrong?

Sean Mahoney
  • 131
  • 7
  • 1
    Have you tried using paranthesis in the calculation: ```reduce(lambda x,y: (1/x) * (1/y), f)``` ? – Lukas Bach Jun 21 '18 at 23:52
  • @LukasBach: As it happens, in this case, the parentheses are (mostly) irrelevant (only mostly because order of operations slightly alters the result of floating point math). Even without the parentheses, it would become `((1 / x) * 1) / y` which is logically equivalent. The problem is in the meaning of `x`. – ShadowRanger Jun 21 '18 at 23:58
  • The "take the reciprocal" part is much more appropriate for `map`, rather than folding it into the `reduce`: `reduce(lambda x, y: x*y, map(lambda x: 1/x, f))`. – user2357112 Jun 22 '18 at 00:08
  • 1
    (Of course, using `reduce`, `map`, or `lambda` at all isn't really encouraged in Python the way it would be in functional languages, and multiplying all the reciprocals together is equivalent to multiplying all the numbers and taking the reciprocal once at the end anyway, which would be easier.) – user2357112 Jun 22 '18 at 00:10

1 Answers1

5

The x on each call is the result of the last call (it's only one of the direct inputs on the very first invocation), so doing 1 / x each time takes the reciprocal of the previous result. To fix, you need to change the lambda to only multiply in the reciprocal of the new number, not the accumulated value. You also need to provide an initial neutral value (1) so that the first value in f has its reciprocal taken properly (otherwise, it would be the plain value of f[0] multiplied by the reciprocals of f[1:]):

# x is accumulated product of reciprocals to date, *DON'T* take reciprocal again
reduce(lambda x, y: x * (1 / y), f, 1)
                                  # ^ multiplicative identity is correct neutral value here

That said, you can simplify a little more; x * (1 / y) is (roughly, given floating point precision issues) equivalent to x / y, so you could simplify further to:

reduce(lambda x, y: x / y, f, 1)

or using the operator module to push all the work to the C layer (only important if f might be really big):

import operator
reduce(operator.truediv, f, 1)

Either way, this gets the expected result:

>>> (1/2) * (1/3) * (1/4)
0.041666666666666664
>>> reduce(lambda x,y: x * (1 / y), f, 1)
0.041666666666666664
>>> reduce(lambda x,y: x / y, f, 1)
0.041666666666666664
>>> reduce(operator.truediv, f, 1)
0.041666666666666664

As noted in the comments below, computing individual reciprocals and multiplying them all together is slower and more prone to error (especially when all inputs are int) than just computing the product of all the values in f, then computing the reciprocal of that product once, at the very end. On Python 3.8+, with math.prod, this is as simple as:

>>> 1 / math.prod(f)
0.041666666666666664

On older versions of Python, you have to make your own product-computing function, but it's easy to do using reduce+operator.mul:

>>> 1 / reduce(operator.mul, f)
0.041666666666666664
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • 3
    Why not just multiply all the numbers and do the reciprocal as the last step? Should have better accuracy. – kindall Jun 22 '18 at 00:08
  • Tangent: If `f` is big, you're likely going to end up suffering from floating point imprecision. A trick for avoiding this would be to convert operands to `fractions.Fraction`; if all your operands are integers (lossless), the `Fraction` form is too, so you can get a result as a `Fraction` with no loss of precision, or only convert to `float` at the very end, limiting the precision loss to a single operation. `reduce(truediv, map(Fraction, f), 1)` produces `Fraction(1, 24)`, and `float(reduce(truediv, map(Fraction, f), 1))` produces `0.041666666666666664` having only performed rounding once. – ShadowRanger Jun 22 '18 at 00:10
  • 1
    @kindall: Heh, good point! I was typing up a comment with the same net result, but yeah, sticking with plain integers would be faster. Knowing the `Fractions` approach might be useful for other, more complicated scenarios where the lossless aspect of `Fraction`s is useful, but in this case, `1 / reduce(operator.mul, f)` would almost certainly be the fastest, simplest possible solution. – ShadowRanger Jun 22 '18 at 00:11
  • Thank you Mr. ShadowRanger. I completely understand how to use Reduce! – Sean Mahoney Jun 22 '18 at 22:17