class ReadingClub: def __init__(self): self.readers_to_books = {} self.books_to_readers = {} def rate(self,reader,book,rating): if reader in self.readers_to_books: # add rating to readers_to_books self.readers_to_books[reader][book] = rating else: self.readers_to_books[reader] = { book : rating } # { key : value } is a convenient way to create a dictionary # with just one key and value. Alternatively, you can # assign {}, then add the key,value to it. # In general, you can also create it with several keys: # { key1:value1, key2:value2, ... , key10:value10 } # add rating to books_to_readers if book in self.books_to_readers: self.books_to_readers[book][reader] = rating else: self.books_to_readers[book] = { reader : rating } def who_read(self,book): return list(self.books_to_readers[book]) def what_read(self,reader): return list(self.readers_to_books[reader]) def recommend(self,reader): """returns a recommendation of a book for reader. Implements simple strategy: the book with highest average rating among those that reader has not read. It returns the empty string if no such book exists""" best_book = "" # we return this "book" unless we find something better best_book_rating = -1 # any book with any rating beats this one for b in self.books_to_readers: # if reader has read b, do not consider it if not b in self.readers_to_books[reader]: #get the list of ratings for the book rating_list = self.books_to_readers[b].items() # but the ratings are pairs (reader,ratings) # we need to collect the ratings to then average them rating_sum = sum(rating for reader, rating in rating_list) avg_rating = rating_sum / len(rating_list) if avg_rating > best_book_rating: best_book = b best_book_rating = avg_rating return best_book # some testing code r = ReadingClub() r.rate("pete","ulisses",5) r.rate("pete","catcherintherye",4) r.rate("pete","odissea",3) r.rate("pete","quixot",5) r.rate("pete","laregenta",3) r.rate("anna","quixot",2) r.rate("anna","ulisses",4) r.rate("anna","iliada",2) r.rate("anna","odissea",5) r.rate("anna","laregenta",4.5) r.rate("laura","iliada",2) r.rate("laura","catcherintherye",2) r.rate("laura","tirant",3) r.rate("laura","ulisses",2) r.rate("laura","iliada",4) r.rate("laura","pandorasseed",3) print(r.who_read("ulisses")) print(r.who_read("catcherintherye")) print(r.what_read("anna")) print(r.what_read("pete")) print(r.what_read("laura")) print(r.recommend("laura")) print(r.recommend("pete")) print(r.recommend("anna")) ##################################################### Now suppose we want to add operation def average_rating(self,book) We want to keep an additional dictionary self.avg_ratings that maps every book to its average rating. Since the average cannot be easily updated with a new rating, we actually keep pairs (sum_of_ratings,number_of_ratings) __int__ should initialize self.avg_ratings = {} rate should update s,n = self.avg_ratings[book] self.avg_ratings[book] = (s+rating,n+1) and average_rating is def average_rating(self,book): if book in self.avg_ratings: return self.avg_ratings(book) else: # indicating wrong operation return -1 Also, in recommend, we can now replace the lines that compute the average rating of a book, rating_list = self.books_to_readers[b].items() rating_sum = sum(rating for reader, rating in rating_list) avg_rating = rating_sum / len(rating_list) with avg_rating = average_rating(book) which is O(1) instead of O(number of ratings of the book). This tells us that it would have been a good idea from the start to have this operation, for efficiency, even if no one had asked for it "from outside".