Section #6 Solutions


Written by Brahm Capoor, Juliette Woodrow, Baker Sharp, Peter Maldonado, Kara Eng, Tori Qiu, Parth Sarin, and Elyse Cornwall


Counting by Consonants

def remove_vowels(s):
    """
    Returns a string that is the parameter string s without any vowels.
    """
    out = ''
    for c in s:
        # Check if c is a consonant
        if c not in 'aieou':
            out += c
    return out


def count_by_consonants(filename):
    """
    Reads in the file whose name is filename and returns a dictionary
    that contains counts of words that share the same consonants in order.
    """
    # Create empty dictionary
    counts = {}

    with open(filename, 'r') as f:
        for word in f:
            # Remove newline and spaces
            word = word.strip()
            word = remove_vowels(word)
            # Check if we've added this word to our dictionary yet
            if word not in counts:
                # Initialize to 0
                counts[word] = 0
            counts[word] += 1
    
    return counts

Wannabe Astronauts

import random

# the skill score needed for an astronaut to pass the first cut
SKILL_SCORE_MINIMUM = 0.90
# combined score is 95 percent skill, feel free to try other numbers
SKILL_WEIGHT = 0.95
# combined score is 5 percent luck, feel free to try other numbers
LUCK_WEIGHT = 0.05


def main():
    # Finish the rest of main()

    print_introduction()

    num_applicants = int(input("How many people want to be astronauts? "))
    applicants = make_applicants(num_applicants)

    skilled_applicants = make_first_cut(applicants)
    print("There are", len(skilled_applicants),
          "wannabe astronauts skillful enough to make the first cut.")

    num_to_select = int(input("How many people get to become astronauts? "))
    selected_astronauts = select_top_astronauts(skilled_applicants,
                                                num_to_select)

    # Find the average luck and skill scores for the skilled 
    # applicants and the selected astronauts
    skilled_averages = find_score_averages(skilled_applicants)
    selected_averages = find_score_averages(selected_astronauts)

    # Calculate the percent increases for luck and skill
    # Use the function find_percent_increase(starting_value, final_value)
    skill_difference = find_percent_increase(skilled_averages['skill'], selected_averages['skill'])
    luck_difference = find_percent_increase(skilled_averages['luck'], selected_averages['luck'])

    # Call print_conclusion here when you have the necessary info
    print_conclusion(skill_difference, luck_difference, len(skilled_applicants))

def find_score_averages(applicants):
    sum_skill = 0
    sum_luck = 0
    for applicant in applicants:
        sum_skill += applicant['skill']
        sum_luck += applicant['luck']
    
    averages = {}
    averages['skill'] = sum_skill / len(applicants)
    averages['luck'] = sum_luck / len(applicants)
    return averages

def make_applicants(num_applicants):
    """
    Returns a list of applicants, where each applicant
    is a list with a random skill and luck score, e.g.
    one_applicant = {'skill': XXXX, 'luck': YYYY}

    Luck and skill scores should be a float between 
    0 and 1, use random.random()
    """
    applicants = []

    for i in range(num_applicants):
        applicants.append(make_astronaut())

    return applicants


def make_first_cut(applicants):
    """
    Returns a new list of skilled applicants, made
    only of applicants with a skill score that is
    greater than or equal to SKILL_SCORE_MINIMUM
    """
    skilled_applicants = []

    for applicant in applicants:
        if applicant['skill'] >= SKILL_SCORE_MINIMUM:
            skilled_applicants.append(applicant)

    return skilled_applicants


def calc_combined_score(applicant):
    """
    Calculates the combined score of an applicant
    Apply SKILL_WEIGHT and LUCK_WEIGHT to each
    score and return the sum
    """

    skill_portion = applicant['skill'] * SKILL_WEIGHT
    luck_portion = applicant['luck'] * LUCK_WEIGHT
    return skill_portion + luck_portion


def make_astronaut():
    """
    Returns an astronaut with random luck and skill
    """
    skill = random.random()  # generates a random decimal between 0 and 1
    luck = random.random()  # generates a random decimal between 0 and 1
    return {'skill': skill, 'luck': luck}


