This question already has answers here:
find and update a value of a dictionary in list of dictionaries
(2 answers)
Closed 1 year ago.
I am trying to update the department of bob to Human Resources. I was under the impression from my readings and research that you just needed to rename the value within the key as I have done within my code but I keep getting an Assertion error. Any tips?
directory= [{'firstName': "bob", 'department': "Accounting", 'salary': 50000}, {'firstName': "alice", 'department': "Marketing", 'salary': 100000}]
#My Code
directory['department'] = 'Human Resources'
Do this:
directory= [{'firstName':"bob",'department':"Accounting",'salary':50000},{'firstName':"alice",'department':"Marketing",'salary':100000}]
for d in directory:
if 'firstName' in d and d['firstName'] == 'bob':
d['department'] = 'Human Resources'
break
print(directory) # [{'firstName': 'bob', 'salary': 50000, 'department': 'Human Resources'}, {'firstName': 'alice', 'salary': 100000, 'department': 'Marketing'}]
Your problem is that you're not actually dealing with a dictionary, but a list of dictionaries. This means you can't directly access the key, you have to access the item in the list.
To fix your current code:
directory[0]['department'] = 'Human resources'
Notice the [0].
You can tell this is the data structure because it has square brackets before and after the curly brackets. Eg. directory =[{ ... }]
For future reference, if you would like to look up someone by name, you'll need to iterate through the list and then update the appropriate dictionary.
for entry in directory:
# We're looking for Bob from accounting, not Bob the auditor.
if entry['name'] == 'bob' and entry['department'] == 'Accounting':
entry['department'] = 'Human Resources'
# break makes sure that this only happens once.
break
Since bob is a relatively common name, you might need to make your name values more unique. Bob Vila is more unique than bob, meaning you are less likely to address someone you don't mean to.
Of course, if you know that you have a unique value for each entry in the directory, then you can convert the whole thing into a dict.
# This is a dictionary comprehension. They're pretty neat.
new_directory = {employee['name']: employee for employee in directory}
new_directorr['bob']['department'] = 'Human resources'
On the linked question, the recommendation is a generator expression inside of next:
try:
bob = next(employee for employee in directory if employee['name'] == 'bob')
bob['department'] = 'Human Resources'
except StopIteration: # next will cause a exception if bob cannot be found.
pass
But you should note: the use of next with a list comprehension as well as for...in the iteration above: both are good ideas in some circumstances, but they will slow down over time as your directory gets bigger.
Related
Say I have a list of dictionaries.
each dict in the list has 3 elements.
Name, id and status.
list_of_dicts = [{'id':1, 'name':'Alice', 'status':0},{'id':2, 'name':'Bob', 'status':0},{'id':3, 'name':'Robert', 'status':1}]
so I get:
In[20]: print list_of_dicts
Out[20]:
[{'id': 1, 'name': 'Alice', 'status': 0},
{'id': 2, 'name': 'Bob', 'status': 0},
{'id': 3, 'name': 'Robert', 'status': 1}]
If i recieve a name, how can I get its status without iterating on the list?
e.g. I get 'Robert' and I want to output 1. thank you.
for example you can use pandas
import pandas as pd
list_of_dicts = [{'id':1, 'name':'Alice', 'status':0},{'id':2, 'name':'Bob', 'status':0},{'id':3, 'name':'Robert', 'status':1}]
a = pd.DataFrame(list_of_dicts)
a.loc[a['name'] == 'Robert']
and play with dataframes its very fast because write on c++ and easy (like sql queries)
As you found you have to iterate (unless you are able to change your data structure to an enclosing dict) why don't you just do it?
>>> [d['status'] for d in list_of_dicts if d['name']=='Robert']
[1]
Despite this, I recommend considering a map type (like dict) every time you see some 'id' field in a proposed data structure. If it's there you probably want to use it for general identification, instead of carrying dicts around. They can be used for relations also, and transfer easily into a relational database if you need it later.
I don't think you can do what you ask without iterating through the dictionary:
Best case, you'll find someone that suggests you a method that hides the iteration.
If what really concerns you is the speed, you may break your iteration as soon as you find the first valid result:
for iteration, elements in enumerate(list_of_dicts):
if elements['name'] == "Robert":
print "Elements id: ", elements['id']
break
print "Iterations: ", iteration
# OUTPUT: Elements id: 3, Iterations: 1
Note that numbers of iteration may vary, since dictionaries are not indexed, and if you have more "Roberts", only for one the "id" will be printed
It's not possible to do this without iteration.
However, but you can transform you dictionary into a different data structure, such as a dictionary where names are the keys:
new_dict = {person["name"]: {k: v for k, v in person.items() if k != "name"} for person in list_of_dicts}
Then you can get the status like so:
new_dict["Robert"]["status"]
# 1
Additionally, as #tobias_k mentions in the comments, you can keep the internal dictionary the same:
{person["name"]: person for person in list_of_dicts}
The only issue with the above approaches is that it can't handle multiple names. You can instead add the unique id into the key to differentiate between names:
new_dict = {(person["name"], person["id"]): person["status"] for person in list_of_dicts}
Which can be called like this:
new_dict["Robert", 3]
# 1
Even though it takes extra computation(only once) to create these data structures, the lookups afterwards will be O(1), instead of iterating the list every time when you want to search a name.
Your list_of_dicts cannot be reached without a loop so for your desire your list should be modified a little like 1 dict and many lists in it:
list_of_dicts_modified = {'name':['Alice', 'Bob', 'Robert'],'id':[1, 2, 3], 'status': [0, 0, 1]}
index = list_of_dicts_modified['name'].index(input().strip())
print('Name: {0} ID: {1} Status: {2}'.format(list_of_dicts_modified['name'][index], list_of_dicts_modified['id'][index], list_of_dicts_modified['status'][index]))
Output:
C:\Users\Documents>py test.py
Alice
Name: Alice ID: 1 Status: 0
I have a dictionary of dictionary called data_dict. Following is how it looks:
{'UMANOFF ADAM S': {'total_stock_value': 'NaN', 'loans': 'NaN', 'salary': 288589},
'YEAP SOON': {'total_stock_value': 192758, 'loans': 'NaN', 'salary': 'NaN'},
'PIPER GREGORY F': {'total_stock_value': 880290, 'loans': 1452356, 'salary': 19791},
'Jack S': {'total_stock_value': 88000, 'loans': 'NaN', 'salary': 288589}
}
Basically it is of the format
{Person Name : Dictionary of that person's attributes}
I am trying to find the name of a person whose salary is certain X.
Specifically in above example - let's say I am trying to find the name of the persons whose salary is 288589. I expect all the names whose salary is 288589.
I have written following generalised function which will take a search key and value and return names of the persons for which that key, value holds true.
def search_person_by_attribute(attribute, value):
person_names = []
for person, attributes_dict in data_dict.items():
if attributes_dict[attribute] == value:
person_names.append(person)
return person_names
This method runs successfully
results = search_person_by_attribute("salary", 288589)
print(results)
and prints
['UMANOFF ADAM S','Jack S']
But somehow I feel this is quite a long way write it. Is there a better/shorter/more pythonic way to do it?
If you can also mention the efficiency (in terms of time complexity) of my as well your suggested solution will be a great bonus.
I would suggest something like this, which I think is not just shorter, but more readable than your version:
def search_person_by_attribute(d, attribute, value):
return [name for name in d if d[name][attribute] == value]
It works exactly like yours, but requires the dictionary as an additional parameter, because I think that's better style:
>>> search_person_by_attribute(d, "salary", 288589)
['UMANOFF ADAM S', 'Jack S']
Hoping someone can help me out. I've spent the past couple hours trying to solve this, and fair warning, I'm still fairly new to python.
This is a repost of a question I recently deleted. I've misinterpreted my code in the last example.The correct example is:
I have a dictionary, with a list that looks similar to:
dic = [
{
'name': 'john',
'items': ['pants_1', 'shirt_2','socks_3']
},
{
'name': 'bob',
items: ['jacket_1', 'hat_1']
}
]
I'm using .append for both 'name', and 'items', which adds the dic values into two new lists:
for x in dic:
dic_name.append(dic['name'])
dic_items.append(dic['items'])
I need to split the item value using '_' as the delimiter, so I've also split the values by doing:
name, items = [i if i is None else i.split('_')[0] for i in dic_name],
[if i is None else i.split('_')[0] for i in chain(*dic_items)])
None is used in case there is no value. This provides me with a new list for name, items, with the delimiter used. Disregard the fact that I used '_' split for names in this example.
When I use this, the index for name, and item no longer match. Do i need to create the listed items in an array to match the name index, and if so, how?
Ideally, I want name[0] (which is john), to also match items[0] (as an array of the items in the list, so pants, shirt, socks). This way when I refer to index 0 for name, it also grabs all the values for items as index 0. The same thing regarding the index used for bob [1], which should match his items with the same index.
#avinash-raj, thanks for your patience, as I've had to update my question to reflect more closely to the code I'm working with.
I'm reading a little bit between the lines but are you trying to just collapse the list and get rid of the field names, e.g.:
>>> dic = [{'name': 'john', 'items':['pants_1','shirt_2','socks_3']},
{'name': 'bob', 'items':['jacket_1','hat_1']}]
>>> data = {d['name']: dict(i.split('_') for i in d['items']) for d in dic}
>>> data
{'bob': {'hat': '1', 'jacket': '1'},
'john': {'pants': '1', 'shirt': '2', 'socks': '3'}}
Now the data is directly related vs. indirectly related via a common index into 2 lists. If you want the dictionary split out you can always
>>> dic_name, dic_items = zip(*data.items())
>>> dic_name
('bob', 'john')
>>> dic_items
({'hat': '1', 'jacket': '1'}, {'pants': '1', 'shirt': '2', 'socks': '3'})
You need a list of dictionaries because the duplicate keys name and items are overwritten:
items = [[i.split('_')[0] for i in d['items']] for d in your_list]
names = [d['name'] for d in your_list] # then grab names from list
Alternatively, you can do this in one line with the built-in zip method and generators, like so:
names, items = zip(*((i['name'], [j.split('_')[0] for j in i['items']]) for i in dic))
From Looping Techniques in the Tutorial.
for name, items in div.items():
names.append(name)
items.append(item)
That will work if your dict is structured
{'name':[item1]}
In the loop body of
for x in dic:
dic_name.append(dic['name'])
dic_items.append(dic['items'])
you'll probably want to access x (to which the items in dic will be assigned in turn) rather than dic.
Lets say we have a list of dictionaries, ranging in the thousands. Every dictionary has the exact same keys, but different values.
Is there any way to lookup which dictionary a key value comes from, and then access different key values from that same dictionary?
For example, say you have a list containing every person in a large city, dictionary format, like so:
people_in_city = [
{'name': 'Bob Jang',
'age': 45,
'sex': 'male'},
{'name': 'Barb Esau',
'age': 56,
'sex': 'female'},
etc.
etc.
Now, you want to lookup the age of Bob Jang, but you only have his name. Is there anyway to get his corresponding age using this format?
There's no fast way to do this in Python. I'd suggest something like:
def get_dict_from_key(list_of_dicts, key, value):
return next((d for d in list_of_dicts if d[key] == value))
result_d = get_dict_from_key(dicts, 'name', 'Bob Jang')
result_d['age']
That said, this is the kind of thing that relational databases are made for! Learn SQL and use it :)
I would suggest looking at a database as Adam Smith suggested. Though I would also like to suggest looking at a class. That would go something like:
class Person():
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
people_in_city = []
def add_person(name, age, gender
people_in_city=people_in_city):
people_in_city.append(Person(name, age, gender))
def find_by_name(name):
for person in people_in_city:
if person.name == name:
return person
Not the most elegant way, but it gets the job done, plus you can add more information and not have have to change the nature of your search function. So lets say you find dear 'Bob Jang', and you realize that you want to know that job he is doing (assuming you coded that into the class). You just do:
person_of_interest = find_by_name('Bob Jang')
person_of_interest.job
person_of_interest.age
Note that this only gives the LAST found value of the name, and not everyone with that name. Other methods will have to be employed for that. This method also means that you are holding all the information in a list, and that might get slow as the list grows. That is why databases would work better as your list grows.
And as a bonus, it is possible to create each person in parallel.
Try this,
provided_name = 'some name'
persons_list_with_name = [person_info in person_info in people_in_city if person_info['name'] == provided_name]
for person_info in persons_list_with_name:
print person_info['name'], person_info['age']
(age,) = [v['age' ]for (k,v) in people_in_city.iteritems() if v['name']=="Bob Jang"]
people_in_city = [
{'name': 'Bob Jang',
'age': 45,
'sex': 'male'},
{'name': 'Barb Esau',
'age': 56,
'sex': 'female'}]
v = age or name sex etc..
for i in people_in_city:
if v in i.values():
print i
There is a python package called bidict that can do this. It provides a two-way dict, which allows you to get the key from the value or the value from the key. An example from the documentation:
>>> element_by_symbol = bidict(H='hydrogen')
>>> element_by_symbol['H'] # forward mapping works just like with dict
'hydrogen'
>>> element_by_symbol[:'hydrogen'] # use slice for the inverse mapping
'H'
I have a list of people:
[
{'name' : 'John', 'wins' : 10 },
{'name' : 'Sally', 'wins' : 0 },
{'name' : 'Fred', 'wins' : 3 },
{'name' : 'Mary', 'wins' : 6 }
]
I am adding wins using a list of names (['Fred', 'Mary', 'Sally']). I don't know if the name is in the list of people already, and I need to insert a new record if not. Currently I'm doing the following:
name = 'John'
person = None
pidx = None
for p in people_list:
if p['name'] == name:
person = p
pidx = people_list.index(p)
break
if person is None:
person = {'name' : name, 'wins' : 0}
person['wins'] += 1
if pidx is None:
people_list.append(person)
else
people_list[pidx] = person
Is there a better way to do this with a list? Given that I'm saving this to MongoDB I can't use a dict as it will save as an object and I want to use native array functions for sorting and mapping that aren't available for objects.
I'm assuming here that you don't want to use any structure other than the list. Your code should work, although you unnecessarily write the dictionary back to the list after updating it. Dictionaries are copied by reference, so once you update it, it stays updated in the list. After a little housekeeping, your code could look like this:
def add_win(people_list, name):
person = find_person(people_list, name)
person['wins'] += 1
def find_person(people_list, name):
for person in people_list:
if person['name'] == name:
return person
person = {'name': name, 'wins': 0}
people_list.append(person)
return person
Yes, use a dict.
wins = {}
for name in winners:
wins.setdefault(name, 0)
wins[name] += 1
edit:
index = {}
for name in wins:
person = index.setdefault(name, { 'name' : name, 'wins': 0 })
if person['wins'] == 0:
person_list.append(person)
person['wins'] += 1
If you don't want a dict permanently use one temporarily.
people = [
{'name' : 'John', 'wins' : 10 },
{'name' : 'Sally', 'wins' : 0 },
{'name' : 'Fred', 'wins' : 3 },
{'name' : 'Mary', 'wins' : 6 }
]
wins = ['Fred', 'Mary', 'Sally']
people_dict = dict((p["name"], p) for p in people)
for winner in wins:
people_dict[winner].setdefault("wins", 0)
people_dict[winner]["wins"] += 1
people = people_dict.values()
Your access pattern dictates the use of a different data structure (or at least another helper data structure). Scanning the list as you're doing is in fact the right thing to do if you're using a list, but you shouldn't be using a list (if you want it to be efficient, anyhow).
If the order of the list doesn't matter, you should use a Dictionary (python dict). If it does, you should use an OrderedDict from the collections module.
You could also use two separate data structures - the list you already have, and additionally a set containing just the names in the list so you have quick access to test inclusion or not. However, the set doesn't help you access the actual name data quickly (you'd still have to do a linear search in the list for that), so it would only be a helpful pattern if you merely were testing inclusion, but otherwise always walking the list as it was inserted.
Edit: it seems like what you might actually want is a list and a dict, where the dictionary is a mapping between the name and the index in the list. Alternatively you could still use a dict or OrderedDict, but insert them into Mongo as an array by using dict.iteritems() to create an array (or what would look like an array to Mongo) on insertion. You could use various mutators from zip to things in itertools to dynamically build up the objects you need in your resultant array.
This specific case is implemented by the collections.Counter type. Along with array generators, this is one expression:
[{'name':name, 'wins':wins}
for name, wins in Counter(names).items()]
If you want a specific order, sorted() is the easiest way (this also uses a plain generator (), rather than an array generator [], since it's temporary):
sorted(({'name':name, 'wins':wins} for name, wins in Counter(names).items()),
key=lambda item: item['name'])
Where item['name'] could be item['wins'] or any other comparable expression.