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¶
- Open a file containing the
Declaration of Independence - 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 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:
>>> 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]