Viktor Grafskiy
Viktor Grafskiy

Reputation: 31

Why is my lambda expression for reduce not working as expected?

>>> import functools
>>> functools.reduce(lambda acc, val: acc + 1 if val == ' ' else 0, list("test test test"), 0)
0

I just want to count whitespaces in the text so I expected the function to return 2 (because there are two whitespaces) not 0.

Upvotes: 1

Views: 296

Answers (2)

MSeifert
MSeifert

Reputation: 152775

An easy way to debug lambdas is to use a print() or (original lambda content). That works because print always returns None and therefore Python will always execute the part after the or.

Applying that to your case:

import functools
functools.reduce(lambda acc, val: print(acc, val) or (acc + 1 if val == ' ' else 0), list("test test test"), 0)

which prints:

0 t
0 e
0 s
0 t
0  
1 t
0 e
0 s
0 t
0  
1 t
0 e
0 s
0 t
0

This gives an explanation what went wrong: You don't keep the accumulator in case it's not a whitespace.

There are several ways to fix this. I'll keep the print for debugging reasons but if you really want to use any of those you probably should remove it.

One would be to use the fact that booleans behave like integers if used in arithmetic operations:

functools.reduce(lambda acc, val: print(acc, val) or (acc + (val == ' ')), list("test test test"), 0)

This adds 1 (True) to the accumulator whenever it encounters a whitespace otherwise it adds 0 (False).

Or the trivial solution to just keep the acc:

functools.reduce(lambda acc, val: print(acc, val) or (acc + 1 if val == ' ' else acc), list("test test test"), 0)

But there are much better ways to count whitespaces than using reduce and lambda. For example:

"test test test".count(" ")

which also works for your list:

list("test test test").count(" ")  # but that's slower

or:

from collections import Counter
cnts = Counter("test test test")  # counts all letters
cnts[" "]

Upvotes: 1

bereal
bereal

Reputation: 34290

The expression:

acc + 1 if val == ' ' else 0

is parsed as

(acc + 1) if val == ' ' else 0

so, the accumulator is reset every time it encounters anything but space. So it should be either:

acc + 1 if val == ' ' else acc

or

acc + (1 if val == ' ' else 0)

or even just:

acc + (val == ' ')

But, of course, str.count() is the way to go there.

Upvotes: 1

Related Questions