13

I'm trying to write a simple Python function that sums all values that have the key as likes. I'm working with functional programming for this assignment. Thus, I am required to use either a list-comprehension, map, filter, or reduce. In this case, I see reduce as a reasonable option.

def sum_favorites(msgs):
     num_favorites = reduce(lambda x, y: x["likes"] + y["likes"], msgs)
     return num_favorites


content1 = {"likes": 32, ...}
content2 = {"likes": 8, ...}
content3 = {"likes": 16, ...}
contents = [content1, content2, content3]
print(sum_favorites(contents)) 

The issue comes to when I actually run the code. I seem to receive something along the lines of: TypeError: 'int' object is not subscriptable. To me, this error makes no sense. If reduce is truly iterating through the given parameter, then each item passed into the lambda-function should be a dictionary - and each of them definitely has a likes key in them. What is the issue, and what exactly does this Python error mean?

elena
  • 3,740
  • 5
  • 27
  • 38
Veer Singh
  • 913
  • 2
  • 11
  • 26

5 Answers5

25

To me, this error makes no sense. If reduce is truly iterating through the given parameter, then each item passed into the lambda-function should be a dictionary

No, the first parameter passed to the lambda (for all calls except the first) is the return value from the previous call to the lambda. Your function returns a number, so it will be called with x being a number, not a dictionary.

There are two ways to deal with this. The probably more straightforward one is:

num_favorites = reduce(lambda x, y: x + y['likes'], msgs, 0)

The 0 is the "initializer" argument to reduce, which provides the first value for x. Now in each call, x is the running sum and y is the next dictionary.

Another way, just to show that it can be done, is:

result = reduce(lambda x, y: { 'likes': x['likes'] + y['likes'] }, msgs)
num_favorites = result['likes']

which makes the return value of the lambda be a dict with a likes key, just like its arguments, so we're working with the same type the whole way through. In this case it's unnecessary and wasteful, but if you were aggregating more than one key, it might be an interesting approach.

hobbs
  • 223,387
  • 19
  • 210
  • 288
6

In your code snippet reduce(lambda x, y: x["likes"] + y["likes"], msgs), x variable at first is the first element of list msgs (dict), but at the second iteration it'll be a sum of "likes" (int).

So, to sum the likes use the initializer argument of reduce function doc.

def sum_favorites(msgs):
    num_favorites = reduce(lambda x, y: x + y["likes"], msgs, 0)
    return num_favorites

But I believe, using sum is a more pythonic way:

def sum_favorites(msgs):
    num_favorites = sum(i['likes'] for i in msgs)
    return num_favorites
elena
  • 3,740
  • 5
  • 27
  • 38
Eugene Oskin
  • 791
  • 8
  • 14
4

Consider how reduce is working. x is the accumulator variable that is set to the return value of the previous function.

1st iteration

lambda x, y: x["likes"] + y["likes"] = 40

2nd iteration

lambda 40, y: 40["likes"] + y["likes"] = ???
icc97
  • 11,395
  • 8
  • 76
  • 90
3

Depending on how exactly reduce is implemented, on your second iteration of reduce one operand passed to your lambda will not be a dict but the sum computed so far. Which gives you the error you are seeing.

To avoid this you could first do a list or generator comprehension to extract all the values at "likes" from the various dicts and then reduce operator.add on those. Or just use sum.

Paul Panzer
  • 51,835
  • 3
  • 54
  • 99
0

After reading Paul's response, I realized that to make sure that consistent data-types were always being summed up, I had to initiate reduce to use an int for 'x' and a dictionary for 'y'. Thus,

def average_favorites(tweets):
    num_favorites = reduce(lambda x, y: x + y["favorites"], tweets, 0)
    return num_favorites
Veer Singh
  • 913
  • 2
  • 11
  • 26