Python: Function that searches through a dictionary in a dictionary - python

I have a question that goes like this
Write the contract, docstring and implementation for a function findActor that takes a movie title and a character's name and returns the actor/actress that played the given character in the given movie. If the given movie or the given character is not found, it prints out an error message and returns an empty string
I have already done the following functions that will be of assistance for doing this. And myIMDb is a global dictionary, set to an empty dic to start
def addMovie (title, charList, actList):
"""The function addMovie takes a title of the movie, a list of characters,
and a list of actors. (The order of characters and actors match one
another.) The function addMovie adds a pair to myIMDb. The key is the title
of the movie while the value is a dictionary that matches characters to
actors"""
dict2 = {}
for i in range (0, len(charList)):
dict2 [charList[i]] = actList[i]
myIMDb[len(myIMDb)] = {title: dict2}
return myIMDb
def listMovies():
"""returns a list of titles of all the movies in the global variable myIMDb"""
titles = []
for i in range (len(myIMDb)):
titles.append((list(myIMDb[i].keys())))
return titles
Here's where I'm having problems. When I want to write the findActor function I'm getting nothing to return. I'm not finished with the function but I'm thinking that i've done something fundamentally wrong. I feel like I'm going down the wrong road and I'm getting more and more lost the more I write. Here's what I have so for. Any suggestions for how to right this sinking ship would be appreciated.
def findActor(title, name):
myIMDb = {}
for i in range (len(myIMDb)):
if title == myIMDb[i].keys():
if name == myIMDb[i].get(name):
return myIMDb[i].get(name)
else:
return "Error: No Movie found"

You need to populate your myIMDB dictionary in findActor before you use it.
In addition, I'd suggest mapping myIMDB directly from the title of the move to characters. In other words, instead of doing myIMDb[len(myIMDb)] = {title: dict2} in your addMoive, you should just do myIMDb[title] = dict2.
This way, when you need to look up the title and character, you can simply do:
def findActor(title, name):
if title in myIMDb:
if name in myIMDb[title]:
return myIMDb[title][name]
return "Error: No movie found"

The first thing to learn, programming in any language, is to reduce your task to sub-tasks. Here, why not start first with creating just a dictionary of roles and actors for a single movie. If you can't do that, then you won't be able to complete the full project.
After you work on that, maybe everything else will fall into place.
Warning: in the real world, occasionally more than one actor may play a role - for example - a role in which a child matures into adulthood. But that likely is not in your spec.

Related

Case insensitive Full Name dictionary search

I am creating a dictionary with "Full Name": "Birthday" for numerous people as an exercise.
The program should ask
"Who's birthday do you want to look up?"
I will input a name, say "Benjamin Franklin"
And it will return his birthday: 1706/01/17.
Alright, the problem I am encountering is name capitalization.
How can I input "benjamin franklin" and still find "Benjamin Franklin" in my dictionary? I am familiar with .lower() and .upper() functions, however I am not able to implement them correctly, is that the right way to approach this problem?
Here is what I have
bday_dict = {"Person1": "YYYY/MM/DD1",
"Person2": "YYYY/MM/DD2",
"Benjamin Franklin": "1706/01/17"}
def get_name(dict_name):
name = input("Who's birthday do you want to look up? > ")
return name
def find_bday(name):
print(bday_dict[name])
find_bday(get_name(bday_dict))
The best way to do this is to keep the keys in your dictionary lowercase. If you can't do that for whatever reason, have a dictionary from lowercase to the real key, and then keep the original dictionary.
Otherwise, Kraigolas's solution works well, but it is O(N) whereas hashmaps are supposed to be constant-time, and thus for really large dictionaries the other answer will not scale.
So, when you are setting your keys, do bday_dict[name.lower()] = value and then you can query by bday_dict[input.lower()].
Alternatively:
bday_dict = {"John": 1}
name_dict = {"john": "John"}
def access(x):
return bday_dict[name_dict[x.lower()]]
Probably the most straight forward way I can think of to solve this is the following:
def get_birthday(name):
global bday_dict
for person, bday in bday_dict.items():
if name.lower() == person.lower():
return bday
return "This person is not in bday_dict"
Here, you just iterate through the entire dictionary using the person's name paired with their birthday, and if we don't find them, just return a message saying we don't have their birthday.
If you know that all names will capitalize the first letter of each word, you can just use:
name = ' '.join([word.capitalize() for word in name.split()])
then you can just search for that. This is not always the case. For example, for "Leonardo da Vinci" this will not work, so the original answer is probably the most reliable way to do this.
One final way to do this would be to just store the names as lowercase from the beginning in your dictionary, but this might not be practical when you want to draw a name from the dictionary as well.
Depending what your exercise allows, I would put the names in the dictionary as all lowercase or uppercase. So:
bday_dict = {"person1": "YYYY/MM/DD1",
"person2": "YYYY/MM/DD2",
"benjamin franklin": "1706/01/17"}
And then look up the entered name in the dictionary like this:
def find_bday(name):
print(bday_dict[name.lower()])
You may also want to do a check that the name is in the dictionary beforehand to avoid an error:
def find_bday(name):
bday = bday_dict.get(name.lower(), None)
if bday:
print(bday)
else:
print("No result for {}.".format(name))

