Iterating through nested dict in a particular order with python3 - python

I have a nested dict which has lists as well as dict in it as given below.
m = {'abc':
{'bcd': [
{'cde':'100','def':'200','efg':'300'},
{'cde':'3000','def':'500','efg':'4000'}
],
'ghi':
{'mnc': [
{'xyz':'8827382','mnx':'e838','wyew':'2232'}
]
}
}
}
My requirement is to match mnx key and if the value is 'e838' then get the value of the other keys in that particular dict. So from the above example I may require the value of xyz key.
For this, I have created a recursive looping function as given below which is working. However my question is whether there is a better / easy way to do it. Also what can be done in the same code if I need to get all the values with key mnx. Thanks.
Note: I am converting an XML into dict with the help of jxmlease lib.
def iterate_dict(dict1,key1,val1):
for key, value in dict1.items():
if key == key1 and value == val1:
return dict1
if isinstance(value,list):
for item1 in value:
if isinstance(item1,dict):
for k,v in item1.items():
if k == key1 and v == val1:
return item1
if isinstance(value,dict):
for key,var in value.items():
if key == key1 and var == val1:
return value
else:
return iterate_dict(value,key1,val1)

You can sort of "flatten" the dict into a list of dicts and then query as necessary:
def flatten_dict(d):
flattened = []
current = {}
for k, v in d.items():
if isinstance(v, dict):
flattened.extend(flatten_dict(v))
elif isinstance(v, list):
flattened.extend(sum((flatten_dict(v_d) for v_d in v), []))
else:
current[k] = v
if len(current) > 0:
flattened = [current] + flattened
return flattened
def values_in_flattened(flattened, key):
return list(filter(None, (d.get(key, None) for d in flattened))) or None
m = {'abc': {'bcd':[{'cde':'100','def':'200','efg':'300'},{'cde':'3000','def':'500','efg':'4000'}], 'ghi':{'mnc':[{'xyz':'8827382','mnx':'e838','wyew':'2232'}]}}}
mf = flatten_dict(m)
efg_vals = values_in_flattened(mf, 'efg')
print(mf)
print(efg_vals)
>>>
[{'xyz': '8827382', 'mnx': 'e838', 'wyew': '2232'}, {'def': '200', 'efg': '300', 'cde': '100'}, {'def': '500', 'efg': '4000', 'cde': '3000'}]
['300', '4000']

m['abc']['bcd'] + m['abc']['ghi']['mnc']
out:
[{'cde': '100', 'def': '200', 'efg': '300'},
{'cde': '3000', 'def': '500', 'efg': '4000'},
{'mnx': 'e838', 'wyew': '2232', 'xyz': '8827382'}]
you should build a list of dict to iterate, rather than use raw data.

