I want to write a function which removes a user id from a dictionary. This is for a H.W assignment and it is one of the function for a movie recommender program I have to write.
def remove_unknown_movies(user_ratings, movies):
"""Modify the user_ratings dictionary so that only movie ids that are in
the movies dictionary is remaining. Remove any users in user_ratings that
have no movies rated.
>>> small_ratings = {1001: {68735: 5.0, 302156: 3.5, 10: 4.5}, 1002: {11: 3.0}}
>>> remove_unknown_movies(small_ratings, MOVIE_DICT_SMALL)
>>> len(small_ratings)
1
>>> small_ratings[1001]
{68735: 5.0, 302156: 3.5}
>>> 1002 in small_ratings
False
"""
for user_id in user_ratings.items():
for movie_id in movies.items():
if movie_id not in user_id:
del user_ratings[user_id[0]]
Note that MOVIE_DICT_SMALL = {68735: ('Warcraft', ['Action', 'Adventure', 'Fantasy']), 293660: ('Deadpool', ['Action', 'Adventure', 'Comedy']), 302156: ('Criminal', ['Action']), 124057: ('Kids of the Round Table', [])}
Expected output (as per docstring example): len(small_ratings) = 1
Actual output:
builtins.KeyError: 1001
What am I doing wrong and how can I get the desired output?
3 Answers 3
Code below is what you want. Remember, when you want to change dict during for loops, you should first convert it to list. Or you will get error:dictionary changed size during iteration
small_ratings = {1001: {68735: 5.0, 302156: 3.5, 10: 4.5}, 1002: {11: 3.0}}
MOVIE_DICT_SMALL = {
68735: ('Warcraft', ['Action', 'Adventure', 'Fantasy']),
293660: ('Deadpool', ['Action', 'Adventure', 'Comedy']),
302156: ('Criminal', ['Action']),
124057: ('Kids of the Round Table', [])
}
for uid, uscores in list(small_ratings.items()):
for mid in list(uscores):
if mid not in MOVIE_DICT_SMALL.keys():
del small_ratings[uid][mid]
if small_ratings[uid] == {}:
del small_ratings[uid]
print(small_ratings)
output
{1001: {68735: 5.0, 302156: 3.5}}
2 Comments
The problem is on this line
del user_ratings[user_id[0]]
When you are on this line you delete an element in the dictionary. In the next iteration of the loop you try again to access this same element (i.e. user_ratings[user_id[0]]) but since this element is deleted you get a key error.
KeyError: 1001
This says that the key 1001 is not in your dictionary.
You can maintain a copy of this dict and delete items only from the copy and use the original to iterate over. Or, you can put in a try - catch block. But the simplest method would to check if the element is in dict before accessing it using if -else
Comments
So, I am a little unsure of exactly what you need to accomplish with this single function... It seems that you not only need to remove any irrelevant users from the small_ratings dict, but also make sure to remove any irrelevant movies from users ratings list.
Using dict.items() returns two values to unpack: keys, and values. You are only dealing with the returned keys. You can call just keys() or values(), but calling .items() is usually to deal with keys and values, like:
for k, v in d.items():
key, value = k, v
Also, in Python 3, the default behavior when removing keys while iterating over a dictionary has changed -- you will get an error message if you remove the key while iterating.
So, you can do a dict.copy() of your dictionary value, and return the modified copy that you've deleted from, or you can do something like this example, where you create an empty list and append items marked for deletion to it, then iterate over your dictionary object and delete the keys with del.
#!/usr/bin/env python3
MOVIE_DICT_SMALL = {
68735: ('Warcraft', ['Action', 'Adventure', 'Fantasy']),
293660: ('Deadpool', ['Action', 'Adventure', 'Comedy']),
302156: ('Criminal', ['Action']),
124057: ('Kids of the Round Table', [])
}
small_ratings = {1001: {68735: 5.0, 302156: 3.5, 10: 4.5}, 1002: {11: 3.0}}
def remove_users(user_ratings, movies):
delete = []
for user_id in user_ratings:
if not any(movie_id in movies.keys()
for movie_id in user_ratings[user_id].keys()):
delete.append(user_id)
for user in delete:
del user_ratings[user]
def remove_movies(user_ratings, movies):
for user_id in user_ratings:
delete = []
for movie_id in user_ratings[user_id].keys():
if movie_id not in movies.keys():
delete.append(movie_id)
for movie in delete:
del user_ratings[user_id][movie]
remove_users(small_ratings, MOVIE_DICT_SMALL)
print(small_ratings) # {1001: {68735: 5.0, 302156: 3.5, 10: 4.5}}
remove_movies(small_ratings, MOVIE_DICT_SMALL)
print(small_ratings) # {1001: {68735: 5.0, 302156: 3.5}}