Pythonic way to test single-element lists to avoid IndexErrors

Throughout my codebase there is a design pattern in which we have two lists of objects and try to whittle it down to one object.
Say we have two classes,
class Employee():
def __init__(self, _id, name):
self._id = _id
self.name = name
class Shift():
def __init__(self, employee_id, shift_id):
self.employee_id = employee_id
self.shift_id = shift_id
We have lists of objects of these classes. I want to find the employee who has a shift with their id attached to it. Suppose I have the list employees containing Employee objects, and the list shifts containing Shift objects, then I do:
for shift in shifts:
# Find employee who is assigned to this shift
employee = [e for e in employees if e._id == shift.employee_id]
So far so good. There's no guarantee that the employees_with_shift will contain an employee though. If there is an employee, there's only one. Currently it's being handled like this:
if employee:
employee = employee[0]
... do something
But I don't think this is Pythonic. A simple solution would be to:
for e in employee:
...do something
I don't know if this is unpythonic, but it does handle the case smoothly. Is it wrong to use a for-loop on lists that have either zero or one elements?
The other one is to go by AFNP and do this:
try:
employee = employee[0]
... do something
except IndexError:
pass
I don't like this though because there is quite a lot of coding to do on the employee, and the error handling would get extremely complicated.
Which of my solutions (if any) is the most pythonic?
EDIT:
This question is not answered by the one in the close suggestion, because this question looks for the most pythonic way to handle the element of a list that contains either 0 or 1 elements.
Instead of first creating a list that will contain either 0 or 1 items and then unpacking the list again, I would use next to find the first employee matching the condition. If there is none, this will raise StopIteration, because the iteration of the generator expression passed into next is exhausted:
# Find employee who is assigned to this shift
try:
employee = next(e for e in employees if e._id == shift.employee_id)
except StopIteration:
# no such employee
However, why don't you just have a dictionary mapping employees by their ID?
Then you could simply write:
try:
employee = employees[shift.employee_id]
except KeyError:
# no such employee
And then you should ask yourself how it could happen that a shift was assigned an employee that doesn't exist. Maybe it's because no employee was assigned to the shift and shift.employee_id is None? Then LBYL would in fact be clearer IMO:
if shift.employee_id is None:
# no employee was assigned yet
return # or continue, break, ...
# now this must succeed, if not there is a bug
assert shift.employee_id in employees
employee = employees[shift.employee_id]
If you are searching for a single object in a list, and you expect it to either be in the list or not (and not multiple possible values), then don't create a list in the first place. The Pythonic thing to do would be simply:
for employee in employees:
if e._id == shift.employee_id:
# handle employee
break
else:
# handle the case where no employee is found, else clause not necessery
# if you simply want to pass
Probably the better design overall is to have a dictionary mapping employee id's to employees so you can handle it like this:
try:
employee = employee_dict[shift.employee_id]
except KeyError:
# handle not found case
Borrowing from #mkrieger1's answer, next() takes a default return value, so you can just do
employee = next(e for e in employees if e._id == shift.employee_id,None)
This will default employee to None for example.
A dictionary mapping would indeed also be great:
You could avoid the try..except by using .get():
employee = employees.get(shift.employee_id,None)
You don't say what you want employee to default to if it's absent in the shift list though.
I suspect you're already at the most pythonic point, barring an architecture restructuring, but you could always turn it into one line:
employee = employee[0] if bool(employee) else None
# or this is perhaps more pythonic
employee = employee[0] if employee else None
# I dislike using implicit booleans because of type ambiguity.
It's not much different to what you're already doing, but it would look better, and that might be enough here!
EDIT: I agree with the other answers about using a dictionary. My answer applies if you really can't do that

