Getting the key from a value within a dict of dicts - python

I have a dict of dicts dict_of_dicts={1:{'id':1,'name':'joe'},2: {'id':2,'name':'jim'},3: {'id':3,'name':'bob'}} (the structure is out of my control so I cannot define my dict more conveniently). I am looking to extract the id # (which also corresponds to the key of the upper dict) of the sub dict containing 'name':'bob'. i.e I want to extract the id value for a specific name.
I have thought of a looping solution, but is there a more pythonic way to go about this? My attempt is below
for i in list(dict_of_dicts.values()):
if i['name']=='bob':
print(i['id'])

Here is solution you can try out,
search_ = 'bob'
for k, v in dict_of_dicts.items():
if search_ in v.values():
print(f"Id {k}")
break
else:
print("No Match Found")
Edit
def search_(key):
for k, v in dict_of_dicts.items():
if key in v.values():
return k
return -1 # Indicating no match found

for k, v in dict_of_dicts.items():
if v["name"] == "bob":
print(v["id"])
You can also just print(k)

Even if the structure is out of your control, in case you want to have multiple searches, it might make sense to reformat the data before searchign multiple times, you can match the results later again if needed:
d = {1:{'id':1,'name':'joe'},2: {'id':2,'name':'jim'},3: {'id':3,'name':'bob'}}
n = {e['name']:e['id'] for e in d.values()}
n.get('bob')
#Out[32]: 3

Alternatively you could use filter function by passing a lambda that resolves to True if the name matches the search criteria. I passed in a default value of {'id': None} when grabbing the first item using next item if there are no match.
filtered = filter(lambda x: x['name'] == 'bob', dict_of_dicts.values())
match = next(filtered, {'id': None})
print(match['id'])

