How can I reduce an operation with a subscript?

Question:

expenses = [
    ('Dinner', 80),
    ('Car repair', 120),
    ('csgo skins', 200)
]

# Version with for-loop
sum = 0
for expense in expenses:
    sum += expense[1]
print(sum)

# Version using lambda+reduce
sum2 = reduce((lambda a, b: a[1] + b[1]), expenses)
print(sum2)

I’m trying to obtain, with lambda and reduce, the same result that I had with the for-loop version.

If the expenses list contains only 2 items, everything works fine, but if I add more I get this error:

Traceback (most recent call last):
  File "r:Developing_Pythonlambda_map_filter_reduce.py", line 44, in <module>
    sum2 = reduce((lambda a, b: a[1] + b[1]), expenses)
  File "r:Developing_Pythonlambda_map_filter_reduce.py", line 44, in <lambda>
    sum2 = reduce((lambda a, b: a[1] + b[1]), expenses)
TypeError: 'int' object is not subscriptable

What is the correct way of doing this?

Asked By: Ceccoclat

||

Answers:

Recall that reduce calls itself with the result from the previous reduce operation as its first argument. So a[1] no longer makes sense because on the second and subsequent iterations, a is the sum returned from the previous iteration.

You can painfully get around this with an explicit special case, of course; or, you can pass in a sequence which removes this obstacle:

from operator import itemgetter
...
sum2 = reduce(
   lambda a, b: a + b,
   map(itemgetter(1), expenses))
print(sum2)

itemgetter is a convenience function for fetching an item at a particular subscript. So, we are basically passing x[1] for x in expenses to reduce instead of the original list.

Chaining transformations like this is one of the cornerstones of functional programming. The paradigm gently nudges you to rethink your problem into simple operations which fit together elegantly.

Demo: https://ideone.com/KnqksZ

As pointed out by @deceze, you could further prettify this;

from operator import add, itemgetter
...
sum2 = reduce(add, map(itemgetter(1), expenses))
print(sum2)
Answered By: tripleee

I modified your reduce + lambda version so that now it is using accumulator as well provided at start, will work for any number of tuples in the list

from functools import reduce

expenses = [
    ('Dinner', 80),
    ('Car repair', 120),
    ('csgo skins', 200)
]

# Version with for-loop
sum = 0
for expense in expenses:
    sum += expense[1]
print(sum)

# Version using lambda+reduce
sum2 = reduce(lambda accumulator, expense: accumulator + expense[1], expenses, 0)
print(sum2)

400 400

Answered By: Zain Ul Abidin
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.