Search for item in list of dictionary - python

So I have this params:
p = [{'quantity': 1}, {'args': {'id': 12345678, 'age': 12}}]
And I want to be able to search for quantity and get the value 1 or the args key and get its doctionary ({'id': 12345678, 'age: 12})
This is what I have try:
def search(name: str, params: list[dict]):
try:
return next((x[name] for x in params if x), None)
except KeyError:
return None
I case I search for the value of quantity:
search(name='quantity', params=p)
This return 1
But in case I want to value of args:
search(name='args', params=p)
This return None

I have a set of functions that you could use for this [ getNestedVal(p, 'args') would return {'id': 12345678, 'age: 12} ]...or you can use this generator
def yieldNestedVals(obj, key):
if isinstance(obj, str): return
try: yield obj[key]
except: pass
if isinstance(obj, dict): obj = obj.values()
for i in (obj if hasattr(obj, '__iter__') else []):
for v in yieldNestedVals(i, key): yield v
inside search like
def search(name, params, defaultVal=None):
for v in yieldNestedVals(params, name): return v # return 1st generated value
return defaultVal # if nothing if generated
[ Ofc you can omit both the defaultVal parameter and the last line if you want to just return None when nothing if found, since that is the default behavior when a function doesn't come across a return statement. ]
Now search('args', p) should also return {'id': 12345678, 'age: 12} and you can try it with other keys as well:
# p = [{'quantity': 1}, {'args': {'id': 12345678, 'age': 12}}]
{k: search(k, p) for k in ['quantity', 'args', 'id', 'age', 'dneKey']}
would return
{'quantity': 1,
'args': {'id': 12345678, 'age': 12},
'id': 12345678,
'age': 12,
'dneKey': None}

Related

Recursive dictionary searching

I'm trying to make a function that would take nested array (dict/list in any order) and a key name as arguments and return all values of that key in a list.
my_key = "Items"
my_dict = [{'z': 0, 'x': 0, 'y': 0, 'Items': [{'Slot': 1, 'id': 'minecraft:rail', 'Count': 1}, {'Slot': 2, 'id': 'minecraft:white_shulker_box', 'tag': {'BlockEntityTag': {'id': 'minecraft:shulker_box', 'Items': [{'Slot': 0, 'Count': 1, 'tag': {'Items': [{'id': 'minecraft:amethyst_shard', 'Count': 1}]}, 'id': 'minecraft:bundle'}]}}, 'Count': 1}]}]
def recursive_lookup(data, key):
if isinstance(data, list):
for i in data:
recursive_lookup(i, key)
elif isinstance(data, dict):
for i, v in data.items():
if i == key:
print(f'{v = }')
if isinstance(v, list) or isinstance(v, dict): recursive_lookup(v, key)
print(recursive_lookup(my_dict, my_key))
Currently it prints out found items at print(f'{v = }'). How can I store those in a list and pass as a function return?
You can use .extend() to concatenate the result of recursive calls to a list.
def recursive_lookup(data, key):
values = []
if isinstance(data, list):
for i in data:
values.extend(recursive_lookup(i, key))
elif isinstance(data, dict):
for i, v in data.items():
if i == key:
values.append(v)
if isinstance(v, list) or isinstance(v, dict):
values.extend(recursive_lookup(v, key))
return values
You can what you want without any explicit recursion at all by making use of the json module in the standard library (assuming your data can be serialized into that format). This is because the JSON decoder supports an object_hook argument which is a function it will call everytime it encounters a dictionary.
The basic idea is to specify a function via this argument that merely "watches" what is being decoded and checks it for the sought-after key.
Here's what I mean:
import json
my_key = "Items"
my_dict = [{'z': 0, 'x': 0, 'y': 0, 'Items': [{'Slot': 1, 'id': 'minecraft:rail', 'Count': 1}, {'Slot': 2, 'id': 'minecraft:white_shulker_box', 'tag': {'BlockEntityTag': {'id': 'minecraft:shulker_box', 'Items': [{'Slot': 0, 'Count': 1, 'tag': {'Items': [{'id': 'minecraft:amethyst_shard', 'Count': 1}]}, 'id': 'minecraft:bundle'}]}}, 'Count': 1}]}]
def lookup(data, key):
results = []
def decode_dict(a_dict):
try:
results.append(a_dict[key])
except KeyError:
pass
return a_dict
json_repr = json.dumps(data) # Convert to JSON format.
json.loads(json_repr, object_hook=decode_dict) # Return value ignored.
return results
from pprint import pprint
pprint(lookup(my_dict, my_key), sort_dicts=False)
Pretty-printed result list:
[[{'id': 'minecraft:amethyst_shard', 'Count': 1}],
[{'Slot': 0,
'Count': 1,
'tag': {'Items': [{'id': 'minecraft:amethyst_shard', 'Count': 1}]},
'id': 'minecraft:bundle'}],
[{'Slot': 1, 'id': 'minecraft:rail', 'Count': 1},
{'Slot': 2,
'id': 'minecraft:white_shulker_box',
'tag': {'BlockEntityTag': {'id': 'minecraft:shulker_box',
'Items': [{'Slot': 0,
'Count': 1,
'tag': {'Items': [{'id': 'minecraft:amethyst_shard',
'Count': 1}]},
'id': 'minecraft:bundle'}]}},
'Count': 1}]]
You can keep a running list:
def recursive_lookup(data, key):
lst = []
if isinstance(data, list):
for i in data:
lst.append(recursive_lookup(i, key))
elif isinstance(data, dict):
for i, v in data.items():
if i == key:
lst.append([v])
if isinstance(v, list) or isinstance(v, dict): lst.append(recursive_lookup(v, key))
return lst
print(recursive_lookup(data, 'Items'))

How to change values in a nested dictionary

I need to change values in a nested dictionary. Consider this dictionary:
stocks = {
'name': 'stocks',
'IBM': 146.48,
'MSFT': 44.11,
'CSCO': 25.54,
'micro': {'name': 'micro', 'age': 1}
}
I need to loop through all the keys and change the values of all the name keys.
stocks.name
stocks.micro.name
These keys need to be changed. But, I will not know which keys to change before hand. So, I'll need to loop through keys and change the values.
Example
change_keys("name", "test")
Output
{
'name': 'test',
'IBM': 146.48,
'MSFT': 44.11,
'CSCO': 25.54,
'micro': {'name': 'test', 'age': 1}
}
A recursive solution that supports unknown number of nesting levels:
def change_key(d, required_key, new_value):
for k, v in d.items():
if isinstance(v, dict):
change_key(v, required_key, new_value)
if k == required_key:
d[k] = new_value
stocks = {
'name': 'stocks',
'IBM': 146.48,
'MSFT': 44.11,
'CSCO': 25.54,
'micro': {'name': 'micro', 'age': 1}
}
change_key(stocks, 'name', 'new_value')
print(stocks)
# {'name': 'new_value',
# 'MSFT': 44.11,
# 'CSCO': 25.54,
# 'IBM': 146.48,
# 'micro': {'name': 'new_value',
# 'age': 1}
# }
def changeKeys(d, repl):
for k,v in zip(d.keys(),d.values()):
if isinstance(v, dict):
changeKeys(v,repl)
elif k == "name":
d[k]= repl

Change value in dictionary

I would like to know how I can change a value in a dictionary using list of keys and 1 value
my_dict = {
'bob': {
'name': {'first': 'FirstName', 'last': 'LastName'},
'job': 'Developer'
}
}
string1 = 'bob.name.first=Bob'
string1 = string.split('=')
string2 = string1[0].split('.')
string2.append(string[1])
Here I end up with a list of 4 items, the first 3 are keys and the last is the value.
How can I use this given list to change the value in my_dict considering that the given list keys number can be changed for example if I want to change bob.job=QA
You can write:
string1 = 'bob.name.first=Bob'
string1,string2 = string1.split('=')
string1 = string1.split('.')
my_dict[string1[0]][string1[1]][string1[2]] = string2
I suppose the following function is what you are looking for, it works with any number of keys and creates intermediates dictionaries if not exist yet.
d = {
'bob': {
'name': {
'first': 'FirstName',
'last': 'LastName'
},
'job': 'Developer'
}
}
def update_dict_by_expr(d, expr):
keys_value = expr.split('=')
keys = keys_value[0].split('.')
value = keys_value[1]
item = d
while len(keys) > 1:
key = keys.pop(0)
if key not in item:
item[key] = {}
item = item[key]
key = keys[0]
item[key] = value
print(d)
update_dict_by_expr(d, 'bob.name.first=Bob Junior')
update_dict_by_expr(d, 'bob.name.birth.date=01/01/2017')
update_dict_by_expr(d, 'bob.name.birth.place=NYC')
print(d)
You want a dict with keys that can be accessed as attributes. You can achieve that by subclassing dict class, and add support for your need. I think this is more pythonic solution as it is much more intuative:
class MyDict(dict):
def __getattr__(self, attr):
return self[attr] if attr in self.keys() else None
def __setattr__(self, attr, value):
self[attr] = value
my_dict = MyDict({
'bob': MyDict(
{'name':
MyDict({'first': 'FirstName', 'last': 'LastName'}),
'job':'Developer'})})
>>> my_dict.bob
{'job': 'Developer', 'name': {'last': 'LastName', 'first': 'FirstName'}}
>>> my_dict.bob.job
'Developer'
>>> my_dict.bob.name
{'last': 'LastName', 'first': 'FirstName'}
It does require some overhead, as you will need to build your dicts based on MyDict. Regular dicts won't work if added to this dict.
This supports setting a new value as well:
>>> my_dict.bob.job = 'QA'
>>> my_dict.bob.job
'QA'
If you want to update Bob's job you can access it using my_dict['bob']['job']
my_dict = {
'bob': {
'name': {'first': 'FirstName', 'last': 'LastName'},
'job': 'Developer'
}
}
my_dict['bob']['job'] = 'QA'
print(my_dict)
>> {'bob': {'name': {'last': 'LastName', 'first': 'FirstName'}, 'job': 'QA'}}
or by splitting your string:
my_dict = {
'bob': {
'name': {'first': 'FirstName', 'last': 'LastName'},
'job': 'Developer'
}
}
bobjob_key_value = 'bob.job=QA'
key, value = bobjob_key_value.split('=')
key = key.split('.')
my_dict[key[0]][key[1]] = value
print(my_dict)
>> {'bob': {'job': 'QA', 'name': {'last': 'LastName', 'first': 'FirstName'}}}
import yaml
def get_dictionary_replace_value(file, new_value, strip_qoutes=True):
with open(file, 'r') as rf:
yaml_doc = yaml.load(rf)
rf.close()
key, value = new_value.split('=')
keys = key.split('.')
inner_dict = yaml_doc
for i in keys[:-1]:
inner_dict = inner_dict[i]
inner_dict[keys[-1]] = value
with open(file, 'w') as wf:
if strip_qoutes:
wf.write(yaml.dump(yaml_doc,
default_flow_style=False).replace("'", "").replace('"', ""))
else:
wf.write(yaml.dump(yaml_doc, default_flow_style=False))
wf.close()

I have dictionary need to fill it into tree, how to?

I have dictionary like this, need to fill it into tree like scheme in array or DB for example:
a = {"seasons": "episodes", "peka": {"lol": "wow", "kek": {"wtf": "is this"}}, "ololo": "wololo"}
key "seasons" have own ID = 1, Parent_ID = NONE
and value "episode" have its own ID = 2 and Parent_ID = 1 ,
and the same with other item of dictionary.
/!\ WARNING the order in a dictionnary is not guaranteed see here
a = {
"seasons": "episodes",
"peka": {"lol": "wow", "kek": {"wtf": "is this"}},
"ololo": "wololo"
}
The object a is dictionnary the order of (key, value) is not guaranteed, that is to say random if you do print(a), you have :
{'ololo': 'wololo', 'peka': {'kek': {'wtf': 'is this'}, 'lol': 'wow'}, 'seasons': 'episodes'}
It is another order.
To keep the same order, copy/past this type in the file file.json and user
OrderedDict.
file.json:
{
"seasons": "episodes",
"peka": {"lol": "wow", "kek": {"wtf": "is this"}},
"ololo": "wololo"
}
Here your solution:
import json
from collections import OrderedDict
from pprint import pprint
with open('file.json', 'r') as filename:
a = json.load(filename, object_pairs_hook=OrderedDict)
def build_item(_id, parent_id, value):
return {'ID': _id, 'Parent_ID': parent_id, 'Value': value}
def dfs(_id, root, tree):
_id += 1
flat_tree = [build_item(_id, None, root)]
stack = [(_id, tree)]
while len(stack) != 0:
parent_id, tree = stack.pop(0)
if isinstance(tree, dict):
for value in tree.keys():
_id += 1
flat_tree.append(build_item(_id, parent_id, value))
stack.append((_id, tree[value]))
else:
value = tree
_id += 1
flat_tree.append(build_item(_id, parent_id, value))
return _id, flat_tree
def convert_dict_to_flat_tree(d):
flat_trees = list()
_id = 0
for root, tree in d.items():
_id, flat_tree = dfs(_id, root, tree)
flat_trees.extend(flat_tree)
return flat_trees
flat_tree = convert_dict_to_flat_tree(a)
pprint(flat_tree)
Output:
[{'ID': 1, 'Parent_ID': None, 'Value': 'seasons'},
{'ID': 2, 'Parent_ID': 1, 'Value': 'episodes'},
{'ID': 3, 'Parent_ID': None, 'Value': 'peka'},
{'ID': 4, 'Parent_ID': 3, 'Value': 'lol'},
{'ID': 5, 'Parent_ID': 3, 'Value': 'kek'},
{'ID': 6, 'Parent_ID': 4, 'Value': 'wow'},
{'ID': 7, 'Parent_ID': 5, 'Value': 'wtf'},
{'ID': 8, 'Parent_ID': 7, 'Value': 'is this'},
{'ID': 9, 'Parent_ID': None, 'Value': 'ololo'},
{'ID': 10, 'Parent_ID': 9, 'Value': 'wololo'}]
You want something like this:
a = {"seasons": "episodes", "peka": {"lol": "wow", "kek": {"wtf": "is this"}}, "ololo": "wololo"}
_id = {}
def newid():
id = _id.setdefault('foo', 0)
_id['foo'] += 1
return id
def flat(dic, parent):
for k,v in dic.items():
id = newid()
yield (id, parent, k, v if not isinstance(v, dict) else None)
if isinstance(v, dict):
for tup in flat(v, id):
yield tup
print list(flat(a, newid()))
Which prints:
[(1, 0, 'seasons', 'episodes'),
(2, 0, 'ololo', 'wololo'),
(3, 0, 'peka', None),
(4, 3, 'kek', None),
(5, 4, 'wtf', 'is this'),
(6, 3, 'lol', 'wow')]
These are tuples in the form (ID, Parent ID, Key, Value?). I would have prefer to output E(ID, Parent ID, Key) V(ID, Value).

Reaching a dictionary inside a list of dictionaries by key

I have a dictionary that looks like this:
{'items': [{'id': 1}, {'id': 2}, {'id': 3}]}
and I'm looking for a way to directly get the inner dictionary with id = 1.
Is there a way to reach this other than looping the list items and comparing the id?
first_with_id_or_none = \
next((value for value in dictionary['items'] if value['id'] == 1), None)
You will have to loop through the list. The good news is is that you can use a generator expression with next() to do that looping:
yourdict = next(d for d in somedict['items'] if d['id'] == 1)
This can raise a StopIteration exception if there is no such matching dictionary.
Use
yourdict = next((d for d in somedict['items'] if d['id'] == 1), None)
to return a default instead for that edge-case (here None is used, but pick what you need).
Make it into a function:
def get_inner_dict_with_value(D, key, value):
for k, v in D.items():
for d in v:
if d.get(key) == value:
return d
else:
raise ValueError('the dictionary was not found')
With explanation:
def get_inner_dict_with_value(D, key, value):
for k, v in D.items(): # loop the dictionary
# k = 'items'
# v = [{'id': 1}, {'id': 2}, {'id': 3}]
for d in v: # gets each inner dictionary
if d.get(key) == value: # find what you look for
return d # return it
else: # none of the inner dictionaries had what you wanted
raise ValueError('the dictionary was not found') # oh no!
Running it:
>>> get_inner_dict_with_value({'items': [{'id': 1}, {'id': 2}, {'id': 3}]}, 'id', 1)
{'id': 1}
Another method:
def get_inner_dict_with_value2(D, key, value):
try:
return next((d for l in D.values() for d in l if d.get(key) == value))
except StopIteration:
raise ValueError('the dictionary was not found')
>>> get_inner_dict_with_value2({'items': [{'id': 1}, {'id': 2}, {'id': 3}]}, 'id', 1)
{'id': 1}

Categories

Resources