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?
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)
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
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?
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)
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