With Python it can be tempting to want to write one-liners; but, short code does not necessarily make for better code, and I'd like to review your code in a way that I think would make it more maintainable, flexible and professional, rather than shorter. (maybe someone else will address the one-liner request)
Type hints
Since you are using Python 3.x you could take advantage of the new type hints. According to PEP 484:
This PEP aims to provide a standard syntax for type annotations, opening up Python code to easier static analysis and refactoring, potential runtime type checking, and (perhaps, in some contexts) code generation utilizing type information.
Of these goals, static analysis is the most important. This includes support for off-line type checkers such as mypy, as well as providing a standard notation that can be used by IDEs for code completion and refactoring.
Even if you don't use static code analysis at the moment, type hints still have the advantage of making the code easier to read and understand.
In your case:
def vowels_finder(s: str) -> list:
# ...
Reusable functions
The first thought I had looking at your function is that some logic could be extracted for more general reuse. For instance, this function could come in handy for other things:
def is_vowel(ch: chr, include_y: bool=False) -> bool:
if include_y:
return ch in ('a', 'e', 'i', 'o', 'u', 'y')
else:
return ch in ('a', 'e', 'i', 'o', 'u')
You will notice I also added support for optionally including "Y" as a vowel, which can be useful for certain contexts.
Note that I also used a tuple instead of set, since vowels won't change anyways and tuples are generally faster since they are immutable, and we don't need set operations in this case other than in
membership, which tuples support as well.
Now we can simply do this in your vowels_finder
function:
for el in s:
if is_vowel(el):
vowels[el]+=1
Main function improvements
I would expect a function named vowels_finder
to do just that: look for a vowel, and return True
if it finds one. Furthermore, I would expect a name like this to be an object/class "Thing", rather than a function which is usually named like "do something".
Let's call it count_individual_vowels
instead. Also, now that we have a function for vowels with added functionality for "Y", we can very easily add this option to this function. Note that I have changed some of the variable names a bit to make them more clear:
def count_individual_vowels(input_str: str, include_y: bool = False) -> list:
vowel_counts = {'a':0, 'e':0, 'i':0, 'o':0, 'u':0}
if include_y:
vowel_counts['y'] = 0
for el in input_str:
if is_vowel(el, include_y):
vowel_counts[el] += 1
return [(key, pair) for key, pair in vowel_counts.items()]
Bug / overlooked problem
After refactoring this I noticed a problem, see for illustration:
string1 = "My phrase has some vowels, pretty cool don't you think?"
print(count_individual_vowels(string1))
print(count_individual_vowels(string1, True))
string2 = string1.upper()
print('UPPER CASE')
print(count_individual_vowels(string2))
print(count_individual_vowels(string2, True))
Results:
[('u', 1), ('o', 6), ('i', 1), ('e', 4), ('a', 2)]
[('e', 4), ('a', 2), ('u', 1), ('y', 3), ('o', 6), ('i', 1)]
UPPER CASE
[('u', 0), ('o', 0), ('i', 0), ('e', 0), ('a', 0)]
[('e', 0), ('a', 0), ('u', 0), ('y', 0), ('o', 0), ('i', 0)]
This could of course cause problems, but thankfully the fix is extremely simple, just use .lower()
method on strings in the functions
Here in the new helper function...
return ch.lower() in ('a', 'e', 'i', 'o', 'u')
And also when adding it into the dict, so that uppercase vowels get grouped into the lowercase count:
for el in input_str:
if is_vowel(el, include_y):
# here:
vowel_counts[el.lower()] += 1
OrderedDict to keep vowels in order
As you've noticed, using a regular dict
to store the counts results in the output vowels returns the values in arbitrary order. You could make it return always in the same order by using from collections import OrderedDict
and just replacing this:
vowel_counts = {'a':0, 'e':0, 'i':0, 'o':0, 'u':0}
if include_y:
vowel_counts['y'] = 0
By this a bit more verbose, but much nicer output:
vowel_counts = OrderedDict()
for vow in 'a', 'e','i', 'o', 'u':
vowel_counts[vow] = 0
if include_y:
vowel_counts['y'] = 0
See:
[('a', 2), ('e', 4), ('i', 1), ('o', 6), ('u', 1)]
[('a', 2), ('e', 4), ('i', 1), ('o', 6), ('u', 1), ('y', 3)]
Finally, here is a working demo on repl.it with all the above suggestions applied.
return vowels.items()
instead of using that list comprehension. \$\endgroup\$