# Module 9 Practice Problems

# Part 1 - Warm up questions

# Complete the Python function common that consumes two 
# dictionaries (d1 and d2, with common key and value types).
# Return a list of all the keys (in increasing order)
# that occur in both d1 and d2 and have the same 
# associated values in each. For example, 
# common({1:'1', 2:'2', 4:'4'}, {3:'3', 2:'2', 1:'1', 4:'four'})
# => [1,2]

def common(d1,d2):
    pass


# Definition of Movie class - note all the pieces (the class definition
# includes a function to complete as well. 
# Problems follow the class definition and are based on 
# Module 09, slides 14 through to the end (including the new slides 
# on class functions or methods). 


class Movie:
    '''fields: title (Str), 
               year (Nat),
               stars (listof Str)
    requires: year is the year the movie was released (> 1900)
              stars includes the names of the actors in the movie'''
    
    def __init__(self, name, date, actors):
        self.title = name
        self.year = date
        self.stars = actors
    
    def __repr__(self):
        return "{0}({1}) featuring {2}".format(
            self.title, self.year, self.stars)
    
    def __eq__(self, other):
        return isinstance(other, Movie) and \
               self.title == other.title and \
               self.year == other.year and \
               self.stars == other.stars
    
    def add_star(self, star):
        pass
# End of Movie class definition

# Sample movies
princess_bride = Movie("Princess Bride", 1987, 
                       ["Cary Elwes", "Robin Wright", "Mandy Patinkin"])
singin_in_the_rain = Movie("Singin' in the Rain", 1952,
                           ["Gene Kelly", "Debbie Reynolds", "Donald O'Connor"])
dirty_dancing = Movie("Dirty Dancing", 1987,
                      ["Patrick Swayze", "Jennifer Grey"])
flicks = [princess_bride, singin_in_the_rain, dirty_dancing]


# Complete the class function add_star that consumes a Movie object
# and a string, star, and add the star's name to the end of the movie's
# stars list. In addition, the function returns the number of actors
# listed under stars. 
# For example, if princess_bride is
# Movie("Princess Bride", 1987,["Cary Elwes", "Robin Wright", "Mandy Patinkin"])
# then princess_bride.add_star("Andre the Giant") => 4, 
# and mutates princess_bride to 
# Movie("Princess Bride", 1987,["Cary Elwes", "Robin Wright", "Mandy Patinkin", "Andre the Giant"])
# Remember to test add_star outside the Movie class, even though the function
# is defined inside the class. 




# Complete the function common_stars that consumes two movies and
#returns True if there is any actor who is in both movies, and
# False otherwise.
# For example,
# common_stars(Movie("Princess Bride", 1987, 
#                      ["Cary Elwes", "Robin Wright", "Mandy Patinkin"]),
#              Movie("Alien Nation", 1988, ["James Caan", "Mandy Patinkin"]))
# => True

def common_stars(movie1, movie2):
    pass

# Part 2 - Extra Practice

#
# Problem 1:
# Consider a number/word dictionary that has entries of the form:
#                 n : word_for_n
# where n is a natural number, and word_for_n is the 
# written word corresponding to the value n.
# For example, an english version of such a dictionary might be
# eng = { 1:"one", 10:"ten", 21:"twenty-one", 2:"two", 99:"ninety-nine"}
# and a French version might be
# fr = { 2:"deux", 10:"dix", 1:"un", 3:"trois", 21:"vingt-et-un"}
#
# Now, suppose you want to combine them, into a
# single dictionary, with entries of the form:  n : t
# where translation is an object of the type Translation,

#
# Write a function create_translation which consumes two
# number/word dictionaries (one in English and one 
# in French) and returns a number/Translation dictionary. 
# You can assume: same keys in both consumed dictionaries
#

class Translation:
    "Fields: english, french"
    
    def __init__(self, eng, fr):
        self.english = eng
        self.french = fr
        
    def __repr__(self):
        return "English: {0}, French {1}".format(self.english, self.french)
    
    def __eq__(self, other):
        return type(self)==type(other) and \
               self.english==other.english and \
               self.french==other.french
# End of Translation

def create_translation(d1, d2):
  pass

# Problem 2: The following functions all consume two dictionaries with 
# a common key type.
# a) Write a function to merge_common that consumes two dictionaries with a 
#    common key type, and return a dictionary containing the common keys. 
#    The associate values should be [v1,v2] where v1 is the associated value for
#    the common key in d1, and v2 is the associate value in d2.2.

def merge_common(d1,d2):
    pass

