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".