A one-liner:
d={1:{'id':1,'name':'joe'},2: {'id':2,'name':'jim'},3: {'id':3,'name':'bob'}}
print ([ k for k,v in d.items() if 'bob' in v['name'] ])
Output:
[3]
Here you get a list of keys/id in case you have multiple ids with `'name':'bob'.

You can use next() to find the first instance in a generator that satisfy your needs:
dict_of_dicts = {
1: {
'id': 1,
'name': 'joe'
},
2: {
'id': 2,
'name': 'jim'
},
3: {
'id': 3,
'name': 'bob'
}
}
# Find "bob"
try:
id_bob = next(
dict_of_dicts[key]["id"]
for key in dict_of_dicts
if dict_of_dicts[key]["name"] == "bob"
)
except StopIteration:
print("'bob' not found, handle this some way")
raise
# bob_id == 3
If you know for sure that "id" is the same value as the key, just use key instead of dict_of_dicts[key]["id"].

Related

Get a specific value from a list of dictionaries

I want to get the value a specific value '1222020' which has 'Expiration' as a key:
The 'Expiration' key can be placed at any position.
input :
my_list=[{'Key': 'Expiration', 'Value': '12122020'}, {'Key': 'Name', 'Value': 'Config Test 2'}]
my solution:
res = [sub['Value'] for sub in my_list if sub['Key'] =='Expiration' ]
print(res)
Sometimes the tag 'Expiration' is not present.
How to Handle that and avoid NoneType Object error
If you could re-organize your data like so,
custom_dict = {'Expiration': '12122020', 'Name': 'Config Test 2'}
Then, you could write the code like this,
def get_key_value_from_dictionary_search(dict_data, key_search_phrase, value_search_phrase):
for k,v in dict_data.items():
if k is key_search_phrase and v is value_search_phrase:
return k, v
_k, _v = get_key_value_from_dictionary_search(custom_dict, "Expiration", "12122020")
print("Key : {}\nValue : {}".format(_k, _v))
If the Expiration key isn't present, your res evaluates to an empty list. So if you just check for the presence on an empty list, you'll know if Expiration was in there to begin with.
def get_result(lst, default="99999999"):
res = [sub['Value'] for sub in lst if sub['Key'] == 'Expiration']
if res:
# there is something in the list, so return the first thing
return res[0]
else:
# the list is empty, so Expiration wasn't in lst
return default
print(get_result(my_list))

Access the most recent year in a python dictionary

I have a python dictionary which looks like this:
test_dict = {'id': '00000001',
'2017': {'some_info': 'toast'},
'2018': {'some_more_info': 'bread'},
}
I want to access the most recent year but the most recent year could change every time.
I have tried converting each key to an integer if it is possible ('id' obviously won't convert to an integer) using:
new_d = {}
for k,v in test_dict.items():
try:
new_d[int(k)] = v
except ValueError:
new_d[k] = v
Then tried to find the max key:
max(new_d, key=int)
But I still have the error:
ValueError: invalid literal for int() with base 10: '_id' which makes sense.
I want to search the dictionary for the max value key for all keys which are integers.
In this example I want to access the 2018 nested dictionary.
This is one approach using max with custom key.
Ex:
test_dict = {'id': '00000001',
'2017': {'some_info': 'toast'},
'2018': {'some_more_info': 'bread'},
}
print(max(test_dict.items(), key=lambda x: int(x[0]) if x[0].isdigit() else False))
#or
print(max(test_dict, key=lambda x: int(x) if x.isdigit() else False))
Output:
('2018', {'some_more_info': 'bread'})
2018
How about a custom dictionary?
from collections import UserDict
class YearDict(UserDict):
def most_recent(self):
ints = (key for key in self if key.isdigit())
return max(ints, key=int)
Demo:
>>> test_dict = {'id': '00000001',
...: '2017': {'some_info': 'toast'},
...: '2018': {'some_more_info': 'bread'},
...: }
>>>
>>> yd = YearDict(test_dict)
>>> yd.most_recent()
'2018'
Depending on your use case, you might want to add additional methods and a sanity check for when there are no years in the dict.
Also, consider making the keys integers and simplify most_recent accordingly.
In the end I came up with this helper function:
def helper_max_year(dictionary_of_comp):
new_d = {}
for k,v in dictionary_of_comp.items():
try:
new_d[int(k)] = v
except ValueError:
new_d[k] = v
integer_keys = [key for key in new_d.keys() if type(key) == int]
return (max(integer_keys))
Assuming that 'id' is the only non-numerical value, you can use the fact that max works as expected on strings as long as you only pass 4-digit years:
test_dict[max(x for x in test_dict if x != 'id')]
or if you like Python's functional programming primitives,
test_dict[max(filter(partial(str.__ne__, 'id'), test_dict))]
Alternatively, in your given example, the value of interest is the last one. If you can assume that this is always the case, you can make use of the fact that Python dictionaries are ordered (since Python 3.7) and just do
list(test_dict.items())[-1]
in O(N) time, maintain a variable to save current max year till the present iterator and loop through this
test_dict = {'id': '00000001',
'2017': {'some_info': 'toast'},
'2018': {'some_more_info': 'bread'},
}
def func(data):
max_year= -10000
for k, v in data.items():
if k.isdigit() and int(k)>max_year:
max_year = int(k)
return {str(max_year): data[str(max_year)]}
print(func(test_dict))
output
{'2018': {'some_more_info': 'bread'}}

Get specific key of a nested iterable and check if its value exists in a list

I am trying to access a specific key in a nest dictionary, then match its value to a string in a list. If the string in the list contains the string in the dictionary value, I want to override the dictionary value with the list value. below is an example.
my_list = ['string1~', 'string2~', 'string3~', 'string4~', 'string5~', 'string6~']
my_iterable = {'A':'xyz',
'B':'string6',
'C':[{'B':'string4', 'D':'123'}],
'E':[{'F':'321', 'B':'string1'}],
'G':'jkl'
}
The key I'm looking for is B, the objective is to override string6 with string6~, string4 with string4~, and so on for all B keys found in the my_iterable.
I have written a function to compute the Levenshtein distance between two strings, but I am struggling to write an efficient ways to override the values of the keys.
def find_and_replace(key, dictionary, original_list):
for k, v in dictionary.items():
if k == key:
#function to check if original_list item contains v
yield v
elif isinstance(v, dict):
for result in find_and_replace(key, v, name_list):
yield result
elif isinstance(v, list):
for d in v:
if isinstance(d, dict):
for result in find_and_replace(key, d, name_list):
yield result
if I call
updated_dict = find_and_replace('B', my_iterable, my_list)
I want updated_dict to return the below:
{'A':'xyz',
'B':'string6~',
'C':[{'B':'string4~', 'D':'123'}],
'E':[{'F':'321', 'B':'string1~'}],
'G':'jkl'
}
Is this the right approach to the most efficient solution, and how can I modify it to return a dictionary with the updated values for B?
You can use below code. I have assumed the structure of input dict to be same throughout the execution.
# Input List
my_list = ['string1~', 'string2~', 'string3~', 'string4~', 'string5~', 'string6~']
# Input Dict
# Removed duplicate key "B" from the dict
my_iterable = {'A':'xyz',
'B':'string6',
'C':[{'B':'string4', 'D':'123'}],
'E':[{'F':'321', 'B':'string1'}],
'G':'jkl',
}
# setting search key
search_key = "B"
# Main code
for i, v in my_iterable.items():
if i == search_key:
if not isinstance(v,list):
search_in_list = [i for i in my_list if v in i]
if search_in_list:
my_iterable[i] = search_in_list[0]
else:
try:
for j, k in v[0].items():
if j == search_key:
search_in_list = [l for l in my_list if k in l]
if search_in_list:
v[0][j] = search_in_list[0]
except:
continue
# print output
print (my_iterable)
# Result -> {'A': 'xyz', 'B': 'string6~', 'C': [{'B': 'string4~', 'D': '123'}], 'E': [{'F': '321', 'B': 'string1~'}], 'G': 'jkl'}
Above can has scope of optimization using list comprehension or using
a function
I hope this helps and counts!
In some cases, if your nesting is kind of complex you can treat the dictionary like a json string and do all sorts of replacements. Its probably not what people would call very pythonic, but gives you a little more flexibility.
import re, json
my_list = ['string1~', 'string2~', 'string3~', 'string4~', 'string5~', 'string6~']
my_iterable = {'A':'xyz',
'B':'string6',
'C':[{'B':'string4', 'D':'123'}],
'E':[{'F':'321', 'B':'string1'}],
'G':'jkl'}
json_str = json.dumps(my_iterable, ensure_ascii=False)
for val in my_list:
json_str = re.sub(re.compile(f"""("[B]":\\W?")({val[:-1]})(")"""), r"\1" + val + r"\3", json_str)
my_iterable = json.loads(json_str)
print(my_iterable)

Find lowest value in a list of dictionaries in python

I want to find and return the minimal value of an id in a string, for example:
find_min_id([{"nonid": "-222", "id": 0}, {"id": -101}])
-101
find_min_id([{’id’: 63, 'id': 42}])
42
So far I have this:
def find_min_id(list):
return min(list)
but that gives:
{'id': -101}
and I only want the value of the lowest id.
Use the key parameter of min:
def find_min_id(l):
return min(l, key=lambda d: d.get("id", float('inf')))["id"]
This actually finds the min id, and does it without creating a new list.
The only thing is, the elements in your list might not had an 'id' key. For that reason I had to use .get("id", float('inf')). Thus the function will return inf if there is no id key, which might not be desirable. What min() does when given an empty list is it throws an exception, so we'd probably like to do the same if none of the dicts we pass it have an 'id' key. In that case the min of a generator appoach might indeed be better:
def find_min_id(l):
return min(d["id"] for d in l if "id" in d)
The other approach would be to check for inf as the result of min, but this is more cumbersome:
import math
def find_min_id(l):
res = min(l, key=lambda d: d.get("id", float('inf')))["id"]
if math.isinf(res):
raise ValueError("No dict has an 'id' key")
return res
Another approach, but works where there is no id in the dictionary, and when there is no dictionary with an id at all:
def find_min_id(lst):
ids = [d['id'] for d in lst if 'id' in d]
return min(ids) if ids else None
Without exceptions, and without running min for artificially extended list (i.e. the answer which puts maximum floats where an entry isn't an id-entry).
Following code return None if no id key is in list.
>>> data = [{"nonid": "-222", "id": 0}, {"id": -101}, {"nonid":-200}]
>>> min(filter(lambda x: x is not None, map(lambda x: x.get('id', None),
... data)) or [None])
-101
Here filter(lambda x: x is not None, ...) filters out dictionaries without id,
map(lambda x: x.get('id', None), data) gets all id's from data, and or [None] part treats case when no id key will be found in data.
You are trying to find the 'lowest' dictionaries. What we want is, to find the lowest 'id' value in the list.
def find_min_id(lst):
return min([d[key] for key in d for d in lst if key=="id"])
Also, avoid using list as a variable name, it overrides the built-in function list().
A little demo:
>>> def find_min_id(lst):
return min([d[key] for key in d for d in lst if key=="id"])
>>> find_min_id(lst)
-101
Hope this helps!
>>> ids = [{"nonid": "-222", "id": 0}, {"id": -101}]
>>> min([val for obj in ids for key, val in obj.items() if key == 'id'])
-101
>>> ids = [{'id': 63}, { 'id': 42}]
>>> min([val for obj in ids for key, val in obj.items() if key == 'id'])
42
Try the above.
You can make this into a function definition:
def find_lowest(ids):
return min([val for obj in ids for key, val in obj.items() if key == 'id'])
Working example.
Let me explain what I'm doing. Firstly, the min function takes in an iterable object as an argument. So, let me demonstrate:
>>> min([1,2,3,4,6,1,0])
0
So, what this means is this, we are essentially taking the minimum value of the list that we get from this, [val for obj in ids for key, val in obj.items() if key == 'id'].
Now, you might be wondering, well whats happening in there? It might be a little intimidating at first, but thats a list comprehension. Whats that you say? Well, in simple terms its a concise we in which we make a list:
Let me start with be first part, and no its not the beginning of the statement:
for obj in ids
What we are doing here, is iterating over all the dictionary objects in in side of ids. Now, we use that object here:
key, val in obj.items() if key == 'id'
Since object, is a dict, we use the items function to get a generator that gives a tuple of key, value pairs. In an object like this: {'id': 100}, the id would be they key and 100 would be the value. So, we are going over all the items in the dictionary object, and if the key happens to be id, then we append it to the list:
[val
Thats what the first part does. The first part of the list comprehension appends something to the final list, and that is val.
UPDATE:
If for some reason, the list does not containt anything with id as a key, then it will throw a ValueError as min does not accept an empty list, so to remedy this, we can check:
def find_lowest(ids):
_ret = [val for obj in ids for key, val in obj.items() if key == 'id']
if _ret:
return min(_ret)
else:
return None
list is a built-in type in Python. don't use it as an identifier
def find_min_id(my_list)
id_list = []
for record in my_list:
if 'id' in record:
id_list.append(record['id'])
return min(id_list)

Find the index of a dict within a list, by matching the dict's value

I have a list of dicts:
list = [{'id':'1234','name':'Jason'},
{'id':'2345','name':'Tom'},
{'id':'3456','name':'Art'}]
How can I efficiently find the index position [0],[1], or [2] by matching on name = 'Tom'?
If this were a one-dimensional list I could do list.index() but I'm not sure how to proceed by searching the values of the dicts within the list.
lst = [{'id':'1234','name':'Jason'}, {'id':'2345','name':'Tom'}, {'id':'3456','name':'Art'}]
tom_index = next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
# 1
If you need to fetch repeatedly from name, you should index them by name (using a dictionary), this way get operations would be O(1) time. An idea:
def build_dict(seq, key):
return dict((d[key], dict(d, index=index)) for (index, d) in enumerate(seq))
people_by_name = build_dict(lst, key="name")
tom_info = people_by_name.get("Tom")
# {'index': 1, 'id': '2345', 'name': 'Tom'}
A simple readable version is
def find(lst, key, value):
for i, dic in enumerate(lst):
if dic[key] == value:
return i
return -1
It won't be efficient, as you need to walk the list checking every item in it (O(n)). If you want efficiency, you can use dict of dicts.
On the question, here's one possible way to find it (though, if you want to stick to this data structure, it's actually more efficient to use a generator as Brent Newey has written in the comments; see also tokland's answer):
>>> L = [{'id':'1234','name':'Jason'},
... {'id':'2345','name':'Tom'},
... {'id':'3456','name':'Art'}]
>>> [i for i,_ in enumerate(L) if _['name'] == 'Tom'][0]
1
Seems most logical to use a filter/index combo:
names=[{}, {'name': 'Tom'},{'name': 'Tony'}]
names.index(next(filter(lambda n: n.get('name') == 'Tom', names)))
1
And if you think there could be multiple matches:
[names.index(item) for item in filter(lambda n: n.get('name') == 'Tom', names)]
[1]
Answer offered by #faham is a nice one-liner, but it doesn't return the index to the dictionary containing the value. Instead it returns the dictionary itself. Here is a simple way to get: A list of indexes one or more if there are more than one, or an empty list if there are none:
list = [{'id':'1234','name':'Jason'},
{'id':'2345','name':'Tom'},
{'id':'3456','name':'Art'}]
[i for i, d in enumerate(list) if 'Tom' in d.values()]
Output:
>>> [1]
What I like about this approach is that with a simple edit you can get a list of both the indexes and the dictionaries as tuples. This is the problem I needed to solve and found these answers. In the following, I added a duplicate value in a different dictionary to show how it works:
list = [{'id':'1234','name':'Jason'},
{'id':'2345','name':'Tom'},
{'id':'3456','name':'Art'},
{'id':'4567','name':'Tom'}]
[(i, d) for i, d in enumerate(list) if 'Tom' in d.values()]
Output:
>>> [(1, {'id': '2345', 'name': 'Tom'}), (3, {'id': '4567', 'name': 'Tom'})]
This solution finds all dictionaries containing 'Tom' in any of their values.
Here's a function that finds the dictionary's index position if it exists.
dicts = [{'id':'1234','name':'Jason'},
{'id':'2345','name':'Tom'},
{'id':'3456','name':'Art'}]
def find_index(dicts, key, value):
class Null: pass
for i, d in enumerate(dicts):
if d.get(key, Null) == value:
return i
else:
raise ValueError('no dict with the key and value combination found')
print find_index(dicts, 'name', 'Tom')
# 1
find_index(dicts, 'name', 'Ensnare')
# ValueError: no dict with the key and value combination found
One liner!?
elm = ([i for i in mylist if i['name'] == 'Tom'] or [None])[0]
I needed a more general solution to account for the possibility of multiple dictionaries in the list having the key value, and a straightforward implementation using list comprehension:
dict_indices = [i for i, d in enumerate(dict_list) if d[dict_key] == key_value]
def search(itemID,list):
return[i for i in list if i.itemID==itemID]
The following will return the index for the first matching item:
['Tom' in i['name'] for i in list].index(True)
my answer is better in one a dictionary to use
food_time_dict = {"Lina": 312400, "Tom": 360054, "Den": 245800}
print(list(food_time_dict.keys()).index("Lina"))
I request keys from the dictionary, then I translate the list if it is not added, there will be an error then I use it as a list. but on your code:
lists = [{'id': '1234', 'name': 'Jason'},
{'id': '2345', 'name': 'Tom'},
{'id': '3456', 'name': 'Art'}]
def dict_in_lists_index(lists, search): # function for convenience
j = 0 # [j][i]
for i in lists:
try: # try our varible search if not found in list
return f"[{j}][{list(i.values()).index(search)}]"
# small decor
except ValueError: # error was ValueError
pass # aa... what must was what you want to do
j += 1 # not found? ok j++
return "Not Found"
def dict_cropped_index(lists, search):
for i in lists:
try:
return list(i.values()).index(search)
except ValueError:
pass
return "Not Found"
print(dict_in_lists_index(lists, 'Tom')) # and end
print(dict_cropped_index(lists, 'Tom')) # now for sure end
For a given iterable, more_itertools.locate yields positions of items that satisfy a predicate.
import more_itertools as mit
iterable = [
{"id": "1234", "name": "Jason"},
{"id": "2345", "name": "Tom"},
{"id": "3456", "name": "Art"}
]
list(mit.locate(iterable, pred=lambda d: d["name"] == "Tom"))
# [1]
more_itertools is a third-party library that implements itertools recipes among other useful tools.

Categories

Resources