Python find object in a list - python

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.

Related

Python Accessing a Value in a List of Dictionaries, if another Value in the Dictionary Exists

My question is an extension to this:
Python Accessing Values in A List of Dictionaries
I want to only return values from dictionaries if another given value exists in that dictionary.
In the case of the example given in the linked question, say I only want to return 'Name' values if the 'Age' value in the dictionary is '17'.
This should output
'Suzy'
only.
result = [d["Name"] for d in dicts if d.get("Age") == 17)]
Of course this would select all nanes that satisfy the condition. You can put that in a function.
In the following case :
listname= [
{'Name': 'Albert' , 'Age': 16},
{'Name': 'Suzy', 'Age': 17},
{'Name': 'Johnny', 'Age': 13}
]
If you want return only people Name when "age == 17" use :
for d in listname:
if d['Age'] == 17 :
print (d['Name'])
Use a condition inside your for.
Edit 10/01/2022 : Change "list" to "listname", cause list il already reserved in python.

Is it possible to iterate/loop over keys, values of dictionaries nested in list?

So i am using crash course 2e.
I am on ex 6-7:"Start with the program you wrote for Exercise 6-1 (page 102). Make two new dictionaries representing different people, and store all three dictionaries in a list called
people
. Loop through your list of people. As you loop through the list, print everything you know about each person."
I have my code formatted as follows(values have been replaced by 'value' for privacy reasons:
people = []
#add person to empty list
person = {
'first_name' : 'value',
'last_name' : 'value',
'city' : 'value',
}
people.append(person)
#add person to empty list
person = {
'first_name' : 'value',
'last_name' : 'value',
'city' : 'value',
}
people.append(person)
#add person to empty list
person = {
'first_name' : 'value',
'last_name' : 'value',
'city' : 'value',
}
people.append(person)
#verify list has persons added
print(people)
Now for the for loop part, is it possible to access the keys and values associated with each dictionary in the list ? I tried but failed. i thought I could do what I do with dictionaries and for example do :
for key, value in people.items():
and then follow it up with an f-string inside a print call .
I know that if 'people' was a dictionary it would work but I am assuming that the following error occurred because it is a list:
"AttributeError: 'list' object has no attribute 'items'"
So , in short:
Is it not possible to loop using .items() when it's a preceded by a list with dictionaries nested inside? How would I access each key, value pair in the above situation?
Is there an easier way then what I finally got to work:
for person in people:
name = f"{person['first_name'].title()} {person['last_name'].title()}"
residence = f"{person['city'].title()}"
print(f"{name} lives in {residence}.")
Thanks in advance folks!
.items() does the trick
for list_entry in people:
for propertie_ikey, propertie_item in list_entry.items():
print(propertie_ikey, propertie_item)

find an item inside a list of dictionaries

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

how to find value in list of dictionaries where keys are not consistent

here, I have a list of dictionaries, I need to find the object using value.
people = [
{'name': mifta}
{'name': 'khaled', 'age':30},
{'name': 'reshad', 'age':31}
]
I would like to find by 'age' key where value is 30. I can do this by following way
for person in people:
if person.get('age'):
if person['age'] == 30:
is there any better way to do this without lots of if else?
You can just use dict.get() one time without person['age'], it allows you to provide a default value if the key is missing, so you can try this:
dict.get
Return the value for key if key is in the dictionary, else default. If
default is not given, it defaults to None, so that this method never
raises a KeyError
people = [
{'name': 'mifta'},
{'name': 'khaled', 'age':30},
{'name': 'reshad', 'age':31}
]
for person in people:
if person.get('age',0)==30:
print(person)
If you want to avoid if..else you can use lambda function.
fieldMatch = filter(lambda x: 30 == x.get('age'), people)
or also use list comprehension to get names in a list.
names = [person['name'] for person in people if person.get('age') == 30]

How do I turn list values into an array with an index that matches the other dic values?

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.

Categories

Resources