###
#
# No need to edit the code after this point, but feel free to look!
#
###
def print_introduction():
    print(
        "Welcome! This program explores a simplified comparison of luck and skill."
    )
    print(
        "Set the constant PRINT_STAT_DETAILS to True to see additional information."
    )
    print("-------------------------")


def select_top_astronauts(astronauts, num_to_select):
    """
    Returns the top X astronauts, as determined by
    the calc_score function, which incorporates a bit of luck
    """
    # the key=calc_combined_score arranges this list based on the combined score,
    sorted_astronauts = sorted(astronauts, key=calc_combined_score)

    # This gets the last astronauts in the list, AKA the astronauts with the top scores
    selected_astronauts = sorted_astronauts[-num_to_select:]

    return selected_astronauts


def find_percent_increase(starting_value, final_value):
    decimal_increase = (final_value - starting_value) / starting_value
    percent_increase = round(decimal_increase * 100, 1)
    return percent_increase


def print_conclusion(skill_percent_increase, luck_percent_increase,
                     num_skillful_astronauts):
    print("-------------------------")
    print("-------- SUMMARY --------")
    print("The selected group is (on average):")
    print(" -",
          str(skill_percent_increase) + "%", "more skillful than the",
          num_skillful_astronauts, "people that passed the first cut")
    print(" -",
          str(luck_percent_increase) + "%", "luckier than the",
          num_skillful_astronauts, "people that passed the first cut")


# This provided line is required at the end of a Python file
# to call the main() function.
if __name__ == '__main__':
    main()
      
    

Drawing Friend Graphs

def draw_friend_graph(canvas, friends_file, coordinates_file):
    network = create_friend_dict(friends_file)
    coord_list = create_coord_dict(coordinates_file)
    draw_first_coords(canvas, coord_list)
    draw_connections(canvas, network, coord_list)


def create_friend_dict(filename):
    """
    This function creates a dictionary from a given file containing the names of each person in the network
    along with a list of people that they follow. The key is the name of a person in the
    network. The value is a list of people that they follow. 
    """
    network = {}
    with open(filename) as f:
        for line in f:
            line = line.strip()
            first_split = line.split(": ")
            key = first_split[0]
            friends = first_split[1].split(", ")

            # add each name to the network
            network[key] = friends

    return network


def create_coord_dict(filename):
    """
    This function creates a dictionary from a given file with each name containing a name and a list 
    of corresponding x and y coordinates. Each key in the dictionary is the name of a person in the network, 
    and the value is a list where the first item is the x coordinate for this person's node and the second 
    item is the y coordinate. This fuction returns the dictionary created. 
    """
    coord_list = {}
    with open(filename) as f:
        for line in f:
            first_split = line.split(": ")
            key = first_split[0]
            coords = first_split[1].split(", ")

            # convert from string to int
            coords[0] = int(coords[0])
            # trim the trailing whitespace and convert from string to int
            coords[1] = int(coords[1].strip())
            # update the dictionary to include this key value pair
            coord_list[key] = coords

    return coord_list


def draw_first_coords(canvas, coord_list):
    """
    This function draws a single circle for each friend in the network at it's given starting coordinate. 
    It also adds a string of text with the name of the person that the circle represents in the network. 
    """
    for name in coord_list:
        x_val = coord_list[name][0]
        y_val = coord_list[name][1]
        canvas.create_oval(x_val, y_val, x_val+10-1, y_val+10-1, fill='green')
        canvas.create_text(x_val, y_val, text=name)


def draw_connections(canvas, network, coord_list):
    """
    This function draws the lines betweens friends in the network based on the given dictionary. 
    network is a the dictionary of relationships created in create_dict
    coord_list is the dict of names and coordinates given
    """
    for name in network:
        start_x = coord_list[name][0]
        start_y = coord_list[name][1]
        for friend in network[name]:
            end_x = coord_list[friend][0]
            end_y = coord_list[friend][1]
            canvas.create_line(start_x, start_y, end_x, end_y)

