Exercise Answers
Intro Exercises
These exercises are all in the intro.py file in the exercises directory.
Edit the appropriate function in that file to complete each exercise.
To run the tests, from the exercises folder, type python test.py <function_name>, like this:
$ python test.py has_vowel
Hint
Match objects are always “truthy” and None is always “falsey”. Truthy means when you convert something to a boolean, it’ll be True.
You can convert the result of re.search to a boolean to get True or False for a match or non-match like this:
>>> bool(re.search(r"hello", sentence))
True
>>> bool(re.search(r"hi", sentence))
False
Has Vowels
Create a function has_vowel, that accepts a string and returns True if the string contains a vowel (a, e, i, o, or u) returns False otherwise.
Tip
Modify the has_vowel function in the intro module.
Your function should work like this:
>>> has_vowel("rhythm")
False
>>> has_vowel("exit")
True
Answer
def has_vowel(string):
"""Return True iff the string contains one or more vowels."""
return bool(re.search(r"[aeiou]", string))
Is Integer
Create a function is_integer that accepts a string and returns True if the string represents an integer.
By our definition, an integer:
Consists of 1 or more digits
May optionally begin with
-Does not contain any other non-digit characters.
Tip
Modify the is_integer function in the intro module.
Your function should work like this:
>>> is_integer("")
False
>>> is_integer(" 5")
False
>>> is_integer("5000")
True
>>> is_integer("-999")
True
>>> is_integer("+999")
False
>>> is_integer("00")
True
>>> is_integer("0.0")
False
Answer
def is_integer(string):
"""Return True if the string represents a valid integer."""
return bool(re.search(r"^-?\d+$", string))
Is Fraction
Create a function is_fraction that accepts a string and returns True if the string represents a fraction.
By our definition a fraction consists of:
An optional
-characterFollowed by 1 or more digits
Followed by a
/Followed by 1 or more digits, at least one of which is non-zero (the denominator cannot be the number
0).
Tip
Modify the is_fraction function in the intro module.
Your function should work like this:
>>> is_fraction("")
False
>>> is_fraction("5000")
False
>>> is_fraction("-999/1")
True
>>> is_fraction("+999/1")
False
>>> is_fraction("00/1")
True
>>> is_fraction("/5")
False
>>> is_fraction("5/0")
False
>>> is_fraction("5/010")
True
>>> is_fraction("5/105")
True
>>> is_fraction("5 / 1")
False
Answer
def is_fraction(string):
"""Return True iff the string represents a valid fraction."""
return bool(re.search(r'^-?\d+/\d*[1-9]+\d*$', string))
Validation and Search Exercises
These exercises are all in the validation.py file in the exercises directory.
Edit the appropriate function in that file to complete each exercise.
To run the tests, from the exercises folder, type python test.py <function_name>, like this:
$ python test.py has_word
Has Word
Create a function has_word that accepts a single word string and a sentence string and returns True if the sentence contains the word (as a word by itself), or False otherwise.
Tip
Modify the has_word function in the validation module.
Your function should work like this:
>>> has_word('help', 'She was a big help when I learned French')
True
>>> has_word('help', 'She helped me learn French')
False
Answer
def has_word(word, sentence):
"""Return True iff sentence contains word as an individual word."""
return bool(re.search(r'\b' + word + r'\b', sentence, re.IGNORECASE))
Four Letter Words
Create a function get_4_letter_words which accepts a sentence and returns all four letter words from the given sentence.
Tip
Modify the get_4_letter_words function in the validation module.
Your function should work like this:
>>> get_4_letter_words("She was a big help when I learned French")
["help", "when"]
>>> get_4_letter_words('help', 'What is going on here?')
["What", "here"]
Answer
def get_4_letter_words(string):
"""Return all four letter words found in the given string."""
return re.findall(r'\b\w{4}\b', string)
Is Email
Create a function is_email that accepts a string and returns True if the string represents a valid email address.
Tip
Modify the is_email function in the validation module.
Your function should work like this:
>>> is_email('123@example.com')
True
>>> is_email('info123@help.example.com')
True
>>> is_email('help+info@help-example.com')
True
>>> is_email('100%@help-example.com')
True
>>> is_email('123@example.c')
False
>>> is_email('123example.com')
False
Answer
Using the IGNORECASE and VERBOSE flags:
def is_email(string):
"""Return True iff the string represents a valid email"""
return bool(re.search(r"""
^
[A-Z0-9._%+-] +
@
[A-Z0-9.-] +
\.
[A-Z] {2,}
$
""", string, re.IGNORECASE | re.VERBOSE))
Using the \w special sequence too:
def is_email(string):
"""Return True iff the string represents a valid email"""
return bool(re.search(r"""
^
[\w.%+-] +
@
[\w.-] +
[.]
[A-Z] {2,}
$
""", string, re.IGNORECASE | re.VERBOSE))
Is Phone Number
Create a function is_phone_number that accepts a string and returns True if the string represents an valid US-style phone number.
Let’s just concern ourselves with allowing (xxx)yyy-zzzz, (xxx) yyy-zzzz, or xxx-yyy-zzzz.
Tip
Modify the is_phone_number function in the validation module.
Your function should work like this:
>>> is_phone_number('202-762-1401')
True
>>> is_phone_number('(202)762-1401')
True
>>> is_phone_number('(202) 762-1401')
True
>>> is_phone_number('20-2762-1401')
False
>>> is_phone_number('202 762-1401')
True
>>> is_phone_number('2027621401')
True
Answer
def is_phone_number(string):
"""Return True iff the string represents a valid US phone number"""
return bool(re.search(r"""
^
[(]? # optional open parenthesis
\d{3} # area code
\s* [)-.]? \s* # Optional space/sybols (including close paren)
\d{3} # prefix
\s* [-.]? \s* # optional space/sybols
\d{4} # remainder of number
$
""", string, re.VERBOSE))
Get Email
Create a function get_email that accepts a string, searches for an email address and returns the email address from the string. If there is no valid email, it should return None.
Tip
Modify the get_email function in the validation module.
Your function should work like this:
>>> get_email('Send an email to info@example.com for information')
'info@example.com'
>>> get_email('Do not use email of info@example.c.')
>>> get_email('Help is available at info123@help.example.com.')
'info123@help.example.com'
Answer
def get_email(string):
"""Search the string for a valid email and return it; else return None."""
match = re.search(r"""
\b
[\w.%+-] +
@
[\w.-] +
[.]
[A-Z] {2,}
\b
""", string, re.IGNORECASE | re.VERBOSE)
if match:
return match.group()
else:
return None
More Search Exercises
These exercises are all in the grouping.py file in the exercises directory.
Edit the appropriate function in that file to complete each exercise.
To run the tests, from the exercises folder, type python test.py <function_name>, like this:
$ python test.py get_extension
Note
Most of these exercises involves searching in a dictionary.
You can find the contents of this dictionary file in the dictionary variable within the grouping module.
Get File Extension
Make a function that accepts a full file path and returns the file extension.
Tip
Modify the get_extension function in the grouping module.
Example usage:
>>> get_extension('archive.zip')
'zip'
>>> get_extension('image.jpeg')
'jpeg'
>>> get_extension('index.xhtml')
'xhtml'
>>> get_extension('archive.tar.gz')
'gz'
Answers
Works with examples given:
def get_extension(filename):
return re.search(r"([^.]*)$", filename).group()
Works with no extension:
def get_extension(filename):
match = re.search(r"\.([^.]*)$", filename)
return match.group(1) if match else ""
Works with only word-based extensions (try a.b/c):
def get_extension(filename):
match = re.search(r"\.(?!.*\W)([^.]*)$", filename)
return match.group(1) if match else ""
Hexadecimal Words
Find every word that consists solely of the letters A, B, C, D, E, and F.
The input is a variable containing all the words in the file dictionary.txt.
Tip
Modify the hexadecimal function in the grouping module.
Examples: decaf, bead, cab
>>> hexadecimal(dictionary) ['abbe', 'abed', 'accede', 'acceded', 'ace', 'aced', 'ad', 'add', 'added', 'baa', 'baaed', 'babe', 'bad', 'bade', 'be', 'bead', 'beaded', 'bed', 'bedded', 'bee', 'beef', 'beefed', 'cab', 'cabbed', 'cad', 'cafe', 'ceca', 'cede', 'ceded', 'dab', 'dabbed', 'dace', 'dad', 'dead', 'deaf', 'deb', 'decade', 'decaf', 'deed', 'deeded', 'def', 'deface', 'defaced', 'ebb', 'ebbed', 'ed', 'efface', 'effaced', 'fa', 'facade', 'face', 'faced', 'fad', 'fade', 'faded', 'fed', 'fee', 'feed']
Answers
def hexadecimal(dictionary=dictionary):
"""Return a list of all words consisting solely of the letters A to F."""
return re.findall(r"\b[a-f]+\b", dictionary)
Tetravocalic
Find all words that include four consecutive vowels.
The input is a variable containing all the words in the file dictionary.txt.
Tip
Modify the tetravocalic function in the grouping module.
>>> tetravocalic(dictionary)
['aqueous', 'aqueously', 'archaeoastronomies', 'archaeoastronomy', 'assegaaied', 'assegaaiing', 'banlieue', 'banlieues', 'beauish', 'bioaeration', 'bioaerations', 'bioaeronautics', 'blooie', 'booai', 'booais', 'braaied', 'braaiing', 'camaieu', 'camaieux', 'cooee', 'cooeed', 'cooeeing', 'cooees', 'dequeue', 'dequeued', 'dequeueing', 'dequeues', 'dequeuing', 'enqueue', 'enqueued', 'enqueueing', 'enqueues', 'enqueuing', 'epigaeous', 'epopoeia', 'epopoeias', 'euoi', 'euouae', 'euouaes', 'flooie', 'forhooie', 'forhooied', 'forhooieing', 'forhooies', 'giaour', 'giaours', 'gooier', 'gooiest', 'guaiac', 'guaiacol', 'guaiacols', 'guaiacs', 'guaiacum', 'guaiacums', 'guaiocum', 'guaiocums', 'homoiousian', 'homoiousians', 'hypoaeolian', 'hypogaeous', 'looie', 'looies', 'louie', 'louies', 'maieutic', 'maieutical', 'maieutics', 'meoued', 'meouing', 'metasequoia', 'metasequoias', 'miaou', 'miaoued', 'miaouing', 'miaous', 'mythopoeia', 'mythopoeias', 'nonaqueous', 'obsequious', 'obsequiously', 'obsequiousness', 'obsequiousnesses', 'onomatopoeia', 'onomatopoeias', 'palaeoanthropic', 'palaeoecologic', 'palaeoecologies', 'palaeoecologist', 'palaeoecology', 'palaeoethnology', 'pharmacopoeia', 'pharmacopoeial', 'pharmacopoeian', 'pharmacopoeias', 'plateaued', 'plateauing', 'prosopopoeia', 'prosopopoeial', 'prosopopoeias', 'queue', 'queued', 'queueing', 'queueings', 'queuer', 'queuers', 'queues', 'queuing', 'queuings', 'radioautograph', 'radioautographic', 'radioautographies', 'radioautographs', 'radioautography', 'radioiodine', 'radioiodines', 'reliquiae', 'rhythmopoeia', 'rhythmopoeias', 'saouari', 'saouaris', 'scarabaeoid', 'scarabaeoids', 'sequoia', 'sequoias', 'subaqueous', 'tenuious', 'terraqueous', 'toeier', 'toeiest', 'zoaea', 'zoaeae', 'zoaeas', 'zoeae', 'zooea', 'zooeae', 'zooeal', 'zooeas', 'zoogloeae', 'zoogloeoid', 'zooier', 'zooiest']
Answers
def tetravocalic(dictionary=dictionary):
"""Return a list of all words that have four consecutive vowels."""
return re.findall(r"\b[a-z]*[aeiou]{4}[a-z]*\b", dictionary)
Hexaconsonantal
Find at least one word with 6 consecutive consonants. For this problem treat y as a vowel.
The input is a variable containing all the words in the file dictionary.txt.
Tip
Modify the hexaconsonantal function in the grouping module.
>>> re.findall(r"\b.*[^aeiouy\s]{6}.*\b", dictionary)
['bergschrund', 'bergschrunds', 'borschts', 'catchphrase', 'catchphrases', 'crwths', 'eschscholtzia', 'eschscholtzias', 'eschscholzia', 'eschscholzias', 'festschrift', 'festschriften', 'festschrifts', 'grrrls', 'latchstring', 'latchstrings', 'lengthsman', 'lengthsmen', 'sightscreen', 'sightscreens', 'tsktsk', 'tsktsked', 'tsktsking', 'tsktsks', 'watchspring', 'watchsprings', 'watchstrap', 'watchstraps', 'weltschmerz', 'weltschmerzes']
>>> re.findall(r'\b.*[bcdfghjklmnpqrstvwxz]{6}.*\b', dictionary)
['bergschrund', 'bergschrunds', 'borschts', 'catchphrase', 'catchphrases', 'crwths', 'eschscholtzia', 'eschscholtzias', 'eschscholzia', 'eschscholzias', 'festschrift', 'festschriften', 'festschrifts', 'grrrls', 'latchstring', 'latchstrings', 'lengthsman', 'lengthsmen', 'sightscreen', 'sightscreens', 'tsktsk', 'tsktsked', 'tsktsking', 'tsktsks', 'watchspring', 'watchsprings', 'watchstrap', 'watchstraps', 'weltschmerz', 'weltschmerzes']
Answers
def hexaconsonantal(dictionary=dictionary):
"""Return a list of all words with six consecutive consonants."""
return re.findall(r"\b.*[^aeiouy\s]{6}.*\b", dictionary)
Crossword Helper
Make a function possible_words that accepts a partial word with underscores representing missing letters and returns a list of all possible matches.
Tip
Modify the possible_words function in the grouping module.
Use your crossword helper function to solve the following:
water tank: CIS____
pastry: ___TE
temporary: __A_S_E__
Answers
def possible_words(partial_word):
pattern = rf"\b{partial_word.replace('_', '.')}\b"
return re.findall(pattern, dictionary, re.IGNORECASE)
Repeat Letter
Find every word with 5 repeat letters.
The input is a variable containing all the words in the file dictionary.txt.
Tip
Modify the five_repeats function in the grouping module.
>>> five_repeats(letter, dictionary)
['inconveniencing', 'nondenominational', 'nonindependent', 'nonintervention', 'noninterventions']
Answers
def five_repeats(letter, dictionary=dictionary):
"""Return all words with at least five occurrences of the given letter."""
return re.findall(rf"\b(?:.*{letter}.*){{5}}\b", dictionary)
Substitution Exercises
These exercises are all in the substitution.py file in the exercises directory.
Edit the appropriate function in that file to complete each exercise.
To run the tests, from the exercises folder, type python test.py <function_name>, like this:
$ python test.py normalize_jpeg
Normalize JPEG Extension
Make a function that accepts a JPEG filename and returns a new filename with jpg lowercased without an e.
Tip
Modify the normalize_jpeg function in the substitution module.
Hint
Lookup how to pass flags to the re.sub function.
Example usage:
>>> normalize_jpeg('avatar.jpeg')
'avatar.jpg'
>>> normalize_jpeg('Avatar.JPEG')
'Avatar.jpg'
>>> normalize_jpeg('AVATAR.Jpg')
'AVATAR.jpg'
Answers
def normalize_jpeg(filename):
return re.sub(r"\.jpe?g$", r".jpg", filename, flags=re.IGNORECASE)
Normalize Whitespace
Make a function that replaces all instances of one or more whitespace characters with a single space.
Tip
Modify the normalize_whitespace function in the substitution module.
Example usage:
>>> normalize_whitespace("hello there")
"hello there"
>>> normalize_whitespace("""Hold fast to dreams
... For if dreams die
... Life is a broken-winged bird
... That cannot fly.
...
... Hold fast to dreams
... For when dreams go
... Life is a barren field
... Frozen with snow.""")
'Hold fast to dreams For if dreams die Life is a broken-winged bird That cannot fly. Hold fast to dreams For when dreams go Life is a barren field Frozen with snow.'
Answers
def normalize_whitespace(string):
return re.sub(r"\s+", r" ", string)
Compress blank lines
Write a function that accepts a string and an integer N and compresses runs of N or more consecutive empty lines into just N empty lines.
Tip
Modify the compress_blank_lines function in the substitution module.
Example usage:
>>> compress_blank_lines("a\n\n\nb", max_blanks=1)
'a\n\nb'
>>> compress_blank_lines("a\n\nb", max_blanks=0)
'a\nb'
>>> compress_blank_lines("a\n\nb", max_blanks=2)
'a\n\nb'
>>> compress_blank_lines("a\n\n\n\nb\n\n\nc", max_blanks=2)
'a\n\n\nb\n\n\nc'
Answers
def compress_blank_lines(string, max_blanks):
"""Compress N or more empty lines into just N empty lines."""
max_newlines = max_blanks + 1
regex = r"\n{" + str(max_newlines) + ",}"
return re.sub(regex, "\n" * max_newlines, string)
Normalize URL
I own the domain treyhunner.com. I prefer to link to my website as https://treyhunner.com, but I have some links that use http or use a www subdomain.
Write a function that normalizes all www.treyhunner.com and treyhunner.com links to use HTTPS and remove the www subdomain.
Tip
Modify the normalize_domain function in the substitution module.
Example usage:
>>> normalize_domain("http://treyhunner.com/2015/12/python-list-comprehensions-now-in-color/")
'https://treyhunner.com/2015/12/python-list-comprehensions-now-in-color/'
>>> normalize_domain("https://treyhunner.com/2016/02/how-to-merge-dictionaries-in-python/")
'https://treyhunner.com/2016/02/how-to-merge-dictionaries-in-python/'
>>> normalize_domain("http://www.treyhunner.com/2015/11/counting-things-in-python/")
'https://treyhunner.com/2015/11/counting-things-in-python/'
>>> normalize_domain("http://www.treyhunner.com")
'https://treyhunner.com'
>>> normalize_domain("http://trey.in/give-a-talk")
'http://trey.in/give-a-talk'
Answers
def normalize_domain(url):
return re.sub(
r"^https?://(www\.)?treyhunner\.com",
r"https://treyhunner.com",
url
)
Linebreaks
Write a function that accepts a string and converts linebreaks to HTML in the following way:
text is surrounded by paragraphs
text with two or more line breaks between is considered two separate paragraphs
text with a single line break between is separated by a
<br>
Tip
Modify the convert_linebreaks function in the substitution module.
Example usage:
>>> convert_linebreaks("hello")
'<p>hello</p>'
>>> convert_linebreaks("hello\nthere")
'<p>hello<br>there</p>'
>>> convert_linebreaks("hello\n\nthere")
'<p>hello</p><p>there</p>'
>>> convert_linebreaks("hello\nthere\n\nworld")
'<p>hello<br>there</p><p>world</p>'
Answers
def convert_linebreaks(string):
string = re.sub(r"\n{2,}", "</p><p>", string)
string = re.sub(r"\n", "<br>", string)
return f"<p>{string}</p>"
Or:
def convert_linebreaks(string):
return "".join(
f"<p>{paragraph}</p>"
for paragraph in re.split(r"\n{2,}", string)
).replace("\n", "<br>")
Alternation Exercises
These exercises are all in the alternation.py file in the exercises directory.
Edit the appropriate function in that file to complete each exercise.
To run the tests, from the exercises folder, type python test.py <function_name>, like this:
$ python test.py is_number
Decimal Numbers
Write a function to match decimal numbers.
We want to allow an optional - and we want to match numbers with or without one decimal point.
Tip
Modify the is_number function in the alternation module.
Example usage:
>>> is_number("5")
True
>>> is_number("5.")
True
>>> is_number(".5.")
False
>>> is_number(".5")
True
>>> is_number("01.5")
True
>>> is_number("-123.859")
True
>>> is_number("-123.859.")
False
>>> is_number(".")
False
Answers
def is_number(num_string):
return bool(re.search(r"^[-+]?(\d*\.?\d+|\d+\.)$", num_string))
Abbreviate
Make a function that creates an acronym from a phrase.
Tip
Modify the abbreviate function in the alternation module.
Example usage:
>>> abbreviate('Graphics Interchange Format')
'GIF'
>>> abbreviate('frequently asked questions')
'FAQ'
>>> abbreviate('cascading style sheets')
'CSS'
>>> abbreviate('Joint Photographic Experts Group')
'JPEG'
>>> abbreviate('content management system')
'CMS'
>>> abbreviate('JavaScript Object Notation')
'JSON'
>>> abbreviate('HyperText Markup Language')
'HTML'
Answer
def abbreviate(phrase):
"""Return an acronym for the given phrase."""
return "".join(re.findall(r"(?:[a-z](?=[A-Z])|\b)(\w)", phrase)).upper()
Hex Colors
Write a function to match hexadecimal color codes. Hex color codes consist of an octothorpe symbol followed by either 3 or 6 hexadecimal digits (that’s 0 to 9 or a to f).
Tip
Modify the is_hex_color function in the alternation module.
Example usage:
>>> is_hex_color("#639")
True
>>> is_hex_color("#6349")
False
>>> is_hex_color("#63459")
False
>>> is_hex_color("#634569")
True
>>> is_hex_color("#663399")
True
>>> is_hex_color("#000000")
True
>>> is_hex_color("#00")
False
>>> is_hex_color("#FFffFF")
True
>>> is_hex_color("#decaff")
True
>>> is_hex_color("#decafz")
False
Answer
def is_hex_color(string):
return bool(re.search(r"^#([\da-f]{3}){1,2}$", string, re.IGNORECASE))
def is_hex_color(string):
return bool(re.search(r"^#[\da-f]{3}([\da-f]{3})?$", string, re.IGNORECASE))
def is_hex_color(string):
return bool(re.search(r"^#([\da-f]{3}|[\da-f]{6})$", string, re.IGNORECASE))
Valid Date
Create a is_valid_date function that returns True if given a date in YYYY-MM-DD format.
For this exercise we’re more worried about accepting valid dates than we are about excluding invalid dates.
A regular expression is often used as a first wave of validation. Complete validation of dates should be done in our code outside of regular expressions.
Tip
Create this is_valid_date function in the alternation module.
Example usage:
>>> is_valid_date("2016-01-02")
True
>>> is_valid_date("1900-01-01")
True
>>> is_valid_date("2016-02-99")
False
>>> is_valid_date("20-02-20")
False
>>> is_valid_date("1980-30-05")
False
Answer
import re
def is_valid_date(string):
return bool(re.search(r"^\d{4}-[01]\d-[0-3]\d$", string))
import re
def is_valid_date(string):
return bool(re.search(r"""
^
(19|20) \d \d
-
( 0[1-9] | 1[0-2] )
-
( 0[1-9] | [12]\d | 3[01] )
$
""", string, re.VERBOSE))
I send out 1 Python exercise every week through a Python skill-building service called Python Morsels.
If you'd like to improve your Python skills every week, sign up!
You can find the Privacy Policy here.reCAPTCHA protected (Google Privacy Policy & TOS)