7
\$\begingroup\$

I've used encountered this exercise from a tutorial:

Create a function that takes a string as a parameter and returns True if the initial word of the string passed is an article (“The”, “A”, “An”) and returns False if it isn't an article.

Be careful, if the string starts with “There”, it does not start with an article.

I have written the following code for it:

def startsWithArticle(str):
    '''Return True if the first word of str is "The", "A" or "An"'''

    if(str.startswith("The") and str.find(' ')==3):
        return True
    elif(str.startswith("An") and str.find(' ')==2):
        return True
    elif(str.startswith("A") and str.find(' ')==1):
        return True

    return False

which works as expected. I tested it using:

print('"A boy" returned', startsWithArticle("A boy"))
print('"An apple" returned', startsWithArticle("An apple"))
print('"The cat" returned', startsWithArticle("The cat"))
print('"Abyss" returned', startsWithArticle("Abyss"))
print('"There" returned', startsWithArticle("There"))
print('"Ant" returned', startsWithArticle("Ant"))

and this gave the expected output:

"A boy" returned True
"An apple" returned True
"The cat" returned True
"Abyss" returned False
"There" returned False
"Ant" returned False

Is there anything to improve in the function? Do note that I'm a beginner in python. BTW, I'm using python 3.4.3

\$\endgroup\$
  • \$\begingroup\$ Your tests are optimistic in the sense that you only tested outcomes for the code you wrote (space or not space). What about the single letter "A": should it really return False? What about "The-a-tri-cal-i-za-tion!, that's how it is spelled!" What about lower case? (after a semi-colon, you don't capitalize). What if you have a Tab or Newline instead of a space? \$\endgroup\$ – coredump May 27 '15 at 12:42
  • \$\begingroup\$ @coredump , Good points. But I assume that the user will send a valid sentence to the function. \$\endgroup\$ – Spikatrix May 27 '15 at 12:58
  • \$\begingroup\$ "A... valid sentence?", "Yes! A. Valid. Sentence!". Okay, I might be splitting hairs here, but honestly, anytime you encounter problems with natural language, you should be very careful about your assumptions. It might be okay to miss corner cases, though, depending on your task. \$\endgroup\$ – coredump May 27 '15 at 13:15
  • \$\begingroup\$ Just to add an example, "A. A. Milne was an English author." is an example of a valid sentence (I hope) for which the function should return False. \$\endgroup\$ – coredump May 27 '15 at 13:29
12
\$\begingroup\$

Style

Python has a style guide called PEP8 which is definitly worth a read.

A few things can be improved :

  • function name does not respect convention

  • missing whitespaces

  • useless parenthesis

  • name str hides builtin str

Then your code becomes :

def starts_with_article(string):
    '''Return True if the first word of string is "The", "A" or "An"'''

    if string.startswith("The ") and string.find(' ') == 3:
        return True
    elif string.startswith("An ") and string.find(' ') == 2:
        return True
    elif string.startswith("A ") and string.find(' ') == 1:
        return True

    return False

print('"A boy" returned', starts_with_article("A boy"))
print('"An apple" returned', starts_with_article("An apple"))
print('"The cat" returned', starts_with_article("The cat"))
print('"Abyss" returned', starts_with_article("Abyss"))
print('"There" returned', starts_with_article("There"))
print('"Ant" returned', starts_with_article("Ant"))

Don't Repeat Yourself

You are doing the same thing multiple times. You could try to see what is similar and what changes and see if you can use a relevant data structures for the changing parts.

Here I stored the articles in a list to be able to iterate over them.

def starts_with_article(string):
    '''Return True if the first word of string is "The", "A" or "An"'''
    for article in ["The ", "An ", "A "]:
        if string.startswith(article) and string.find(' ') == len(article) - 1:
            return True

    return False

Don't repeat yourself (again)

You are looking for the article followed by a space and then you check if there is a space in the wanted position. There is no need for that check.

def starts_with_article(string):
    '''Return True if the first word of string is "The", "A" or "An"'''
    for article in ["The ", "An ", "A "]:
        if string.startswith(article):
            return True

    return False

Don't repeat yourself (again again)

I have to repeat the whitespace in the different elements of the list I a using. This is easy to forget if I ever need to add an article. Let's add the space as part of the logic.

def starts_with_article(string):
    '''Return True if the first word of string is "The", "A" or "An"'''
    for article in ["The", "An", "A"]:
        if string.startswith(article + ' '):
            return True
    return False

More Python good stuff

The any builtin seems to do what you are doing. Let's use it.

def starts_with_article(string):
    '''Return True if the first word of string is "The", "A" or "An"'''
    return any(string.startswith(article + ' ')
        for article in ["The", "An", "A"])

The best option: reading the documentation

If we look at the startswith documentation, we have:

prefix can also be a tuple of prefixes to look for.

At that point, your problem is pretty much solved :-)

