Case insensitive Full Name dictionary search - python

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))

Related

Quicker solution to the problem with loops and dictionaries

I have solved one issue recentely and I know for sure that there must be quicker solution to the problem. I am just learning and I want to write efficient code so it would be helpful if you could suggest me anything.
I want this output (which I got):
Jozefína likes to go to here: Amsterdam ,Liverpool ,London.
Marek likes to go to here: Košice.
Jozef likes to go to here: Lisabon ,Vancouver.
PS: names of people and cities are totaly random so you can change it
And my code for this problem is:
favourite_places = {
"Jozefína": ["Amsterdam", "Liverpool", "London"],
"Marek": ["Košice"],
"Jozef": ["Lisabon", "Vancouver"]
}
for name in favourite_places:
line = ""
for place in favourite_places[name]:
if len(favourite_places[name]) == 1:
line = place
else:
line += place + " ,"
if line[-1] == ",":
line = line[0:-2]
print(f"{name} likes to go to here: {line}.")
In the end I want to have one sentence with name of the person and places that he or she likes to go to (devided by the comma).
I hope I described the problem correctely. I just want to optimalize my code. I will be grateful for any suggestions.
What you are missing is a join and items functions.
join joins all items of iterable with specified delimiter. If there is only 1 item, no delimiter will be added. It is generally considered a bad practise to concatenate strings with + or +=. Strings are immutable and thus each concatenation produces a new string, that has to have memory allocated for it. To avoid this, you could first collect all parts of strings into list (which you already have as an input) and then join them.
items() returns tuples of (key, value) of dictionary.
favourite_places = {
"Jozefína": ["Amsterdam", "Liverpool", "London"],
"Marek": ["Košice"],
"Jozef": ["Lisabon", "Vancouver"]
}
for name, places in favourite_places.items():
print(f"{name} likes to go to here: {', '.join(places)}.")
The simplest way I can think of to accomplish this is like this:
for name, places in favourite_places.items():
print(f'{name} likes to go here: {", ".join(places)}.')
The .items() returns a list of (key, value) tuples from the dictionary, which are deconstructed into name and places, then the ", ".join(places) will return a string that separates each element in the list of places with a comma, and will automatically leave it out if there is only a single element.
Try using replace
for name, places in favourite_places.items():
print(name + " likes to go to here: " + str(places).replace('[', '').replace("'", "").replace(']', ''))
here is some improvement:
favourite_places = {
"Jozefína": ["Amsterdam", "Liverpool", "London"],
"Marek": ["Košice"],
"Jozef": ["Lisabon", "Vancouver"]
}
for name, place in favourite_places.items():
line = ', '.join(place)
print(f'{name} likes to go to here: {line}')

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.

Combine/ "concatenate" two variables, one being a user input

