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?

4
  • 1
    Have you tried using paranthesis in the calculation: reduce(lambda x,y: (1/x) * (1/y), f) ? Commented Jun 21, 2018 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. Commented Jun 21, 2018 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)). Commented Jun 22, 2018 at 0: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.) Commented Jun 22, 2018 at 0:10

1 Answer 1

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
Sign up to request clarification or add additional context in comments.

4 Comments

Why not just multiply all the numbers and do the reciprocal as the last step? Should have better accuracy.
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.
@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 Fractions is useful, but in this case, 1 / reduce(operator.mul, f) would almost certainly be the fastest, simplest possible solution.
Thank you Mr. ShadowRanger. I completely understand how to use Reduce!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.