\$\endgroup\$
  • \$\begingroup\$ Great! "useless parenthesis" was because I usually program in C and Java where parenthesis are required. "name str hides builtin str" I didn't know that there was a class named str. And what's "(bis)" and "(ter)" after "Don't repeat yourself"? Thanks for reviewing my code! Shouldn't return any(string.startswith(article + ' ') be return any(string.startswith(article + ' '))? \$\endgroup\$ – Spikatrix May 27 '15 at 8:39
  • \$\begingroup\$ Oops, bis and ter are apparently not used in English (but they are in French, my native language). As for the return, you need the line after in the parenthesis too. \$\endgroup\$ – SylvainD May 27 '15 at 8:56
  • \$\begingroup\$ oh ok. I didn't notice that line. Great answer,BTW! :D \$\endgroup\$ – Spikatrix May 27 '15 at 9:07
  • 5
    \$\begingroup\$ Did you just repeat "Don't repeat yourself" three times? How DRY is that? :-) \$\endgroup\$ – coredump May 27 '15 at 18:52
3
\$\begingroup\$

Although it's not always suitable, this seems like the sort of thing that a regular expression could easily be used for. You can include a word boundary '\b' to distinguish e.g. 'There' and 'The', 'An' and 'Any', etc.

import re

ARTICLE = re.compile(r'''
    ^            # starts at the beginning of the string
    (?:The|An|A) # is either The, An or A
    \b           # followed by a word boundary
''', re.VERBOSE)

def starts_with_article(string):
    return bool(ARTICLE.match(string))

(see demo). In use:

>>> for test_str in ("A boy", "An apple", "The cat", "Abyss", "There", "Ant"):
    print(repr(test_str), starts_with_article(test_str))


'A boy' True
'An apple' True
'The cat' True
'Abyss' False
'There' False
'Ant' False
\$\endgroup\$
  • \$\begingroup\$ I am not familiar with regex and stuff. It is too confusing. I'll look into it later. And shouldn't the print have parenthesis as I'm using python3 ? \$\endgroup\$ – Spikatrix May 27 '15 at 11:12
  • \$\begingroup\$ @CoolGuy regular expressions do look like an explosion in a letters factory, but once you get to grips with them they make sense! And yes; I was testing in 2.x, but I've added the parentheses. \$\endgroup\$ – jonrsharpe May 27 '15 at 11:15
  • \$\begingroup\$ This is a case where regular expressions could be used, but I would argue against there use here. Regular expressions exist to tackle complex problems and are thus themselves complex and prone to errors. The op is looking to solve a simple task. (Checking if a string starts with one of three string literals). So let's Keep it Simple Stupid :) (KISS). \$\endgroup\$ – Roy T. May 27 '15 at 12:48
3
\$\begingroup\$

You can combine the tests that you've written with the documentation of your function by writing them in the form of code examples as part of the docstring. They can be automatically executed and evaluated by the doctest module.

So this

def startsWithArticle(str):
    '''Return True if the first word of str is "The", "A" or "An"'''
    # implementation omitted

print('"A boy" returned', startsWithArticle("A boy"))
print('"An apple" returned', startsWithArticle("An apple"))
print('"The cat" returned', startsWithArticle("The cat"))
print('"Abyss" returned', startsWithArticle("Abyss"))
print('"There" returned', startsWithArticle("There"))
print('"Ant" returned', startsWithArticle("Ant"))

becomes

def startsWithArticle(str):
    '''Return True if the first word of str is "The", "A" or "An"

    >>> startsWithArticle("A boy")
    True
    >>> startsWithArticle("An apple")
    True
    >>> startsWithArticle("The cat")
    True
    >>> startsWithArticle("Abyss")
    False
    >>> startsWithArticle("There")
    False
    >>> startsWithArticle("Ant")
    False
    '''
    # implementation omitted

To execute the tests, run the following from the command line:

$ python -m doctest name_of_your_python_file.py

This technique has the benefit that a user of the function sees the code examples when invoking help(startsWithArticle), and the desired behaviour gets verified automatically (in contrast to your original version, where you simply printed the results of different function calls and had to check manually whether they are correct).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.