Standard Library Answers
Random Exercises
Roll Die
This is the roll.py exercise in the modules directory. Create the file roll.py in the modules sub-directory of the exercises directory. To test it, run python test.py roll.py from your exercises directory.
Create a program roll.py that simulates rolling one or more dice. The number of sides on the dice should be provided as arguments. Each argument represents the number of sides of a die, so python roll.py 6 6 means rolling 2 6-sided dice. If no arguments are given, assume a single 6 sided die.
Examples:
$ python roll.py
5
$ python roll.py 6
4
$ python roll.py 20
10
$ python roll.py 6 6
7
Answers
import sys
import random
def roll_dice(*nums):
if not nums:
nums = [6]
return sum(random.randint(1, n) for n in nums)
if __name__ == "__main__":
print(roll_dice(*(int(n) for n in sys.argv[1:])))
Random Line
This is the randline.py exercise in the modules directory. Create the file randline.py in the modules sub-directory of the exercises directory. To test it, run python test.py randline.py from your exercises directory.
Create a program randline.py that accepts either an argument from the command-line or standard input text. The argument is treated as a file name and the standard input text is treated as lines of text. The program should return a random line from the file or from the standard input lines.
Example usage (with us-state-capitals.csv):
$ cat us-state-capitals.csv | python randline.py
Mississippi,Jackson
$ python randline.py us-state-capitals.csv
New Mexico,Santa Fe
Answers
With only libraries and syntax we have learned so far:
import random
import sys
def read_file_lines(file_name):
with open(file_name) as in_file:
return in_file.readlines()
def main(args):
if not args:
lines = sys.stdin.readlines()
else:
lines = read_file_lines(args[0])
print(random.choice(lines).strip())
if __name__ == "__main__":
main(sys.argv[1:])
With inline if statements and a tricky context manager:
import random
import sys
def read_file_lines(file_name=None):
with open(file_name) if file_name else sys.stdin as in_file:
return in_file.readlines()
def main(args):
file_name = args[0] if args else None
print(random.choice(read_file_lines(file_name)).strip())
if __name__ == "__main__":
main(sys.argv[1:])
Using fileinput.input to read from files or sys.stdin:
import fileinput
import random
import sys
def read_file_lines(file_names):
"""Return lines from given files or standard input if no files given."""
with fileinput.input(*file_names) as file_lines:
return list(file_lines)
def main(args):
print(random.choice(read_file_lines(args)).strip())
if __name__ == "__main__":
main(sys.argv[1:])
Shuffle
This is the shuffle.py exercise in the modules directory. Create the file shuffle.py in the modules sub-directory of the exercises directory. To test it, run python test.py shuffle.py from your exercises directory.
Make a shuffle.py program that accepts an input file name and an output file name as input. The program should read the lines in the input file and write them to the output file in a random order.
$ cat dream.txt
What happens to a dream deferred?
Does it dry up
Like a raisin in the sun?
Or fester like a sore--
And then run?
Does it stink like rotten meat?
Or crust and sugar over--
like a syrupy sweet?
Maybe it just sags
like a heavy load.
Or does it explode?
$ python shuffle.py dream.txt nonsense.txt
$ cat nonsense.txt
Does it stink like rotten meat?
Or fester like a sore--
Or does it explode?
Does it dry up
Maybe it just sags
Or crust and sugar over--
Like a raisin in the sun?
What happens to a dream deferred?
like a heavy load.
And then run?
like a syrupy sweet?
Answer
import random
import sys
in_filename, out_filename = sys.argv[1:]
with open(in_filename, mode='rt') as in_file:
lines = in_file.read().splitlines()
random.shuffle(lines)
with open(out_filename, mode='wt') as out_file:
for line in lines:
out_file.write(line + "\n")
Capital Guessing
This is the guess_capitals.py exercise in the modules directory. Create the file guess_capitals.py in the modules sub-directory of the exercises directory. To test it, run python test.py guess_capitals.py from your exercises directory.
Write a guessing game program that takes a list of locations and their capitals and quizzes us on capitals.
You might want to test this program using us-state-capitals.csv.
Example usage:
$ python guess_capitals.py
Guess U.S. State Capitals
What is the capital of California? Sacramento
Correct! You have correctly guessed 1 capitals in a row!
What is the capital of Texas? Dallas.
Sorry that's not right.
What is the capital of Texas? Austin
Correct! You have correctly guessed 2 capitals in a row!
What is the capital of Nevada? Reno
Sorry that's not right.
What is the capital of Nevada? Las Vegas
Sorry that's not right.
What is the capital of Nevada? Paradise
Sorry that's not right.
The capital of Nevada is Carson City.
What is the capital of Ohio? Columbus
Correct! You have correctly guessed 1 capitals in a row!
Answers
import csv
import random
import sys
def get_capitals(file_name):
"""Open capitals CSV file and return list of capital tuples."""
with open(file_name) as csv_file:
csv_reader = csv.reader(csv_file)
next(csv_reader) # Skip headers row
return [(place, capital) for place, capital in csv_reader]
def main(file_name):
capitals = get_capitals(file_name)
place, capital = random.choice(capitals)
while True:
guess = input(f"What is the capital of {place}? ")
if guess == capital:
print(f"Correct! {capital} is the capital of {place}")
break
else:
print("That's not correct. Try again.")
if __name__ == "__main__":
main(sys.argv[1])
Date Exercises
Random Date
This is the someday.py exercise in the modules directory. Create the file someday.py in the modules sub-directory of the exercises directory. To test it, run python test.py someday.py from your exercises directory.
Create a program someday.py that takes two dates as arguments and returns a random date between the two given dates.
Example usage:
$ python someday.py 1970-01-01 1979-12-31
1974-07-20
$ python someday.py 2099-12-01 2099-12-31
2099-12-19
Answers
Parse YYYY-MM-DD format and use timedelta and randint to select date
from datetime import datetime, timedelta
import random
import sys
DATE_FORMAT = "%Y-%m-%d"
def get_random_date_between(start_date, end_date):
"""Return random date between two given dates."""
if end_date < start_date:
start_date, end_date = end_date, start_date # Order chronologically
days_between = (end_date - start_date).days
return start_date + timedelta(days=random.randint(0, days_between))
def main(start_date, end_date):
start = datetime.strptime(start_date, DATE_FORMAT).date()
end = datetime.strptime(end_date, DATE_FORMAT).date()
print(get_random_date_between(start, end))
if __name__ == "__main__":
main(*sys.argv[1:])
Fourth Thursday
This is the fourth_thursday exercise in dates.py.
The San Diego Python group meets on the fourth Thursday of every month. Make a function that takes a year and a month (January = 1, December = 12) and returns the fourth Thursday of that month.
>>> fourth_thursday(2015, 8)
datetime.date(2015, 8, 27)
>>> fourth_thursday(2015, 9)
datetime.date(2015, 9, 24)
>>> fourth_thursday(2015, 10)
datetime.date(2015, 10, 22)
>>> fourth_thursday(2016, 1)
datetime.date(2016, 1, 28)
>>> fourth_thursday(2016, 2)
datetime.date(2016, 2, 25)
Hint
Look up the weekday method on datetime objects.
Answers
Using datetime:
import datetime
def fourth_thursday(year, month):
"""Return a date of the fourth Thursday of the given month."""
THURSDAY = 3
first = datetime.date(year, month, 1)
day_shift = datetime.timedelta((THURSDAY - first.weekday()) % 7)
first_thursday = first + day_shift
return first_thursday + datetime.timedelta(weeks=3)
Age
There are no tests for this exercise.
Make a function to calculate how old someone is given their birthdate as a datetime.date object. If today is February 4, 2016, your function should work like this:
>>> from datetime import date
>>> get_age(date(2015, 2, 4))
1
>>> get_age(date(2015, 2, 5))
0
>>> get_age(date(2015, 2, 3))
1
>>> get_age(date(1980, 2, 29))
35
Answers
import datetime
def get_age(birthdate):
"""Return age given a person's birthdate."""
today = datetime.date.today()
offset = (birthdate.month, birthdate.day) <= (today.month, today.day)
return (today.year - birthdate.year) + offset - 1
Weekday Module
There are no tests for this exercise.
Make a Python script weekday.py that prints the day of the week for today. It should work like this:
$ python weekday.py
Thursday
Answers
import datetime
WEEKDAYS = ("Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday")
def main():
date = datetime.date.today()
print(WEEKDAYS[date.weekday()])
if __name__ == "__main__":
main()
Using weekday names in calendar.day_name:
import calendar
import datetime
def main():
date = datetime.date.today()
print(calendar.day_name[date.weekday()])
if __name__ == "__main__":
main()
Collection Exercises
Count Words
This is the count_words exercise in dictionaries.py.
Modify the function count_words so that it accepts a string and returns a dictionary noting the number of times each word occurs in the string.
As a bonus exercise, ignore case and remove commas and other symbols between words.
Hint
Consider using a Counter or defaultdict from the collections module.
Note
To run tests for the Bonus exercise, open dictionaries_test.py, find the CountWordsTests class and comment out the @unittest.skip lines from the appropriate test methods.
Answers
With defaultdict:
from collections import defaultdict
def count_words(string):
"""Return the number of times each word occurs in the string."""
count = defaultdict(int)
for word in string.split():
count[word] += 1
return count
With Counter:
from collections import Counter
def count_words(string):
"""Return the number of times each word occurs in the string."""
return Counter(string.split())
Bonus: Ignoring case and symbols:
from collections import Counter
import re
def count_words(string):
"""Return the number of times each word occurs in the string."""
words = re.findall(r"[a-z']+", string.lower())
return Counter(words)
Maximums
This is the MaxCounter exercise in classes.py.
Modify the class MaxCounter so that it acts just like the Counter class but MaxCounter objects should have a max_keys method that provides an iterable of all the keys that occur the most number of times.
For example:
>>> from classes import MaxCounter
>>> counts = MaxCounter('string of various words')
>>> counts
MaxCounter({'s': 3, 'r': 3, ' ': 3, 'o': 3, 'i': 2, 't': 1, 'n': 1, 'g': 1, 'f': 1, 'v': 1, 'a': 1, 'u': 1, 'w': 1, 'd': 1})
>>> counts.max_keys()
['s', 'r', ' ', 'o']
Answers
from collections import Counter
class MaxCounter(Counter):
"""Counter-like class that allows retrieving all maximums."""
def max_keys(self):
if not self:
return set()
(_, max_count), = self.most_common(1)
maximums = set()
for item, count in self.most_common():
if count < max_count:
break
maximums.add(item)
return maximums
Ice Cream DataClasses
Rewrite the Size, Flavor, and IceCream classes in classes.py using data classes.
You can test each of these classes by running tests for Size, Flavor, and IceCream.
Answers
from dataclasses import dataclass, field
@dataclass
class Flavor:
"""Flavor of ice cream."""
name: str
ingredients: list = field(default_factory=tuple)
has_dairy: bool = True
def __post_init__(self):
"""Copy given ingredients iterable."""
self.ingredients = list(self.ingredients)
@dataclass
class Size:
"""Ice cream size."""
quantity: int
unit: str
price: str
def __str__(self):
units = self.unit if self.quantity == 1 else f"{self.unit}s"
return f"{self.quantity} {units}"
@dataclass
class IceCream:
"""Ice cream to be ordered in our ice cream shop."""
flavor: Flavor
size: Size
def __str__(self):
return f"{self.size} of {self.flavor.name}"
Most common
This is the get_most_common exercise in sets.py.
Create a function that accepts a list of iterables and returns a set of the most common items from all of the given iterables.
>>> get_most_common([{1, 2}, {2, 3}, {3, 4}])
{2, 3}
Hint
Consider using a Counter or defaultdict from the collections module.
Answers
With defaultdict:
from collections import defaultdict
def get_most_common(things):
"""Counter-like class that allows retrieving all maximums."""
counts = defaultdict(int)
for item_list in things:
for x in item_list:
counts[x] += 1
most = max(counts.values())
return {x for x, count in counts.items() if count == most}
With Counter:
from collections import Counter
def get_most_common(things):
"""Counter-like class that allows retrieving all maximums."""
all_items = []
for item_list in things:
for x in item_list:
all_items.append(x)
counts = Counter(all_items)
_, most = counts.most_common(1)[0]
return {x for x, count in counts.items() if count == most}
With Counter and nested list comprehension:
from collections import Counter
def get_most_common(things):
"""Counter-like class that allows retrieving all maximums."""
counts = Counter(x for item_list in things for x in item_list)
_, most = counts.most_common(1)[0]
return {x for x, count in counts.items() if count == most}
Flipping Dictionary of Lists
This is the flip_dict_of_lists exercise in collection.py.
Write a function that takes a dictionary of lists and returns a new dictionary containing the list items as keys and the original dictionary keys as list values.
Example:
>>> restaurants_by_people = {
... 'diane': {'Siam Nara', 'Punjabi Tandoor', 'Opera'},
... 'peter': {'Karl Strauss', 'Opera', 'Habaneros'},
... 'trey': {'Habaneros', 'Karl Strauss', 'Opera', 'Punjabi Tandoor'},
... }
>>> favorite_restaurants = flip_dict_of_lists(restaurants_by_people)
>>> favorite_restaurants
{'Siam Nara': ['diane'], 'Karl Strauss': ['trey', 'peter'], 'Opera': ['diane', 'trey', 'peter'], 'Punjabi Tandoor': ['diane', 'trey'], 'Habaneros': ['trey', 'peter']}
Hint
Consider using a defaultdict from the collections module.
Answers
from collections import defaultdict
def flip_dict_of_lists(original_dict):
"""Take dictionary of lists and return flipped dictionary of lists."""
new_dict = defaultdict(list)
for old_key, old_values in original_dict.items():
for value in old_values:
new_dict[value].append(old_key)
return new_dict
Deal Cards
These are the get_cards, shuffle_cards and deal_cards exercises in collection.py.
Create three functions:
get_cards: returns a list of namedtuples representing cards. Each card should havesuitandrank.shuffle_cards: accepts a list of cards as its argument and shuffles the list of cards in-placedeal_cards: accepts a number as its argument, removes the given number of cards from the end of the list and returns them
Examples:
>>> from collection import get_cards, shuffle_cards, deal_cards
>>> deck = get_cards()
>>> deck[:14]
[Card(rank='A', suit='spades'), Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades'), Card(rank='5', suit='spades'), Card(rank='6', suit='spades'), Card(rank='7', suit='spades'), Card(rank='8', suit='spades'), Card(rank='9', suit='spades'), Card(rank='10', suit='spades'), Card(rank='J', suit='spades'), Card(rank='Q', suit='spades'), Card(rank='K', suit='spades'), Card(rank='A', suit='hearts')]
>>> len(deck)
52
>>> shuffle_cards(deck)
>>> deck[-5:]
[Card(rank='9', suit='diamonds'), Card(rank='6', suit='hearts'), Card(rank='7', suit='diamonds'), Card(rank='K', suit='spades'), Card(rank='7', suit='clubs')]
>>> hand = deal_cards(deck)
>>> hand
[Card(rank='9', suit='diamonds'), Card(rank='6', suit='hearts'), Card(rank='7', suit='diamonds'), Card(rank='K', suit='spades'), Card(rank='7', suit='clubs')]
>>> len(deck)
47
>>> deck[-5:]
[Card(rank='5', suit='spades'), Card(rank='Q', suit='clubs'), Card(rank='Q', suit='spades'), Card(rank='2', suit='diamonds'), Card(rank='6', suit='clubs')]
Answers
from typing import NamedTuple
import random
class Card(NamedTuple):
"""Class representing a playing card."""
rank: str
suit: str
def get_cards():
"""Create a list of NamedTuples representing a deck of playing cards."""
ranks = ['A'] + [str(n) for n in range(2, 11)] + ['J', 'Q', 'K']
suits = ['spades', 'hearts', 'diamonds', 'clubs']
return [Card(rank, suit) for suit in suits for rank in ranks]
def shuffle_cards(deck):
"""Shuffles a list in-place"""
random.shuffle(deck)
def deal_cards(deck, count=5):
"""Remove the given number of cards from the deck and returns them"""
return [deck.pop() for i in range(count)]
Memory-efficient CSV
This is the parse_csv exercise in collection.py.
Using DictReader to read CSV files is convenient because CSV columns can be referenced by name (instead of positional order). However there are some downsides to using DictReader. CSV column ordering is lost because dictionaries are unordered. The space required to store each row is also unnecessarily large because dictionaries are not a very space-efficient data structure.
There is discussion of adding a NamedTupleReader to the csv module, but this hasn’t actually happened yet.
In the meantime, it’s not too difficult to use a csv.reader object to open a CSV file and then use a namedtuple to represent each row.
See the collections.namedtuple for information about the original namedtuple.
Create a function parse_csv that accepts a file object which contains a CSV file (including a header row) and returns a list of namedtuples representing each row.
Example with us-state-capitals.csv:
>>> with open('us-state-capitals.csv') as csv_file:
... csv_rows = parse_csv(csv_file)
...
>>> csv_rows[:3]
[Row(state='Alabama', capital='Montgomery'), Row(state='Alaska', capital='Juneau'), Row(state='Arizona', capital='Phoenix')]
Answers
from collections import namedtuple
import csv
def parse_csv(file):
"""Return namedtuple list representing data from given file object."""
csv_reader = csv.reader(file)
Row = namedtuple('Row', next(csv_reader))
return [Row(*values) for values in csv_reader]
itertools Exercises
Head
This is the head exercise in generators.py.
If you have already solved this exercise with a generator expression, try refactoring it to use something from the itertools module.
Make a head function that lazily gives the first n items of a given iterable.
>>> list(head([1, 2, 3, 4, 5], n=2))
[1, 2]
>>> first_4 = head([1, 2, 3, 4, 5], n=4)
>>> list(zip(first_4, first_4))
[(1, 2), (3, 4)]
Answers
from itertools import islice
def head(iterable, n):
"""Return the first n items of given iterable."""
return islice(iterable, n)
All Together
This is the all_together exercise in generators.py.
Write a function that takes any number of iterables and returns an iterator which will loop over each of them in order.
Example:
>>> from generators import all_together
>>> list(all_together([1, 2], (3, 4), "hello"))
[1, 2, 3, 4, 'h', 'e', 'l', 'l', 'o']
>>> nums = all_together([1, 2], (3, 4))
>>> list(all_together(nums, nums))
[1, 2, 3, 4]
Answers
def all_together(*iterables):
"""String together all items from the given iterables."""
return (
item
for iterable in iterables
for item in iterable
)
lstrip
This is the lstrip exercise in iteration.py.
Edit the lstrip function so that it accepts an iterable and an object and returns an iterator that returns the items from the original iterable except any item at the beginning of the iterable which is equal to the given object should be skipped.
Look through the itertools library for something very useful for this.
Example:
>>> list(lstrip([0, 0, 1, 0, 2, 3], 0))
[1, 0, 2, 3]
>>> list(lstrip(' hello ', ' '))
['h', 'e', 'l', 'l', 'o', ' ']
>>> x = lstrip([0, 1, 2, 3], 0)
>>> list(x)
[1, 2, 3]
>>> list(x)
[]
Answers
from itertools import dropwhile
def lstrip(iterable, strip_value):
"""Return iterable with strip_value items removed from beginning."""
def is_strip_value(value): return value == strip_value
return dropwhile(is_strip_value, iterable)
Big Primes
This is the get_primes_over exercise in generators.py.
Write a function that returns an iterator that will result in a specified number of prime numbers greater than 999,999. The input to the function is the number of primes that will be generated by the iterator.
Try doing this without using while loops or for loops.
You can use this function to determine whether a number is prime:
def is_prime(candidate):
"""Return True if candidate number is prime."""
for n in range(2, candidate):
if candidate % n == 0:
return False
return True
Answers
from itertools import count, islice
def is_prime(candidate):
"""Return True if candidate number is prime."""
for n in range(2, candidate):
if candidate % n == 0:
return False
return True
def get_primes_over(limit):
"""Return primes over a given number."""
primes = (
n
for n in count(999999)
if is_prime(n)
)
return islice(primes, limit)
Total Length
This is the total_length exercise in iteration.py.
Make a function total length that should calculate the total length of all given iterables.
Example:
>>> total_length([1, 2, 3])
3
>>> total_length()
0
>>> total_length([1, 2, 3], [4, 5], iter([6, 7]))
7
Answers
With a chained list:
from itertools import chain
def total_length(*iterables):
"""Return the total number of items in all given iterables."""
return len(list(chain.from_iterable(iterables)))
With sum of a generator containing 1 for each item:
from itertools import chain
def total_length(*iterables):
"""Return the total number of items in all given iterables."""
return sum(1 for _ in chain.from_iterable(iterables))
Compact
This is the compact exercise in iteration.py.
Write a function compact that takes an iterable and lazily returns the elements of the iterable, with any adjacent duplicates removed.
Hint
Try using itertools.groupby with a generator expression to solve it.
It should work like this:
>>> from generators import compact
>>> list(compact([1, 1, 1]))
[1]
>>> list(compact([1, 1, 2, 2, 3, 2]))
[1, 2, 3, 2]
>>> list(compact([]))
[]
>>> c = compact(n**2 for n in [1, 2, 2])
>>> iter(c) is c
True
>>> list(c)
[1, 4]
Answers
from itertools import groupby
def compact(iterable):
return (
item
for item, group in groupby(iterable)
)
Stop On
This is the stop_on exercise in iteration.py.
Write a generator function stop_on that accepts an iterable and a value and yields from the given iterable repeatedly until the given value is reached.
Example:
>>> list(stop_on([1, 2, 3], 3))
[1, 2]
>>> next(stop_on([1, 2, 3], 1), 0)
0
Answers
from itertools import takewhile
def stop_on(elements, stop_item):
"""Yield from the iterable until the given value is reached."""
def not_equal(item): return item != stop_item
return takewhile(not_equal, elements)
Random Number
This is the random_number_generator exercise in iteration.py
Make an inexhaustible generator that always provides 4 as the next number.
Hint
Try using a tool from itertools to solve this problem.
Example:
>>> number_generator = random_number_generator()
>>> next(number_generator)
4
>>> next(number_generator)
4
>>> next(number_generator)
4
>>> iter(number_generator) is number_generator
True
Answers
from itertools import repeat
def random_number_generator():
"""Return a generator that yields 4 forever."""
return repeat(4)
Running Mean
This is the running_mean exercise in iteration.py.
Create a running_mean function that takes an iterable and yields the current running mean.
Try doing this without a for loop (using only itertools function and generator expressions).
For example:
>>> numbers = [8, 4, 3, 1, 3, 5]
>>> list(running_mean(numbers))
[8.0, 6.0, 5.0, 4.0, 3.8, 4.0]
Answers
from itertools import accumulate
def running_mean(numbers):
"""Yield the running mean while looping over the iterable."""
running_total = accumulate(numbers)
return (
total/length
for length, total in
enumerate(running_total, start=1)
)
Argparse Exercises
Tip
For all of these programs, make sure your program prints useful help information when an incorrect number of arguments or invalid arguments are specified.
Note
These exercises do not work with the automated tests.
Add
This is the add.py exercise in the modules directory. You need to create the add.py file in the modules sub-directory of the exercises directory.
Make a program add.py that takes two numbers and prints out the sum of these numbers.
$ python add.py 3 5.2
8.2
$ python add.py -7 2
-5.0
$ python add.py -2 -1
-3.0
Answers
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'numbers',
nargs=2,
type=float,
help="2 numbers to add together"
)
args = parser.parse_args()
x, y = args.numbers
print(x + y)
if __name__ == "__main__":
main()
Average
This is the average.py exercise in the modules directory. You need to create the average.py file in the modules sub-directory of the exercises directory.
Make a program average.py that calculates the average of all given command-line arguments and prints a message “No numbers to average!” if there are no arguments given.
$ python average.py 2 3 4 5 6 7
Average is 4.5
$ python average.py 2 3 4
Average is 3.0
Answers
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'numbers',
nargs='+',
type=float,
help="Numbers to average"
)
args = parser.parse_args()
total = 0
for num in args.numbers:
total += num
print(f"Average is {total/len(args.numbers)}")
if __name__ == "__main__":
main()
Word Count
This is the wc.py exercise in the modules directory. You need to create the wc.py file in the modules sub-directory of the exercises directory.
Make a wc.py program that prints newline, word, and letter counts for given files. The command should take optional arguments and work in this way:
$ python wc.py --version
Word Count 1.0
$ python wc.py -h
wc - print newline, word, and letter counts for each file
Usage: wc.py [-hcwl] <file>...
wc.py (-h | --help)
wc.py --version
Options:
-c --chars print the character count
-w --words print the word count
-l --lines print the newline count
-h --help display this help and exit
The arguments should function like this:
$ python wc.py -c us-state-capitals.csv
us-state-capitals.csv:
chars 953
$ python wc.py -cl us-state-capitals.csv
us-state-capitals.csv:
lines 51
chars 953
$ python wc.py -l -c us-state-capitals.csv
us-state-capitals.csv:
lines 51
chars 953
$ python wc.py -w declaration-of-independence.txt
declaration-of-independence.txt:
words 1338
$ python wc.py declaration-of-independence.txt
declaration-of-independence.txt:
lines 67
words 1338
chars 8190
$ python wc.py -c declaration-of-independence.txt us-state-capitals.csv
declaration-of-independence.txt:
chars 8190
us-state-capitals.csv:
chars 953
Answers
"""wc - print newline, word, and letter counts for each file
Usage: wc.py [-hcwl] <file>...
wc.py (-h | --help)
wc.py --version
Options:
-c --chars print the character count
-w --words print the word count
-l --lines print the newline count
-h --help display this help and exit
"""
import argparse
def main():
parser = argparse.ArgumentParser("wc")
parser.add_argument('-c', '--chars', action='store_true',
help="print the character count")
parser.add_argument('-w', '--words', action='store_true',
help="print the word count")
parser.add_argument('-l', '--lines', action='store_true',
help="print the newline count")
parser.add_argument("files", nargs="+")
parser.add_argument(
"--version",
action="version",
version="Word Count 1.0",
)
args = parser.parse_args()
print_file_info(args.files, args.lines, args.words, args.chars)
def print_file_info(filenames, lines, words, chars):
if not(chars or words or lines):
chars = words = lines = True
for filename in filenames:
print(f"{filename}:")
with open(filename, "rt") as handle:
text = handle.read()
if lines:
print(f"lines: {len(text.splitlines())}")
if words:
print(f"words: {len(text.split())}")
if chars:
print(f"chars: {len(text)}")
if __name__ == '__main__':
main()
Roll Die
This is the roll.py exercise in the modules directory. You need to create the roll.py file in the modules sub-directory of the exercises directory.
Create a program roll.py that simulates rolling one or more dice. The number of sides on the dice should be provided as arguments. If no arguments are given, assume a single 6 sided die.
Examples:
$ python roll.py
5
$ python roll.py 6
4
$ python roll.py 20
10
$ python roll.py 6 6
7
Answers
"""
Usage: roll.py [<dice>...]
Roll the given dice and display the sum of the rolls.
"""
import random
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'sides',
nargs='*',
type=int,
help="Number of sides of each die"
)
args = parser.parse_args()
print(roll_dice(*args.sides))
def roll_dice(*dice):
if not dice:
dice = [6]
return sum(random.randint(1, n) for n in dice)
if __name__ == "__main__":
main()
Convert CSV Delimiters
This is the convert_csv.py exercise in the modules directory. You need to create the convert_csv.py file in the modules sub-directory of the exercises directory.
Write a program that reads a CSV file using one delimiter format and outputs a new file using another delimiter format.
Example using the included file named people.csv that uses the | character as the delimiter, where we want it to be converted to a comma-separated file:
$ python convert_csv.py --in-delim="|" --out-delim="," people.csv people_comma.csv
Note
To run this script with escape characters (like tab) you may have to enter a tab key manually on your keyboard. You can usually do this with Ctrl-V Ctrl-TAB on Linux systems.
Answers
"""convert_csv files
Usage:
convert_csv.py --in-delim=<delim> --out-delim=<delim> IN_FILE OUT_FILE
Convert the given CSV file from one delimiter format to another
"""
import argparse
import csv
def main():
parser = argparse.ArgumentParser("convert_csv")
parser.add_argument("--in-delim", help="Delimiter of input file")
parser.add_argument("--out-delim", help="Delimiter of output file")
parser.add_argument("in_file", help="Name of input CSV file")
parser.add_argument("out_file", help="Name of output CSV file")
args = parser.parse_args()
convert(
in_filename=args.in_file,
out_filename=args.out_file,
in_delimiter=args.in_delim,
out_delimiter=args.out_delim,
)
def convert(in_filename, out_filename, in_delimiter, out_delimiter):
with open(in_filename, mode='rt') as in_file:
csv_reader = csv.reader(in_file, delimiter=in_delimiter)
with open(out_filename, mode='wt', newline='') as out_file:
csv_writer = csv.writer(out_file, delimiter=out_delimiter)
csv_writer.writerows(csv_reader)
if __name__ == "__main__":
main()
Difference Between
This is the difference.py exercise in the modules directory. You need to create the difference.py file in the modules sub-directory of the exercises directory.
Make a program difference.py that takes two numbers and prints the positive difference between these two numbers:
$ python difference.py 3 5
2.0
$ python difference.py 6 3.5
2.5
$ python difference.py -7 2
9.0
Answers
import argparse
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'numbers',
nargs=2,
type=float,
help="2 numbers to calculate difference"
)
args = parser.parse_args()
x, y = args.numbers
print(abs(x - y))
if __name__ == "__main__":
main()
Pathlib Exercises
List Files
This is the ls.py exercise in the modules directory. You need to create the ls.py file in the modules sub-directory of the exercises directory.
Write a program ls.py that lists all files and directories in a given directory or in the current directory if no argument is given. Sort the lines by filename.
Example usage:
$ python3 ls.py
my_file.txt
speeches
whereami.py
$ python3 ls.py speeches/
moon_landing.txt
Answers
from pathlib import Path
import sys
def list_files(directory):
for path in sorted(Path(directory).iterdir()):
print(path.name)
if __name__ == "__main__":
directory = Path(sys.argv[1]) if len(sys.argv) > 1 else Path('.')
list_files(directory)
Remove Empty Directories
This is the remove_empty.py exercise in the modules directory. You need to create the remove_empty.py file in the modules sub-directory of the exercises directory.
Write a program remove_empty.py that removes all empty directories inside a given directory.
The empty directories should be removed recursively (if removing empty directories inside a parent directory makes that parent directory empty then it should be removed too).
Given this file hierarchy:
a
├── b
│ └── d
└── c
└── e.txt
Our program should work like this:
$ python remove_empty.py
Deleting directory d
Deleting directory b
Deleting directory a
And our file hierarchy should now look like this:
a
└── c
└── e.txt
Answers
from pathlib import Path
import sys
def remove_empty(directory):
for path in directory.iterdir():
if path.is_dir():
remove_empty(path)
if list(directory.iterdir()) == []:
print(f"Deleting directory {directory.name}")
directory.rmdir()
if __name__ == "__main__":
remove_empty(Path(sys.argv[1]))
Find EditorConfig Files
This is the editorconfig.py exercise in the modules directory. You need to create the editorconfig.py file in the modules sub-directory of the exercises directory.
Write a module editorconfig.py that contains a function find_configs which accepts a filename and returns the contents of all .editorconfig files found in the file’s directory and all its parent directories.
Your editorconfig.find_configs function should return a list of tuples containing the file contents and the full file path of each .editorconfig file found like this.
For example, say we’re in the directory /home/trey/work/project/ and the following .editorconfig files exist:
/home/trey/work/project/.editorconfig:
[*.py]
indent_style = space
indent_size = 4
/home/trey/.editorconfig:
[*]
trim_trailing_whitespace = true
Calling our program should find and print those files like this:
>>> from editorconfig import find_configs
>>> for config, name in find_configs('/home/trey/work/project/my_file.py'):
... print(f"Configuration for {name}")
... print(config)
Configuration for /home/trey/work/project/.editorconfig
[*.py]
indent_style = space
indent_size = 4
Configuration for /home/trey/.editorconfig
[*]
trim_trailing_whitespace = true
Answers
from pathlib import Path
def find_configs(filename):
config_files = [
path / '.editorconfig'
for path in Path(filename).resolve().parents
]
return [
(str(path), path.read_text())
for path in config_files
if path.is_file()
]
Tree
Note
This exercise does not have automated tests.
This is the tree.py exercise in the modules directory.
Write a program tree.py that prints out a file tree for the given directory (defaulting to the current directory of no argument is specified).
Also print out the total number of files and directories seen.
Example usage:
$ python3 tree.py
declaration-of-independence.txt
here/
are/
directories
speeches/
moon_landing.txt
whereami.py
4 directories, 3 files
Bonus: Use ASCII art to represent the files visually
Example usage:
$ python3 tree.py
.
|-- declaration-of-independence.txt
|-- here
| `-- are
| `-- directories
|-- speeches
| `-- moon_landing.txt
`-- whereami.py
4 directories, 3 files
Answers
Implementation using os.walk:
import os
import sys
def print_all_paths(tree, current_path, depth):
"""Print paths for file tree recursively.
tree: mapping of full path to files and directories it contains
current_path: current full path being prined
depth: current path depth
"""
for file_or_dir in tree[current_path]:
printed_line = " " * depth + file_or_dir
full_path = os.path.join(current_path, file_or_dir)
if full_path not in tree:
print(printed_line) # print just the file
else:
print(printed_line + "/") # print directory
print_all_paths(tree, full_path, depth + 1)
def print_file_tree(root):
"""Print file tree for given directory."""
tree = {path: sorted(dirs + files) for path, dirs, files in os.walk(root)}
print_all_paths(tree, root, 0)
if __name__ == "__main__":
directory = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
print_file_tree(directory)
Implementation using os.listdir:
import os
import re
import sys
def print_tree(top, prefix=""):
"""Print paths for file tree recursively."""
print(prefix + os.path.basename(top))
prefix = re.sub(r"[-`]", " ", prefix)
try:
children = os.listdir(top)
except OSError:
return # This is a file, not a directory
if not children:
return # Empty directory
children = sorted(os.path.join(top, name) for name in children)
for name in children[:-1]:
print_tree(name, (prefix + "|-- "))
print_tree(children[-1], (prefix + "`-- "))
if __name__ == "__main__":
print_tree(sys.argv[1])