helpermethod
helpermethod

Reputation: 62304

List comprehension and len() vs. simple for loop

I'm supposed to take a list of words and count all words in it which are 2 or more characters long and where the first and last character are equal.

I came up with two possible solutions:

result = 0
for word in words:
    if len(word) >= 2 and word[0] == word[-1]:
        result += 1
return result

vs.

return len([word for word in words if len(word) >= 2 and word[0] == word[-1]])

Which one would be the preferred solution? Or are there even better ones?

Upvotes: 7

Views: 6210

Answers (5)

Glenn Maynard
Glenn Maynard

Reputation: 57554

Some other variants you might want to consider:

First, you can break the filter condition into a function. This condition is fine either way, but if it becomes any more complex I'd definitely do this:

def check(word):
    return len(word) >= 2 and word[0] == word[-1]
sum(1 for word in words if check(word))

Next, if generating a list (as in the original list comprehension) is acceptable, then you can do this:

len(filter(check, words))

There's itertools.ifilter, but if you use that you need to use the sum expression again, so it doesn't end up any clearer.

The sum trick comes up so often that I'm surprised there isn't a standard library call to count the number of items in an iterator (if there is, I havn't found it). Alternatively, it'd make sense if len would consume and count the number of entries in an iterator if it has no __len__, but it doesn't.

Upvotes: 1

mechanical_meat
mechanical_meat

Reputation: 169534

In your second example a generator expression would be better than list-comp if your list is large.

sum(1 for word in words if len(word) >= 2 and word[0] == word[-1])

Upvotes: 17

Martin v. Löwis
Martin v. Löwis

Reputation: 127587

I personally find the explicit loop more readable, but it's much a matter of taste (some prefer shorter code generally, especially when they have to write it).

Either version can be further shortened/improved:

result = 0
for word in words:
    result += int(len(word) >= 2 and word[0] == word[-1])
return result

The int() conversions is strictly speaking unnecessary, since True is a kind of 1, but it may be better for readability. The same approach can apply to the comprehension:

return sum(len(word) >= 2 and word[0] == word[-1] for word in words)

If you want to use len(), I'd point the reader to the fact that the values don't really matter:

len(1 for word in words if len(word) >= 2 and word[0] == word[-1])

Upvotes: 2

jacrough
jacrough

Reputation: 168

The first one would definitely be the preferred solution in Python.

Don't forget your Zen of Python:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.

Explicit is better than implicit.

Simple is better than complex.

Complex is better than complicated.

Flat is better than nested.

Sparse is better than dense.

Readability counts.

Special cases aren't special enough to break the rules.

Although practicality beats purity.

Errors should never pass silently.

Unless explicitly silenced.

In the face of ambiguity, refuse the temptation to guess.

There should be one-- and preferably only one --obvious way to do it.

Although that way may not be obvious at first unless you're Dutch.

Now is better than never.

Although never is often better than right now.

If the implementation is hard to explain, it's a bad idea.

If the implementation is easy to explain, it may be a good idea.

Namespaces are one honking great idea -- let's do more of those!

Other than that your solutions are good.

Upvotes: 3

pyfunc
pyfunc

Reputation: 66739

Both are pretty good.

There are small differences:

List comprehension returns another list which you are passing to len. The first solution avoids creation of another list.

Upvotes: 1

Related Questions