I am looking to combine a user inputted string as one variable (it is dynamic, of course) and use it to make another variable.
Example:
x = str(input("What do you want to buy? "))
(I want the new variable to be like x_cost - but of course you don't actually write that)
Let's say that the user inputs apple, so the new variable would be: apple_cost.
Is there a way to do this?
You should use a dict for this. I get that it can be hard to understand what a dict is if you've never seen it before, but if you want to learn, it's absolutely necessary to slow down and understand these things.
costs = {}
item_name = input("What do you want to buy? ")
costs[item_name] = input('Price? ')
So you can try and enter a few things
costs = {}
for i in range(4):
item_name = input("What do you want to buy? ")
costs[item_name] = input('Price? ')
How would you print out all these new variables if you don't know the names? With a dict it is easy:
for key, value in costs.items():
print(key, "costs", value)
A good way of solving this problem would be to use a dictionary. A dictionary "entry" holds two objects, a key and an item. You can think of the key as the magic word and the item as the genie--by calling the key (i.e. saying the magic words) you can reference an item (i.e. summoning the genie).
Let's go with the fruit example. If you want the user to input one of three fruits (lets say apple, pear, and cantaloupe) and have it correspond to a price. If we say the apple costs one dollar, the pear two, and the cantaloupe one hundred, then here is what our dictionary would look like:
#This is our dictionary. you can see the keyword (the fruit) goes first
#in order to summon the price, which we will store in another variable
fruit_dict = {'apple': 1.00, 'pear': 2.00, `cantaloupe`: 100.00}
Now that we have a working dictionary, let us write a program!
#First we define the dictionary
fruit_dict = {"apple": 1.00, "pear": 2.00, "cantaloupe": 100.00}
#Now we need to ask for user input
fruit = raw_input("What fruit would ya like?\n>>> ")
#Next, we look for the fruit in our dictionary. We will use a function
#called `values()`, which returns `True` or `False`.
if fruit in fruit_dict:
fruit_cost = fruit_dict[fruit] #accessing the value with dictname[key]
Easy as that! Now you can do what you want with the variable.
Best of luck, and happy coding!
You cannot create variable names dynamically.
What you need is a dictionary.
There are two ways to achieve what you want
mydict = {}
x = str(input("What do you want to buy? "))
mydict[str(x)+'_cost'] = 'some value'
Now using user input directly to populate a dictionary can be a risky business from security point of view, so you may want do:
import hashlib
mydict = {}
x = str(input("What do you want to buy? "))
hashkey = hashlib.md5(str(x)).hexdigest()
mydict[hashkey+'_cost'] = 'some value'
In python everything is object. A program starts in a main module main. Each module, class, etc has its own namespace (a dictionary) where variables are defined. So if you put a key in that namespace dict then it becomes variable. You can make use of that variable to point to any other object you want.
Just look at this code...
try this in python interactive mode....
import sys
sys.modules['__main__'].__dict__ # this is your main modules namespace.
So just push the name of variable into the dict as key and assign the value/object.
sys.modules['__main__'].__dict__['apple_cost']]
apple_cost = 10.5
You can access the namespace of any containers class/modules/etc... But I would not suggest you to do what I explained (this is just one way of doing it. little bit hacky/ugly) instead use descriptors or simple getattr method in a class (bit advanced but something useful to learn) to implement some thing like this.

Python: Function that searches through a dictionary in a dictionary

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.

how to rid "" from a string to non string

I have this
a = "'Something': False"
I want it to be this
a = 'Something': False
How do I do it? I can strip things within the double quotation marks, but not the quotation marks itself. Have researched, cant find. I have mind block at moment. Sorry
Im trying to do this:
query_results = UserProfile.objects.filter(Location: location, Gender: gender).extra(select={'rank': a > 0}, order_by=['-rank'])
where Location = Someplace and Gender = Male or Female.
But what about when i did not want a specific gender or location. The only thing i could think of to do was to do
Location__isnull:False, Gender__isnull:False
But i cant have both
Location:location, Location__isnull:False.
Thus i have to have the location argument as a variable.
How could i then do this. The information referring to Location and gender is coming from a request.GET
I cant post my code as i keep deleting and changing spaghetti to try make something edible.
For what you're trying to do, the easiest way is to build up a dictionary of the arguments you want to supply, then pass them to the filter() call. Here's some non-working sample code that might get you heading in the right direction.
arguments = {}
if location is not None:
arguments['Location'] = location
else:
arguments['Location__isnull'] = False
if gender is not None:
arguments['Gender'] = gender
else:
arguments['Gender__isnull'] = False
query_results = UserProfile.objects.filter(**arguments)
It's not clear from your question whether or not you really need to search these for records which are not null. An empty .filter() would return all the records just as if you'd called .all(), and if you use the pattern I've suggested, you could omit all the else clauses and just feed filter(**arguments) an empty dictionary.
In other words, you only need to specify the query terms you really require, and do that by adding them to the arguments dictionary. Then you call filter with **arguments. The ** is a special Python syntax that says "take all the key/value pairs in this dictionary and turn them into keyword arguments for this function."
You can't. This is not valid python syntax: 'Something': False ... unless it's inside a dictionary: {'Something': False}. And no, you can not assign 'Something': False to a variable.
stufftofilter = {}
if shouldFilterLocation:
stufftofilter['Location'] = 'thelocation'
# etc
query_set = UserProfile.objects.extra(select={'rank':a>0}, order_by=['-rank'])
if stufftofilter:
query_set.filter(**stufftofilter)

Categories