Standard Library Answers

Random Exercises

Roll Die

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

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

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.

Capital Guesser

Write a program that reads us-state-capitals.csv, picks a random state, and asks the user for the capital. The user has three chances to guess the capital before the game gives away the answer. The number of capitals that have been correctly guessed in a row should be printed out after each correct guess.

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


CAPITALS_FILE_NAME = 'us-state-capitals.csv'


def get_capitals():
    with open(CAPITALS_FILE_NAME) as csv_file:
        return [(place, capital) for (place, capital) in csv.reader(csv_file)]


def main():
    place, capital = random.choice(get_capitals())
    while True:
        guess = input("What is the capital of {}? ".format(place))
        if guess == capital:
            print("Correct! {} is the capital of {}".format(capital, place))
            break
        else:
            print("That's not correct.  Try again.")


if __name__ == "__main__":
    main()

Date Exercises

Random Date

Create a program somedate.py that takes two dates as arguments and returns a random date between the two given dates.

Example usage:

$ python somedate.py 1970-01-01 1979-12-31
1974-07-20
$ python somedate.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:])

Age

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):
    today = datetime.date.today()
    offset = (birthdate.month, birthdate.day) <= (today.month, today.day)
    return (today.year - birthdate.year) + offset - 1

Weekday Module

Make a Python script weekday.py that prints the day of the week for today. It should work like this:

$ python weekday.py
Thursday

Hint

look up the strftime method on the datetime.datetime object.

Bonus: make your weekday.py script work also work for a given date:

$ python weekday.py 2016-01-25
Monday
$ python weekday.py 2016-02-5
Friday
$ python weekday.py 2000-01-01
Saturday
$ python weekday.py 3000-01-01
Wednesday

Answers

import datetime
import sys


WEEKDAYS = ("Monday", "Tuesday", "Wednesday", "Thursday",
            "Friday", "Saturday", "Sunday")


def main(date_string):
    date = datetime.datetime.strptime(date_string, '%Y-%m-%d')
    print(WEEKDAYS[date.weekday()])


if __name__ == "__main__":
    main(sys.argv[1])

Using weekday names in calendar.day_name:

import calendar
import datetime
import sys


def main(date_string):
    date = datetime.datetime.strptime(date_string, '%Y-%m-%d')
    print(calendar.day_name[date.weekday()])


if __name__ == "__main__":
    main(sys.argv[1])

Fourth Thursday

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):
    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)

Last Day

Make a function that returns the last day of class given a Monday start date and the number of weeks of the class (assuming it ends on a Friday).

Answers

Disregarding day of the week:

from datetime import timedelta

def last_day(start_date, weeks):
    return start_date + timedelta(weeks=weeks - 1)

Last day (Sunday) of the last week:

from datetime import timedelta

def last_day(start_date, weeks):
    last_day_of_week = start_date + timedelta(days=(6 - start_date.weekday()))
    return last_day_of_week + timedelta(weeks=weeks - 1)

Collection Exercises

Count Words

  1. Open a file containing the Declaration of Independence
  2. Create a dictionary recording the number of times all two letter words occur in the Declaration of Independence. The keys should be words and the values should be the count.

As a bonus exercise, remove words that are not alphanumeric.

Hint

Consider using a Counter or defaultdict from the collections module.

Answers

With defaultdict:

from collections import defaultdict

def count_words():
    with open('declaration-of-independence.txt') as declaration_file:
        declaration = declaration_file.read()
    count = defaultdict(int)
    for word in declaration.split():
        if len(word) == 2 and word.isalpha():
            count[word] += 1
    return(count)

With Counter:

from collections import Counter

def count_words():
    with open('declaration-of-independence.txt') as declaration_file:
        declaration = declaration_file.read()
    word_list = []
    for word in declaration.split():
        if len(word) == 2 and word.isalpha():
            word_list.append(word)
    return dict(Counter(word_list))

With Counter and nested list comprehension:

from collections import Counter

def count_words():
    with open('declaration-of-independence.txt') as declaration_file:
        declaration = declaration_file.read()
    return dict(Counter(word for word in declaration.split() if
                        len(word) == 2 and word.isalpha()))

Most common

Create a function that accepts a list of iterables and returns a set of the most common items from all of the given iterables.

>>> 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 most_common(things):
    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 most_common(things):
    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 most_common(things):
    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

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

def flip_dict_of_lists(original_dict):
    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

Create three functions:

  • get_cards: returns a list of namedtuples representing cards. Each card should have suit and rank.
  • shuffle_cards: accepts a list of cards as its argument and shuffles the list of cards in-place
  • deal_cards: accepts a number as its argument, removes the given number of cards from the end of the list and returns them

Examples:

>>> 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 collections import namedtuple
import random

def get_cards():
    Card = namedtuple('Card', 'rank suit')
    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):
    random.shuffle(deck)

def deal_cards(deck, count=5):
    return [deck.pop() for i in range(count)]

Bonus: Memory-efficient CSV

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.

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):
    csv_reader = csv.reader(file)
    Row = namedtuple('Row', next(csv_reader))
    return [Row(*values) for values in csv_reader]