First Letter Index

def first_list(strs):
    """
    Given a list of strings, create and return a dictionary whose
    keys are the unique first characters of the strings and whose
    values are lists of words beginning with those characters, in
    the same order that they appear in strs.
​
    >>> first_list(['banter', 'brahm', 'aardvark', 'python', 'antiquated'])
    {'b': ['banter', 'brahm'], 'a': ['aardvark', 'antiquated'], 'p': ['python']}
    """
    # Create an empty dictionary
    uniq_ltrs = {}
    # For each string in list of strings
    for s in strs:
        # Get the first character
        first_c = s[0]
        # Check if we've added this character to the dictionary yet
        if first_c not in uniq_ltrs:
            # Initialize the value at this key to an empty list
            uniq_ltrs[first_c] = []
        # Add the current string to this character's list
        uniq_ltrs[first_c].append(s)
    return uniq_ltrs

Cryptography

def encrypt(plaintext):
    """
    Takes in plaintext as an input and returns 'ciphertext': the result
    of substituting each letter in the plaintext by its corresponding
    encrypted character in ENCRYPTION_DICT.

    The plaintext comprises entirely of uppercase letters and non-alphabetic
    characters like punctuation. Non-alphabetic characters needn't be encrypted,
    but rather should appear in the plaintext in their original form.
    """
    output = ""

    # For each character in the unencrypted message
    for ch in plaintext:
        # If this is a character we have an encryption for, add the encryption
        if ch in ENCRYPTION_DICT:
            output += ENCRYPTION_DICT[ch]
        # Otherwise, add the character in its original form
        else:
            output += ch

    return output


def reverse_encryption_dict():
    """
    This helper function returns a dictionary that is the reverse of
    ENCRYPTION_DICT (keys and values are swapped).
    """
    # Create a new dictionary
    decryption_dict = {}

    # For each plaintext character key in the encryption dictionary
    for plaintext_char in ENCRYPTION_DICT:
        # Get the encrypted character value for this key
        encrypted_char = ENCRYPTION_DICT[plaintext_char]
        # In the new dict, add the plaintext character as the value of our encrypted key
        decryption_dict[encrypted_char] = plaintext_char

    return decryption_dict


def decrypt(ciphertext):
    """
    Uses ENCRYPTION_DICT to decrypt each of the alphabetic characters of
    ciphertext.
    """
    decryption_dict = reverse_encryption_dict()
    output = ""

    # For each character in the encrypted word
    for ch in ciphertext:
        # If this character can be decrypted, add its decryption to output string
        if ch in decryption_dict:
            output += decryption_dict[ch]
        # Otherwise, add the original character to the output string
        else:
            output += ch

    return output

Recipes

def read_dict_from_file(filename):
    """
    Takes in the name of a file containing a recipe or
    pantry list and reads it into a dictionary.

    An example doctest using the file above:
    >>> read_dict_from_file('recipe.txt')
    {'flour': 200, 'salt': 2.5}
    """
    # Create an empty dictionary
    d = {}

    with open(filename) as f:
        # For each line in the file
        for line in f:
            # Split this line into a list at '::'
            key_val = line.split(":: ")
            # Add key:value pair of ingredient:weight to our dictionary
            d[key_val[0]] = float(key_val[1])

    return d


def can_make(recipe, pantry):
    """
    Given the contents of the pantry, returns a boolean indicating
    whether or not it is possible to follow the recipe. Note that
    the parameters to this function are dictionaries, and not
    filenames. The pantry should not be modified in this function
    """
    # For each key in the recipe dictionary
    for ingredient in recipe:
        # Check if amount of ingredient in pantry is less than amount needed for recipe
        if pantry.get(ingredient, 0) < recipe[ingredient]:
            return False
    # Return True after checking every ingredient
    return True