# b) Write a function that returns the list of keys in either d1 or d2 (i.e. 
#    the union of all the keys).

def union(d1,d2):
    pass

# c) Write a function that returns the list of all keys in exactly one of the
#    dictionaries.

def exactly_one(d1,d2):
    pass

# d) Assume the two dictionaries have the same set of keys. Write a function
#    that returns a new dictionary with those common keys, but which has the 
#    associated value True if the two values for key k are the same in the two
#    dictionaries, and False otherwise.

def combine(d1,d2):
    pass

# Problem 3: 
# Write a function total_population that consumes a list of Country objects and 
# returns the total population of all countries in the list. 

def total_population(countries):
    pass

# Problem 4 
# A movie_info is a class containing information about a movie,
# including its title (a string), a genre (the type of movie, a string),
# its stars (a list of strings for the actors' names), 
# and its rating (a float between 0 and 10, where 10 is the top rating,
# and corresponds to the imdb rating)

class Movie_Info:
    '''Fields: title, genre, stars, rating
    where title is a Str for the movie title
        genre is a Str for the movie category (comedy, drama, etc.)
        stars is a (listof Str) for the people starring in the movie
        rating is a Float between 0 and 10'''
  
    def __init__(self, t,g,s,r):
        self.title = t
        self.genre = g
        self.stars = s
        self.rating = r
    
    def __repr__(self):
        return "{0} ({1}) {2} [{3}]".format(self.title, self.genre, self.stars, self.rating)
    
    def __eq__(self, other):
        return type(self) == type(other) and \
               self.title==other.title and self.genre==other.genre \
               and self.stars==other.stars and abs(self.rating - other.rating)<=0.00001
    
# a) Initialize several movie objects, including one for "Casablanca",
#    a "drama", starring Humphrey Bogart and Ingrid Bergman, with an 
#    8.8 rating on imdb, and "Witness", a "thriller", starring Harrison Ford, 
#    Kelly McGillis, Lukas Haas, with a 7.6 rating.

# b) Write a function build_movie_dictionary that consumes a list of 
#    movie_info objects and builds a movie dictionary, with the movie
#    titles as the keys and the movie_info objects as the associated values.

def build_movie_dictionary(films):
    pass

# c) Write a function update_rating that consumes a movie dictionary,
#    a movie title (that is in the dictionary), and a new rating, and mutates
#    the dictionary to update the movies rating.

def update_rating(movie_db, title, new_rating):
    pass

# d) Write a function better_than that consumes a movie dictionary,
#    and a float, cut_off, and returns a list of all the movies in the
#    dictionary with at least that rating. 
#    For example, if the dictionary contains information about "Casablanca"
#    and "Witness", and better_than is called with cut_off 8.0, better_than 
#    should return a list containing the information about "Casablanca" 
#    only.

def better_than(movie_db, cut_off):
    pass


# e) Write a class function update_rating that consumes a Movie
#    (called self inside the class) and a Float (called new_rating)
#    and updates the rating field of self to new_rating if 
#    new_rating is between 0 and 10, inclusive. If new_rating < 0,
#    update the rating to 0. If new_rating > 10, update the rating
#    to 10. 

     # Place this function inside the Movie class, after the
     # "magic functions" init, repr, and eq. 
     
     def update_rating(self, new_rating):
         pass
     
# f) Write a function upgrade_comedy that consumes movie dictionary
#    and a positive float inc, and increases the rating of all movies
#    with genre "comedy" by inc, to a maximum of 10. Be sure to use
#    the class function update_rating defined in part e.

def upgrade_comedy(movie_db, inc):
    pass

# g) Write a function starring_in which consumes a movie dictionary
#    and an actor's name, and returns a list of all the titles of all
#    the movies that actor is in.

def starring_in(movie_db, actor):
    pass

# h) Write a function genre_averages that consumes a movie dictionary 
#    and produces a dictionary of average ratings by genres in the dictionary. 
#    For example, if the dictionary includes only "Casablanca" and "Witness", 
#    genre_averages would produce the dictionary
#    {'drama':8.8, 'thriller':7.6}. 

def genre_average(movie_db):
    pass

# Problem 5
# Consider the book structure:

class book:
    '''Fields: title, author, pages
    A book is an object with three fields:
    title - a string for the book title
    author - a string for the author of the book
    pages - an positive int for the number of pages in the book'''
    
    def __init__(self, t,a,p):
      self.title = t
      self.author = a
      self.pages = p
    
    def __repr__(self):
      return "{0} by {1} contains {2} pages".format(self.title, self.author, self.pages)
    
    def __eq__(self, other):
      return type(self)==type(other) and self.title==other.title and \
             self.author==other.author and self.pages==other.pages