Nested for loops iterating over multiple dictionaries, is there an easier way?

So my lazy rear is trying to create a meal plan generator in python 3.x so I don't have to spend 30min every Friday figuring out what I should eat, but I'm new to coding and struggling with something. I'm still going through the Udemy course, but I wanted to get my fingers dirty with code to properly learn. Anywho, here's what I've got so far:
class Meals():
def __init__(self,breakfast,lunch,dinner):
self.breakfast=breakfast
self.lunch=lunch
self.dinner=dinner
def __str__(self):
return f"Breakfast will be {self.breakfast}.\nLunch will be {self.lunch}.\nDinner will be {self.dinner}."
def cost(self):
day_meals=[self.breakfast,self.lunch,self.dinner]
day_cost=0
for i in day_meals:
for ingredient in i:
for key,value in Shop2.items():
if key in ingredient:
day_cost+=value
return f"Today will cost £{round(day_cost,2)}."
If I do:
monday_meals=Meals(random.choice(list(breakfasts.keys())),random.choice(list(lunches.keys())),random.choice(list(dinners.keys())))
And then call monday_meals.breakfast, then I get the result I want, the randomly chosen key from the 'breakfast' dictionary, but whenever I call for:
monday_meals.cost()
then I get £0 with no errors showing.
For reference, my testing dictionaries are as follows:
breakfasts={"a bowl of Rice Crispies":["cereal_1","milk"],"Weetabix":["cereal_2","milk"],"some Golden Grahams":["cereal_3","milk"],"toast":["bread","butter"],"scrambled eggs":["egg","egg","milk"]}
lunches={"cereal bars":["cereal_bar","cereal_bar"],"a boring ham sandwich":["bread","ham"],"some fruit":["banana","apple"],"salad":"salad_bag"}
dinners={"Student Meal #1":["mince","red_sauce","pepper","onion"],"Student Meal #2":["c_breast","white_sauce","onion","pepper"],"Student Meal #3":["egg","pepper","tomato","onion"]}
Shop2={"egg":0.3,"tomato":0.35,"pepper":0.33,"onion":0.4,"mince":1.2,"c_breast":0.7,"rice":0.8,"red_sauce":1.4,"white_sauce":1.5,"cereal_1":0.4,"milk":0.13,"cereal_2":0.35,"cereal_3":0.45,"bread":0.04,"butter":0.04,"cereal_bar":0.75,"ham":0.25,"banana":0.3,"apple":0.3,"salad":0.75}
I'd really appreciate any help with finding an easier way to calculate the cost of a day's meals.
You can implement your design with:
# inside your class:
#staticmethod
def calculate_meal_cost(ingredients, shopdict):
return sum(shopdict[ingredient] for ingredient in ingredients)
#property
def cost(self):
breakfast_cost = self.calculate_meal_cost(breakfasts[self.breakfast], Shop2)
lunch_cost = self.calculate_meal_cost(lunches[self.lunch], Shop2)
dinner_cost = self.calculate_meal_cost(dinners[self.dinner], Shop2)
return breakfast_cost + lunch_cost + dinner_cost
Then:
meal = Meal(...) # however you pick your meals is fine
meal.cost # note: no parens
The problem is in what you pass in when you instantiate the class. breakfasts.keys() just gives you the keys of the dict, as the name implies: the keys are the things to the left of the colon, eg "a bowl of Rice Crispies". The actual ingredients are the values, but these never get sent to the Meals instance; so when you iterate through the "ingredients" you're actually iterating through the letters of the key.
You could fix this by using .values() instead of .keys() there, although a nicer way might be to pass both key and value so that your __str__ method outputs the description, not the ingredients; I'll leave that as an exercise...
Your Meals object is being initialized with the String name of the meal (e.g. random choice of breakfasts.keys() might be "Weetabix").
When you iterate through day_meals and do "ingredient in i", you are actually iterating through each "character" of Weetabix, so your ingredients would be "W" "e" "e" .. and so on.
Instead, you may want to initialize Meals with a choice of breakfasts.items(). Then you would have a tuple, e.g. ("Weetabix",["cereal_2","milk"]), in self.breakfast.
You can then unpack this in your loop:
for name,ingredients in day_meals:
for i in ingredients:
# and so on..
After you create Meals object, you have to call cost method. For now you only create object. Try:
monday_meals=Meals(random.choice(list(breakfasts.keys())),random.choice(list(lunches.keys())),random.choice(list(dinners.keys())))
print monday_meals.cost()
print monday_meals