def make_recipe(recipe, pantry):
    """
    Given a recipe and a pantry with enough ingredients to make the recipe,
    modify the contents of the pantry to remove as many quantities as the
    recipe requires. You may modify the pantry in place, but return the modified
    pantry in order to test the output using doctests.

    # using the recipe and pantry defined above
    >>> make_recipe(recipe, pantry)
    {'flour': 200, 'sugar': 300, 'salt': 7.5, 'chocolate': 150}
    """
    # For each value in recipe dictionary
    for ingredient in recipe:
        # Subtract the needed amount from this ingredient in pantry
        pantry[ingredient] -= recipe[ingredient]
    return pantry


def main():
    pantry = read_dict_from_file(PANTRY_FILENAME)
    while True:
        recipe_filename = input(
            "What recipe should we bake next (Press enter to quit.)? ")
        if recipe_filename == "":
            break
        recipe = read_dict_from_file(recipe_filename)
        if can_make(recipe, pantry):
          make_recipe(recipe, pantry)
          print("You can make that recipe! Your pantry now looks like this:")
          print(pantry)
        else:
          print("You can't make that recipe.") 

Anagrams

LEXICON = 'dictionary.txt'


def get_sorted_word(word):
    """
    Return an alphabetically sorted string made of the
    letters in word.
    """
    return "".join(sorted(word))


def load_anagram_dict():
    """
    Returns a dictionary containing key:value pairs mapping a
    sorted letter string to a list of the anagrams it can form.
    """
    # Create a new dictionary
    anagram_dict = {}

    with open(LEXICON) as f:
        for line in f:
            # Remove whitespace and newline from line
            word = line.strip()
            # Alphabetically sort this word
            sorted_word = get_sorted_word(word)
            # If we haven't added this sorted word to our dictionary before
            if sorted_word not in anagram_dict:
                # Add key sorted_word with value empty list to our dictionary
                anagram_dict[sorted_word] = []
            # Add the unsorted word to the list of anagrams with these letters
            anagram_dict[sorted_word].append(word)

    return anagram_dict

def main():
    # Get a dictionary that maps letter combinations to the anagrams they can form
    anagram_dict = load_anagram_dict()
    while True:
        word = input("Word: ")
        # Break when the user enters empty string
        if word == "":
            break
        # Alphabetically sort the entered word
        sorted_word = get_sorted_word(word)
        # If this word has anagrams, print the list of anagrams
        if sorted_word in anagram_dict:
            print(anagram_dict[sorted_word])
        else:
            print(word + " is not in the dictionary")

Big Tweet Data

def add_tweet(user_tags, tweet):
    user = parse_user(tweet)
    if user == '':
        return user_tags

    # if user is not in there, put them in with empty counts
    if user not in user_tags:
        user_tags[user] = {}

    # counts is the nested tag -> count dict
    # go through all the tags and modify it
    counts = user_tags[user]
    parsed_tags = parse_tags(tweet)
    for tag in parsed_tags:
        if tag not in counts:
            counts[tag] = 0
        counts[tag] += 1

    return user_tags

def parse_tweets(filename):
    user_tags = {}
    # here we specify encoding 'utf-8' which is how this text file is encoded
    # python technically does this by default, but it's better to be explicit
    with open(filename, encoding='utf-8') as f:
        for line in f:
            add_tweet(user_tags, line)
    return user_tags

def user_total(user_tags, user):
    """
    Optional. Given a user_tags dict and a user, figure out the total count
    of all their tags and return that number.
    If the user is not in the user_tags, return 0.
    """
    if user not in user_tags:
        return 0
    counts = user_tags[user]
    total = 0
    for tag in counts.keys():
        total += counts[tag]
    return total

def flat_counts(user_tags):
    """
    Given a user_tags dicts, sum up the tag counts across all users,
    return a "flat" counts dict with a key for each tag,
    and its value is the sum of that tag's count across users.
    """
    counts = {}
    for user in user_tags.keys():
        tags = user_tags[user]
        for tag in tags:
            if tag not in counts:
                counts[tag] = 0
            counts[tag] += tags[tag]
    return counts