This code does the searching using recursive generators, so it will yield all the solutions as it finds them.
When iterate_dict finds a dict with the desired (key, value) pair it calls filter_dict, which creates a new dict to contain the output. This new dict contains the items of the dict passed to filter_dict except that it filters out the desired (key, value) pair, it also filters out any lists or dicts that that dict may contain. However, iterate_dict will recursively process those lists or dicts looking for further matches. If you don't want iterate_dict to look for further matches, it's easy to modify the code so that it doesn't do that; please see below.
If you want to search for dicts that contain the desired key and don't care about the value associated with that key you can pass None as the val argument, or just omit that arg.
I've modified your data slightly so we can test the recursive search for further matches in a dict that contains a match.
def filter_dict(d, key):
return {k: v for k, v in d.items()
if k != key and not isinstance(v, (dict, list))}
def iterate_dict(d, key, val=None):
if key in d and (val is None or d[key] == val):
yield filter_dict(d, key)
yield from iterate_list(d.values(), key, val)
def iterate_list(seq, key, val):
for v in seq:
if isinstance(v, list):
yield from iterate_list(v, key, val)
elif isinstance(v, dict):
yield from iterate_dict(v, key, val)
# test
data = {
'abc': {
'bcd': [
{'cde':'100', 'def':'200', 'efg':'300'},
{'cde':'3000', 'def':'500', 'efg':'4000'},
{'abc': '1', 'mnx': '2', 'ijk': '3',
'zzz': {'xyzz':'44', 'mnx':'e838', 'yew':'55'}
},
],
'ghi': {
'mnc': [
{'xyz':'8827382', 'mnx':'e838', 'wyew':'2232'}
]
}
}
}
for d in iterate_dict(data, 'mnx', 'e838'):
print(d)
output
{'yew': '55', 'xyzz': '44'}
{'xyz': '8827382', 'wyew': '2232'}
Here's a search that looks for all dicts containing the 'mnx' key:
for d in iterate_dict(data, 'mnx'):
print(d)
output
{'ijk': '3', 'abc': '1'}
{'xyzz': '44', 'yew': '55'}
{'wyew': '2232', 'xyz': '8827382'}
If you don't want each dict to be recursively searched for further matches once a match has been found in it, just change iterate_dict to:
def iterate_dict(d, key, val=None):
if key in d and (val is None or d[key] == val):
yield filter_dict(d, key)
else:
yield from iterate_list(d.values(), key, val)

Related

Reduce nested dictionary to "top-key-root-element" dictionary

I would like to derive a dictionary from a nested dictionary with the top keys as keys and the root elements as values. This post is similar to this one, but I would like to end up with a dictionary not with a list of lists.
Example: How to get from this
d = {'foo':{'a':{'b':['1','2']}},
'bar':{'a':{'b':{'c':'3'}}}}
to this?
{'foo': ['1', '2'], 'bar': '3'}
d = {'foo':{'a':{'b':['1','2']}},
'bar':{'c':{'d':'3'}}}
def get_last_value(d):
if isinstance(d, dict):
for k, v in d.items():
return get_last_value(v)
return d
result = {k:get_last_value(v) for k, v in d.items()}
print(result)
result:
{'foo': ['1', '2'], 'bar': '3'}
d = {'foo':{'a':{'b':['1','2']}},
'bar':{'c':{'d':'3'}}}
for key, value, in d.items():
print(key, value.values())

Update dictionary key(s) by drop starts with value from key in Python

I have a dictionary dict:
dict = {'drop_key1': '10001', 'drop_key2':'10002'}
The key(s) in dict startswith drop_, i would like to update dict by dropping drop_ value from key(s):
dict = {'key1': '10001', 'key2':'10002'}
What is the best approach to do it?
something like
d1 = {'drop_key1': '10001', 'drop_key2':'10002'}
d2 = {k[5:]:v for k,v in d1.items()}
print(d2)
output
{'key1': '10001', 'key2': '10002'}
One approach is, for each key value in the dictionary, you can replace the part of the string with the new string value. For instance:
d = {k.replace('drop_', ''): v for k, v in d.items() if k.strip().startswith('drop_')}
or you can define a function, and get the index of the searched string ("drop_"). If the search string index is 0, then remove it. For instance:
def change_key(key, search):
start_idx = key.find(search)
if start_idx == 0:
key = key.replace(search, "")
return key
d = {change_key(k, search="drop_"): v for k, v in d.items()}
Result:
{'key1': '10001', 'key2': '10002'}
Note that if you use a method, then you can guarantee to remove the search string if it is at the beginning of the string. For instance:
d = {' drop_key1': '10001', 'drop_key2': '10002'}
d = {change_key(k, search="drop_"): v for k, v in d.items()}
Result:
{' drop_key1': '10001', 'key2': '10002'}

How to add index to a list inside a dictionary

