patternpythonModerate
Program to count vowels
Viewed 0 times
countprogramvowels
Problem
After reading a certain question on the Stack Overflow website, I tried to write a solution to the problem just for fun. I'm, however, left with the nagging feeling there is a beautiful one-liner that can be used instead.
The question's premises:
Create a function that will receive a string. The function will count each vowel in the string, and return a count of all the vowels, whether found or not, each in a tuple pair. Each tuple parir will be stored in a list.
Example:
My attempt:
My code above is not commented, but I'm confident that its brevity will allow me to pass on this.
Questions:
language: Python 3.4
The question's premises:
Create a function that will receive a string. The function will count each vowel in the string, and return a count of all the vowels, whether found or not, each in a tuple pair. Each tuple parir will be stored in a list.
Example:
>>> vowels_finder("This has some vowels")
[('a', 1), ('o', 2), ('u', 0), ('e', 2), ('i', 1)] # tuple pair of each vowel.
>>>My attempt:
def vowels_finder(s):
vowels = {'a':0, 'e':0, 'i':0, 'o':0, 'u':0}
for el in s:
if el in {'a', 'e', 'i', 'o', 'u'}:
vowels[el]+=1
vowels = [(key, pair) for key, pair in vowels.items()]
return vowelsMy code above is not commented, but I'm confident that its brevity will allow me to pass on this.
Questions:
- Is there _any way, besides using a python library, that this can be condensed into a one-liner?
- Would there be a way to not have to convert the
vowelskey's back into tuple pairs, and just have them be tuples in the beginning. eg:vowels = [('a', 0), ('e', 0), ('i', 0), ('o', 0), ('u', 0)]?
language: Python 3.4
Solution
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:
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:
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
Now we can simply do this in your
Main function improvements
I would expect a function named
Let's call it
Bug / overlooked problem
After refactoring this I noticed a problem, see for illustration:
Results:
This could of course cause problems, but thankfully the fix is extremely simple, just use
Here in the new helper function...
And also when adding it into the dict, so that uppercase vowels get grouped into the lowercase count:
OrderedDict to keep vowels in order
As you've noticed, using a regular
By this a bit more verbose, but much nicer output:
See:
Finally, here is a working demo on repl.it with all the above suggestions applied.
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]+=1Main 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 functionsHere 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()] += 1OrderedDict 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'] = 0By 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'] = 0See:
[('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.
Code Snippets
def vowels_finder(s: str) -> list:
# ...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')for el in s:
if is_vowel(el):
vowels[el]+=1def 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()]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))Context
StackExchange Code Review Q#144074, answer score: 12
Revisions (0)
No revisions yet.