# Write a function long_books_written_by that consumes a library (a list of book structures), 
# an author's name, and a book_length (a positive int),  and returns a list of
# all the titles written by a specified author which have at least that number of pages.

def long_books_written_by(library, author, book_length):
    pass

# Problem 6 
# Consider the new class Student - 
# A Student is an object with three fields - 
# a string name, an 8-digit integer for the id,
# a string for the major area of study, a positive
# float corresponding to the number of credits passed, 
# and a float (between 0 and 100) for the student's
# overall average.

class Student: 
    'Fields: name, id, major, credits, cav'
    def __init__(self, n,i,m,c,av):
      self.name = n
      self.id = i
      self.major = m
      self.credits = c
      self.cav = av
      
    def __repr__(self):
      return "{0}({1}): {2} credits, {3}% cav".format(self.id, self.major, self.credits, self.cav)
    
    def __eq__(self, other):
      return type(self)==type(others) and self.id==other.id and self.major==other.major and\
             abs(self.credits-other.credits)<=0.00001 and \
             abs(self.cav-other.cav)<=0.00001
    
# a) 
# Write a class function update_major that consumes a Student object (called self), 
# a string (called new_major), and a Boolean (called double), and updates the 
# value of self.major. If double is True, self.major is set to a list of length 2,
# containing the current value of self.major in position 0 and new_major in position 1. 
# If double is False, then new_major replaces the current value of self.major. 
# If double if True, but the student is already in two majors, then no change
# is made to the student's plan. (We'll ignore triple majors here.)

     # Place inside Student class, after magic functions init, repr, and eq.
     def update_major(self, new_major, double):
         pass
#
# b)
# Write a function create_student_dictionary that consumes a list of Student objects
# and returns a dictionary with the student information. The keys in the dictionary
# are the unique student IDs, and the values in the dictionary are the Student objects.

def create_student_dictionary(classlist):
    pass

# b)
# Write a function to averages_by_year, that consumes a dictionary
# of Students indexed by id number, and returns a dictionary (indexed by 1,2,3,4)
# containing the averages of all students in the enrolled dictionary
# in first, second, third, and fourth year, respectively. 
# Mote that:
# first year ... up to 5.0 credits
# second year .. more then 5.0 credits, up to 10.0 credits
# third year ... more than 10.0 credits, up to 15.0 credits
# fourth year .. more than 15.0 credits. 
# Produce the averages in a dictionary with keys 1,2,3,4.

def averages_by_year(students):
    pass

# Problem 7
# Consider the class
class Class_Information: 
    ''''Fields: title, instructor, class_list
    A Class_Information object contains
    - a string title (uniquely identifies the course title)
    - an string instructor 
    - a listof Student for all students enrolled in the class'''
    def __init__(self, t,i,cl):
      self.title = t
      self.instructor = i
      self.class_list = cl
    
    def __repr__(self):
      return "{0} taught by {1}, Enrolled: {2}".format(self.title, self.instructor, self.class_list)
    
    def __eq__(self, other):
      return type(self)==type(other) and self.title==other.title and\
             self.instructor==other.instructor and\
             self.class_list==other.class_list

# Write a function students_by_major that consumes a Class_Information object
# and a string representing a major plan of study, and returns the list of the id numbers
# for all students registered in the class with that plan of study.
# If a student is a double major (as described in Problem 6 (a)), include the
# the student in the list for both of their majors.

def students_by_major(a_class, a_major):
    pass

# Problem 8
# A classlist is (list title students), where
# title is a nonempty string giving the course title, and
# students is a list of id numbers of students enrolled in 
#          in the course (i.e. (listof int[>0]))
#
# Write a function enrolled, that consumes a (listof classlist) and
# returns a dictionary where
# * the keys are the student id numbers, and
# * the associated value for each key is the list of course titles the 
#   the student is enrolled in. 
#
# For example,
# enrolled([ ["cs116", [111, 222, 145, 345]], 
#            ["m138", [145, 444, 222] ],
#            ["m135", [333, 555, 111, 222]] ])
# => { 111:["cs116", "m135"], 222: ["cs116", "m135","m138"], 145: ["cs116", "m138"],
#      345:["cs116"], 444: ["m138"], 333:["m135"], 555:["m135"] }

def enrolled(all_classes):
    pass