I have a dictionary here:
dict = {'A':['1','1','1','1','1'], 'B':['2','2'], 'C':['3','3','3','3']}
What is the necessary process to get the following result?
dict = {'A':['1_01','1_02','1_03','1_04','1_05'], 'B':['2_01','2_02'], 'C':['3_01','3_02','3_03','3_04']}
I have been learning python for quite a while now but dictionary is kind of new to me.
As others mentioned, refrain from using built-in keywords as variable names, such as dict. I kept it for simplicity on your part.
This is probably the most pythonic way of doing it (one line of code):
dict = {key:[x+"_0"+str(cnt+1) for cnt,x in enumerate(value)] for key,value in dict.items()}
You could also iterate through each dictionary item, and then each list item and manually change the list names as shown below:
for key,value in dict.items():
for cnt,x in enumerate(value):
dict[key][cnt] = x+"_0"+str(cnt+1)
Also, as some others have mentioned, if you want numbers greater than 10 to save as 1_10 rather than 1_010 you can you an if/else statement inside of the list comprehension...
dict = {key:[x+"_0"+str(cnt+1) if cnt+1 < 10 else x+"_"+str(cnt+1) for cnt,x in enumerate(value)] for key,value in dict.items()}
First iterate on keys.
Then loop on keys you are getting on key like for 'A' value is ['1','1','1','1','1'] then we can change the element at ['1','1','1','1','1']
enumerate() helps you iterate on index,value then index starts with zero as per your expected output add 1 to index. As you want the trailing 0 before each count we did '%02d'% (index+1)
Like this:
dict = {'A':['1','1','1','1','1'], 'B':['2','2'], 'C':['3','3','3','3']}
for i in dict.keys(): #iterate on keys
for index,val in enumerate(dict[i]): #took value as we have key in i
element='%02d'% (index+1) #add trailing 0 we converted 1 to int 01
dict[i][index]=val+"_"+ str(element) #assign new value with converting integer to string
print(dict)
Output:
{'A': ['1_01', '1_02', '1_03', '1_04', '1_05'], 'C': ['3_01', '3_02', '3_03', '3_04'], 'B': ['2_01', '2_02']}
Use enumerate to iterate over list keeping track of index:
d = {'A':['1','1','1','1','1'], 'B':['2','2'], 'C':['3','3','3','3']}
newd = {}
for k, v in d.items():
newd[k] = [f'{x}_0{i}' for i, x in enumerate(v, 1)]
print(newd)
Also a dictionary-comprehension:
d = {k: [f'{x}_0{i}' for i, x in enumerate(v, 1)] for k, v in d.items()}
Note: Don't name your dictionary as dict because it shadows the built-in.
d= {'A':['1','1','1','1','1'], 'B':['2','2'], 'C':['3','3','3','3']}
{x:[j + '_'+ '{:02}'.format(i+1) for i,j in enumerate(y)] for x,y in d.items()}
adict = {'A':['1','1','1','1','1'], 'B':['2','2'], 'C':['3','3','3','3'], 'D': '23454'}
newdict = {}
for i,v in adict.items():
if isinstance(v, list):
count = 0
for e in v:
count += 1
e += '_0' + str(count)
newdict[i] = newdict.get(i, [e]) + [e]
else:
newdict[i] = newdict.get(i, v)
print (newdict)
#{'A': ['1_01', '1_01', '1_02', '1_03', '1_04', '1_05'], 'B': ['2_01', '2_01', '2_02'], 'C': ['3_01', '3_01', '3_02', '3_03', '3_04'], 'D': '23454'}
This solution will check for whether your value in the dictionary is a list before assigning an index to it
You can use a dictcomp:
from itertools import starmap
d = {
'A': ['1', '1', '1', '1', '1'],
'B': ['2', '2'],
'C': ['3', '3', '3', '3']
}
f = lambda x, y: '%s_%02d' % (y, x)
print({k: list(starmap(f, enumerate(v, 1))) for k, v in d.items()})
# {'A': ['1_01', '1_02', '1_03', '1_04', '1_05'], 'B': ['2_01', '2_02'], 'C': ['3_01', '3_02', '3_03', '3_04']}