Simplifying a list into categories

I am a new Python developer and was wondering if someone can help me with this. I have a dataset that has one column that describes a company type. I noticed that the column has, for example, surgical, surgery listed. It has eyewear, eyeglasses and optometry listed. So instead of having a huge list in this column, i want to simply the category to say that if you find a word that contains "eye," "glasses" or "opto" then just change it to "eyewear." My initial code looks like this:
def map_company(row):
company = row['SIC_Desc']
if company in 'Surgical':
return 'Surgical'
elif company in ['Eye', 'glasses', 'opthal', 'spectacles', 'optometers']:
return 'Eyewear'
elif company in ['Cotton', 'Bandages', 'gauze', 'tape']:
return 'First Aid'
elif company in ['Dental', 'Denture']:
return 'Dental'
elif company in ['Wheelchairs', 'Walkers', 'braces', 'crutches', 'ortho']:
return 'Mobility equipments'
else:
return 'Other'
df['SIC_Desc'] = df.apply(map_company,axis=1)
This is not correct though because it is changing every item into "Other," so clearly my syntax is wrong. Can someone please help me simplify this column that I am trying to relabel?
Thank you
It is hard to answer without having the exact content of your data set, but I can see one mistake. According to your description, it seems you are looking at this the wrong way. You want one of the words to be in your company description, so it should look like that:
if any(test in company for test in ['Eye', 'glasses', 'opthal', 'spectacles', 'optometers'])
However you might have a case issue here so I would recommend:
company = row['SIC_Desc'].lower()
if any(test.lower() in company for test in ['Eye', 'glasses', 'opthal', 'spectacles', 'optometers']):
return 'Eyewear'
You will also need to make sure company is a string and 'SIC_Desc' is a correct column name.
In the end your function will look like that:
def is_match(company,names):
return any(name in company for name in names)
def map_company(row):
company = row['SIC_Desc'].lower()
if 'surgical' in company:
return 'Surgical'
elif is_match(company,['eye','glasses','opthal','spectacles','optometers']):
return 'Eyewear'
elif is_match(company,['cotton', 'bandages', 'gauze', 'tape']):
return 'First Aid'
else:
return 'Other'
Here is an option using a reversed dictionary.
Code
import pandas as pd
# Sample DataFrame
s = pd.Series(["gauze", "opthal", "tape", "surgical", "eye", "spectacles",
"glasses", "optometers", "bandages", "cotton", "glue"])
df = pd.DataFrame({"SIC_Desc": s})
df
LOOKUP = {
"Eyewear": ["eye", "glasses", "opthal", "spectacles", "optometers"],
"First Aid": ["cotton", "bandages", "gauze", "tape"],
"Surgical": ["surgical"],
"Dental": ["dental", "denture"],
"Mobility": ["wheelchairs", "walkers", "braces", "crutches", "ortho"],
}
REVERSE_LOOKUP = {v:k for k, lst in LOOKUP.items() for v in lst}
def map_company(row):
company = row["SIC_Desc"].lower()
return REVERSE_LOOKUP.get(company, "Other")
df["SIC_Desc"] = df.apply(map_company, axis=1)
df
Details
We define a LOOKUP dictionary with (key, value) pairs of expected output and associated words, respectively. Note, the values are lowercase to simplify searching. Then we use a reversed dictionary to automatically invert the key value pairs and improve the search performance, e.g.:
>>> REVERSE_LOOKUP
{'bandages': 'First Aid',
'cotton': 'First Aid',
'eye': 'Eyewear',
'gauze': 'First Aid',
...}
Notice these reference dictionaries are created outside the mapping function to avoid rebuilding dictionaries for every call to map_company(). Finally the mapping function quickly returns the desired output using the reversed dictionary by calling .get(), a method that returns the default argument "Other" if no entry is found.
See #Flynsee's insightful answer for an explanation of what is happening in your code. The code is cleaner compared a bevy of conditional statements.
Benefits
Since we have used dictionaries, the search time should be relatively fast, O(1) compared to a O(n) complexity using in. Moreover, the main LOOKUP dictionary is adaptable and liberated from manually implementing extensive conditional statements for new entries.

