Reputation: 62304
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
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
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
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
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
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