Python Join 2 dictionaries into 3rd, 2D dictionary where key from dictionary 2 is list value in dictionary 1

I couldn't find this particular python dictionary question anywhere.
I have two dictionaries:
dict1 = {'key1':['val1','val2','val3']}
dict2 = {'val1':['a','b','c']}
I want a 3rd, 2D dictionary with:
dict3 = {'key1': {'val1':['a','b','c']} }
So, joining 2 dictionaries where the key of the second dictionary is a list value of the first dictionary.
I was trying some nested looping along the lines of:
for key1, val1 in dict1.items():
for key2, in val2 in dict2.items():
# do something here
I am not sure if that is the best way to do this.
This is best done by iterating over dict1 and looking for matching values in dict2:
result = {}
for key, value_list in dict1.items():
result[key] = subdict = {}
for value in value_list:
try:
subdict[value] = dict2[value]
except KeyError:
pass
Result:
{'key1': {'val1': ['a', 'b', 'c']}}
You can use a dictionary comprehension and then check if the final result contains only one dictionary. If the latter is true, then a dictionary of dictionaries will be the final result; else, a listing of dictionaries will be stored for the key:
dict1 = {'key1':['val1','val2','val3']}
dict2 = {'val1':['a','b','c']}
new_dict = {a:[{i:dict2[i]} for i in b if i in dict2] for a, b in dict1.items()}
last_result = {a:b if len(b) > 1 else b[0] for a, b in new_dict.items()}
Output:
{'key1': {'val1': ['a', 'b', 'c']}}
dict1 = {
'key1':['val1','val2','val3']
}
dict2 = {
'val1':['a','b','c']
}
dict3 = {
key : { val_key : dict2[val_key]
for val_key in val_list if (val_key in dict2.keys())
} for key, val_list in dict1.items()
}
You can try this solution .
dict1 = {'key1':['val1','val2','val3']}
dict2 = {'val1':['a','b','c']}
join_dict={}
for i,j in dict1.items():
for sub_l,sub_value in dict2.items():
if sub_l in j:
join_dict[i]={sub_l:sub_value}
print(join_dict)
output:
{'key1': {'val1': ['a', 'b', 'c']}}

How to get a list which is a value of a dictionary by a value from the list?

I have the following dictionary :
d = {'1' : [1, 2, 3, 4], '2' : [10, 20, 30, 40]}
How do I get the corresponding key I'm searching by a value from one of the lists?
Let's say I want key '1' if I'm looking for value 3 or key '2' if I'm looking for value 10.
You can reverse the dictionary into this structure to do that kind of lookup:
reverse_d = {
1: '1',
2: '1',
3: '1',
4: '1',
10: '2',
…
}
which can be built by looping over each value of each key:
reverse_d = {}
for key, values in d.items():
for value in values:
reverse_d[value] = key
or more concisely as a dict comprehension:
reverse_d = {value: key for key, values in d.items() for value in values}
Lookups are straightforward now!
k = reverse_d[30]
# k = '2'
This only offers better performance than searching through the whole original dictionary if you do multiple lookups, though.
You can use a generator expression with a filtering condition, like this
>>> def get_key(d, search_value):
... return next(key for key, values in d.items() if search_value in values)
...
>>> get_key(d, 10)
'2'
>>> get_key(d, 2)
'1'
If none of the keys contain the value being searched for, None will be returned.
>>> get_key(d, 22)
None
This is my first time to answer question. How about this method?
def get_key(d,search_value):
res = []
for v in d.items():
if search_value in v[1]:
res.append(v[0])
return res
>>> D = {'a':[2,2,3,4,5],'b':[5,6,7,8,9]}
>>> getkey.get_key(D,2)
['a']
>>> getkey.get_key(D,9)
['b']
>>> getkey.get_key(D,5)
['a', 'b']

Categories