Related
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"].
I have a JSON object which is like this:
{ "produktNr:"1234",
"artNr_01":"12",
"artNr_02":"23",
"artNr_03":"",
"artNr_04":"14",
"name_01":"abc",
"name_02":"der",
"test":"junk"
}
I would like to convert this into a dictionary like this:
{ "produktNr:"1234", "artNr":["12","23","","14"], "name":["abc","der"], "test":"junk"}
This conversion is based on a sequence given say, seq = ["artNr","name"]. So the contents of the sequence are searched in the dictionary's keys and the values collected into a list.
My attempt so far:
tempDict = {}
for key,value in fmData.iteritems():
for seqval in seq:
if seqval in key:
if seqval in tempDict:
tempDict[seqval].append(value)
else:
x = []
x.append(value)
tempDict[seqval]=x
else:
tempDict[key] = value
faces a few problems.
The list of values are not ordered i.e, "artNr":["","14","12","23"]
instead of values of [_01,_02,_03,_04]
The items cannot be popped from the dictionary since in the loop the dictionary items cannot be deleted resulting in:
{ "produktNr:"1234", "artNr":["12","23","","14"],"artNr_01":"12", "artNr_02":"23", "artNr_03":"","artNr_04":"14","name":["abc","der"],"name_01":"abc", "name_02":"der", "test":"junk"}
Would love to understand how to deal with this, especially if there's a pythonic way to solve this problem.
You may use OrderedDict from the collections package:
from collections import OrderedDict
import re
input_dict = { "produktNr":"1234",
"artNr_01":"12",
"artNr_02":"23",
"artNr_03":"",
"artNr_04":"14",
"name_01":"abc",
"name_02":"der",
"test":"junk" }
# split keys on the first '_'
m = re.compile('^([^_]*)_(.*)')
def _order_by( item ):
# helper function for ordering the dict.
# item is split on first '_' and, if it was successful
# the second part is returned otherwise item is returned
# if key is something like artNr_42, return 42
# if key is something like test, return test
k,s = item
try:
return m.search(k).group(2)
except:
return k
# create ordered dict using helper function
orderedDict = OrderedDict( sorted(input_dict.items(), key=_order_by))
aggregated_dict = {}
for k, v in orderedDict.iteritems():
# split key
match = m.search(k)
if match:
# key is splittable, i.e., key is something like artNr_42
kk = match.group(1)
if kk not in aggregated_dict:
# create list and add value
aggregated_dict[kk] = [v]
else:
# add value
aggregated_dict[kk].append(v)
else:
# key is not splittable, i.e., key is something like produktNr
aggregated_dict[k] = v
print(aggregated_dict)
which gives the desired output
{'produktNr': '1234', 'test': 'junk', 'name': ['abc', 'der'], 'artNr': ['12', '23', '', '14']}
You can recreate a new dictionary that will group values of keys with '_' in the keys in a list while the other keys and values are kept intact. This should do:
d = { "produktNr":"1234", "artNr_01":"12", "artNr_02":"23","artNr_03":"","artNr_04":"14","name_01":"abc","name_02":"der","test":"junk"}
new_d= {}
for k, v in d.items():
k_new = k.split('_')[0]
if '_' in k:
if k_new not in new_d:
new_d[k_new] = [v]
else:
new_d[k_new].append(v)
else:
new_d[k_new] = v
print(new_d)
# {'artNr': ['', '14', '23', '12'], 'test': 'junk', 'produktNr': '1234', 'name': ['der', 'abc']}
Dicts are unordered collections, so the order with which the values are appended to the list will be indeterminate.
A slight modification of your code:
tempDict = {}
for key,value in fmData.iteritems():
seqval_in_key = "no"
for seqval in seq:
if seqval in key:
seqval_in_key = "yes"
for seqval in seq:
if seqval in key:
if seqval in tempDict:
tempDict[seqval].append(value)
else:
x = []
x.append(value)
tempDict[seqval]=x
else:
if (seqval_in_key == "no"):
tempDict[key] = value
print tempDict
Result:
{'produktNr': '1234', 'test': 'junk', 'name': ['abc', 'der'], 'artNr': ['14', '23', '', '12']}
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)
I'm trying to programmatically set a value in a dictionary, potentially nested, given a list of indices and a value.
So for example, let's say my list of indices is:
['person', 'address', 'city']
and the value is
'New York'
I want as a result a dictionary object like:
{ 'Person': { 'address': { 'city': 'New York' } }
Basically, the list represents a 'path' into a nested dictionary.
I think I can construct the dictionary itself, but where I'm stumbling is how to set the value. Obviously if I was just writing code for this manually it would be:
dict['Person']['address']['city'] = 'New York'
But how do I index into the dictionary and set the value like that programmatically if I just have a list of the indices and the value?
Python
Something like this could help:
def nested_set(dic, keys, value):
for key in keys[:-1]:
dic = dic.setdefault(key, {})
dic[keys[-1]] = value
And you can use it like this:
>>> d = {}
>>> nested_set(d, ['person', 'address', 'city'], 'New York')
>>> d
{'person': {'address': {'city': 'New York'}}}
I took the freedom to extend the code from the answer of Bakuriu. Therefore upvotes on this are optional, as his code is in and of itself a witty solution, which I wouldn't have thought of.
def nested_set(dic, keys, value, create_missing=True):
d = dic
for key in keys[:-1]:
if key in d:
d = d[key]
elif create_missing:
d = d.setdefault(key, {})
else:
return dic
if keys[-1] in d or create_missing:
d[keys[-1]] = value
return dic
When setting create_missing to True, you're making sure to only set already existing values:
# Trying to set a value of a nonexistent key DOES NOT create a new value
print(nested_set({"A": {"B": 1}}, ["A", "8"], 2, False))
>>> {'A': {'B': 1}}
# Trying to set a value of an existent key DOES create a new value
print(nested_set({"A": {"B": 1}}, ["A", "8"], 2, True))
>>> {'A': {'B': 1, '8': 2}}
# Set the value of an existing key
print(nested_set({"A": {"B": 1}}, ["A", "B"], 2))
>>> {'A': {'B': 2}}
Here's another option:
from collections import defaultdict
recursivedict = lambda: defaultdict(recursivedict)
mydict = recursivedict()
I originally got this from here: Set nested dict value and create intermediate keys.
It is quite clever and elegant if you ask me.
First off, you probably want to look at setdefault.
As a function I'd write it as
def get_leaf_dict(dct, key_list):
res=dct
for key in key_list:
res=res.setdefault(key, {})
return res
This would be used as:
get_leaf_dict( dict, ['Person', 'address', 'city']) = 'New York'
This could be cleaned up with error handling and such. Also using *args rather than a single key-list argument might be nice; but the idea is that
you can iterate over the keys, pulling up the appropriate dictionary at each level.
Here is my simple solution: just write
terms = ['person', 'address', 'city']
result = nested_dict(3, str)
result[terms] = 'New York' # as easy as it can be
You can even do:
terms = ['John', 'Tinkoff', '1094535332'] # account in Tinkoff Bank
result = nested_dict(3, float)
result[terms] += 2375.30
Now the backstage:
from collections import defaultdict
class nesteddict(defaultdict):
def __getitem__(self, key):
if isinstance(key, list):
d = self
for i in key:
d = defaultdict.__getitem__(d, i)
return d
else:
return defaultdict.__getitem__(self, key)
def __setitem__(self, key, value):
if isinstance(key, list):
d = self[key[:-1]]
defaultdict.__setitem__(d, key[-1], value)
else:
defaultdict.__setitem__(self, key, value)
def nested_dict(n, type):
if n == 1:
return nesteddict(type)
else:
return nesteddict(lambda: nested_dict(n-1, type))
The dotty_dict library for Python 3 can do this. See documentation, Dotty Dict for more clarity.
from dotty_dict import dotty
dot = dotty()
string = '.'.join(['person', 'address', 'city'])
dot[string] = 'New York'
print(dot)
Output:
{'person': {'address': {'city': 'New York'}}}
Use these pair of methods
def gattr(d, *attrs):
"""
This method receives a dict and list of attributes to return the innermost value of the give dict
"""
try:
for at in attrs:
d = d[at]
return d
except:
return None
def sattr(d, *attrs):
"""
Adds "val" to dict in the hierarchy mentioned via *attrs
For ex:
sattr(animals, "cat", "leg","fingers", 4) is equivalent to animals["cat"]["leg"]["fingers"]=4
This method creates necessary objects until it reaches the final depth
This behaviour is also known as autovivification and plenty of implementation are around
This implementation addresses the corner case of replacing existing primitives
https://gist.github.com/hrldcpr/2012250#gistcomment-1779319
"""
for attr in attrs[:-2]:
# If such key is not found or the value is primitive supply an empty dict
if d.get(attr) is None or isinstance(d.get(attr), dict):
d[attr] = {}
d = d[attr]
d[attrs[-2]] = attrs[-1]
Here's a variant of Bakuriu's answer that doesn't rely on a separate function:
keys = ['Person', 'address', 'city']
value = 'New York'
nested_dict = {}
# Build nested dictionary up until 2nd to last key
# (Effectively nested_dict['Person']['address'] = {})
sub_dict = nested_dict
for key_ind, key in enumerate(keys[:-1]):
if not key_ind:
# Point to newly added piece of dictionary
sub_dict = nested_dict.setdefault(key, {})
else:
# Point to newly added piece of sub-dictionary
# that is also added to original dictionary
sub_dict = sub_dict.setdefault(key, {})
# Add value to last key of nested structure of keys
# (Effectively nested_dict['Person']['address']['city'] = value)
sub_dict[keys[-1]] = value
print(nested_dict)
>>> {'Person': {'address': {'city': 'New York'}}}
This is a pretty good use case for a recursive function. So you can do something like this:
def parse(l: list, v: str) -> dict:
copy = dict()
k, *s = l
if len(s) > 0:
copy[k] = parse(s, v)
else:
copy[k] = v
return copy
This effectively pops off the first value of the passed list l as a key for the dict copy that we initialize, then runs the remaining list through the same function, creating a new key under that key until there's nothing left in the list, whereupon it assigns the last value to the v param.
This is much easier in Perl:
my %hash;
$hash{"aaa"}{"bbb"}{"ccc"}=1; # auto creates each of the intermediate levels
# of the hash (aka: dict or associated array)
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.