need help making a dictionary

Im still new to python and trying to teach myself and at the same time tying to make make a FIFA 14 autobuyer. Ive been working with an already made Library https://github.com/Fire30/Fifa14Client
What I'm trying to do is make a dictionary out of the information I get when searching for a specific card
The function used to search for a card is in WebAppFunctioner.py file and is
def search(self, type="player", lev="gold", pos="CAM", num=1, team="101059",
macr="", micr="", minb="", nat="", maxb="",
playStyle="", leag="", start=50, cat="",
definitionId="", maskedDefId=""):
the_url = self.TRANSFER_URL % (self.platform_string,type, lev, pos, num, team, macr, micr, minb, nat, maxb,
playStyle, leag, start, cat, definitionId, maskedDefId)
r = requests.post(the_url, headers=self.get_headers('GET'))
try:
json = r.json()
except:
raise BadRequestException("Could not complete search. No JSON object could be decoded")
if 'auctionInfo' in json:
card_list = json['auctionInfo']
return [Card.Card(card_dict) for card_dict in card_list]
elif 'code' in json:
raise FUTErrorCodeException("Could not get complete search.",json)
when I call function, print(func.search()), it makes a list of objects and prints each object in the terminal. Each object has a bunch of information in it and I am trying to take all the information and store it an a dictionary. I think that Card.py has exactly what I need except I dont really understand how to use it.
If the request is in fact valid the function should return a list of card dictionaries.
return [Card.Card(card_dict) for card_dict in card_list] is a list comprehension, which returns one or more of the results of card_dict (presumably the return value is a dict object) for each card that was in the auctionInfo json.
You shouldn't be expecting a dictionary back, but rather a list of dictionaries. If there is some unique identifier you could edit the code to be
if 'auctionInfo' in json:
card_list = json['auctionInfo']
return {some_unique_id: Card.Card(card_dict) for card_dict in card_list}
Edit: I had curly braces arount the some_unique_id k:v pair, that would have broken it.
Alternative: leave the function as is and handle it on the other side and just do a quick comprehension like:
{card.unique_id_thing:card for card in search()}

Categories