HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonModerate

Program to count vowels

Submitted by: @import:stackexchange-codereview··
0
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:

>>> 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 vowels


My 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 vowels key'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:

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.

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]+=1
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()]
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.