Adding to a value in Python Dictionary - python

I am having trouble with the following code. I need to add a key to a python dictionary if it does not exist, and if it does then I need to add to the value.
My output should look like this.
{'STA': {'value':-62**.***, 'count': 4}}.....
But I'm getting this instead.
{'STA': {'value': -1194.14562548, 'count': 0}},
{'STA': {'value': -5122.985396600001, 'count': 0}},
{'STA': {'value': 25.2293, 'count': 0}},
{'STA': {'value': 34.0099, 'count': 0}},
What am I doing wrong?
new_dict = []
for item in sales_orders['progress_output']:
ex = (list(filter(lambda ex:ex['_branch_shortname'].strip() == item['_branch_shortname'].strip(), expenses)))
value = float(item['gross_profit'].strip().replace(',', '')) - (float(item['load_factor_extended'].strip().replace(',', '')) * float(ex[0]['value']))
branch = item['_branch_shortname'].strip()
if branch in new_dict:
new_dict[branch]['value'] += value
new_dict[branch]['count'] += 1
else:
new_dict.append({branch: {'value': value, 'count': 0}})
#print(item['_branch_shortname'], value)
print(new_dict)

You can use setdefault to ensure there is a default value, setdefault returns the value if it already exists for key (first parameter) or adds the default (second parameter) and returns it:
new_dict = {}
for ...:
b = new_dict.setdefault(branch, {'value': 0, 'count': 0})
b['value'] += value
b['count'] += 1
You can also use a defaultdict:
from collections import defaultdict
new_dict = defaultdict(lambda: {'value': 0, 'count': 0})
for ...:
new_dict[branch]['value'] += value
new_dict[branch]['count'] += 1

Related

How to iterate dynamically over a dictionary

How to iterate over a dictionary / JSON using a dynamic query.
For example consider the below dict
dict = {'Adam': {
'English': {
'Score': 99,
'Time': 3400,
'Classes': 4},
'Math': {
'Score': 45,
'Time': 779,
'Classes': 5}},
'Tim': {
'English': {
'Score': 74,
'Time': 12,
'Classes': 99},
'Math': {
'Score': 12,
'Time': 333,
'Classes': 1}}
}
I want to set the value of a given path for example
path = '/Adam/English/Score'
new_value = 87
Note that the value assigned could be another dict as well for example
path = '/Adam/English'
new_value = {'Score': 11,
'Time': 2,
'Classes': 9}
Any help would be useful.
Edit: Below is my attempt
keys = path.split('/')[1:]
new_data = None
for key in keys:
if new_data is None:
new_data = dict[key]
else:
new_data = new_data[key]
new_data = new_value
print(dict)
But here the dict still has the old value
I made some assumptions, for example that '/' is not part of any dict-keys and that the path must be valid. Adjust the function as needed.
def deep_set(d, path, value):
sep = '/'
*trail, last = path.strip(sep).split(sep)
for part in trail:
d = d[part]
d[last] = value
Demo:
>>> d = {'a': 1}
>>> deep_set(d, 'a', 2)
>>> d
{'a': 2}
>>> d = {'a': {'b': 1}}
>>> deep_set(d, 'a/b', 2)
>>> d
{'a': {'b': 2}}
edit:
Note that if there are consecutive '/' characters then the empty string will be looked up as a dict key. e.g.
'a/b//c'.split('/') -> ['a', 'b', '', 'c']
It's unclear whether you want to treat leading/trailling '/' characters as part of the path or not (in my function, they are removed with str.strip). Again, adjust as needed.

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'))

Remove items from nested dictionary where value is zero

Here is a nested dictionary:
fdict = {}
fdict['apple'] = {}
fdict['banana'] = {}
fdict['apple']['green'] = 5
fdict['apple']['red'] = 0
fdict['banana']['light_yellow'] = 10
fdict['banana']['dark_yellow'] = 0
fdict['appraisal round'] = 1
{'apple': {'green': 5, 'red': 0},
'banana': {'light_yellow': 10, 'dark_yellow': 0},
'appraisal round': 1}
What is the most pythonic way to remove the key and value pairs where the value is zero from this nested dictionary such that the following dictionary results:
{'apple': {'green': 5}, 'banana': {'light_yellow': 10}, 'appraisal round': 1}
Note: The appraisal round key does not have a dictionary as a value itself.
Here is what I have implemented so far:
overall_dict = {}
for key in [key for key in fdict.keys() if key != 'appraisal round']:
new_dict = {k:v for k,v in fdict[key].items() if v != 0}
overall_dict[key] = new_dict
overall_dict['appraisal round'] = 1
However, using a temporary dictionary, constructing an entirely new dictionary and adding back in the appraisal round does not seem like a clean approach. Is there perhaps a way to amend the existing dictionary more effectively?
I suggest the following solution (it works for multiple nested levels):
fdict = {
'apple': {'green': 5, 'red': 0},
'banana': {'light_yellow': 10, 'dark_yellow': 0},
'appraisal round': 1
}
noZero = lambda d: { k1: noZero(v1) if isinstance(v1, dict) else v1 for k1, v1 in d.items() if v1 }
print(noZero(fdict)) # {'apple': {'green': 5}, 'banana': {'light_yellow': 10}, 'appraisal round': 1}
Assuming that You do not know the key that has non-dict type, a solution could be:
fdict = {}
fdict['apple'] = {}
fdict['banana'] = {}
fdict['apple']['green'] = 5
fdict['apple']['red'] = 0
fdict['banana']['light_yellow'] = 10
fdict['banana']['dark_yellow'] = 0
fdict['appraisal round'] = 1
def non_zero_value(item):
k, v = item
return v != 0
result = {}
for k, v in fdict.items():
if isinstance(v, dict):
result[k] = dict(filter(non_zero_value, v.items()))
else:
result[k] = v
print(result)
# {'apple': {'green': 5}, 'banana': {'light_yellow': 10}, 'appraisal round': 1}

Need to add/delete key-val, if key doesn't exists for nested dictionary of variables depth

I've two nested dictionaries of variable depth orig_dict , new_dict
orig_dict = {"a":1, "b":[{"c":{"d":2}, "e":[{"q":12}, {"a":2}]}, {"h":[1,2,3,4]}], "e":{"we":12}}
new_dict = {"a":2, "b":[{"c":{"d":3}, "e":[{"q":120}, {"a":2}, {"x":10000}]}], "e":{"we":12}, "f":100}
Here new_dict is the updates needed to be done in orig_dict,
if key doesn't exists add that key-val to orig_dict
if key in orig_dict doesn't exists in new_dict then delete key-val from orig_dict
if key matches skip changes
I've code which will process only dictionaries as values but not list
import collections
def map_and_update(orig_dict, new_dict):
for key, val in new_dict.items():
if isinstance(val, collections.Mapping):
tmp = updatee(orig_dict.get(key, { }), val)
orig_dict[key] = tmp
else:
orig_dict[key] = new_dict[key]
return orig_dict
this will result:
{'a': 2, 'b': [{'c': {'d': 3}, 'e': [{'q': 120}, {'a': 2}, {'x': 10000}]}], 'e': {'we': 12}, 'f': 100}
but i expect
{'a': 1, 'b': [{'c': {'d': 2}, 'e': [{'q': 12}, {'a': 2}, {'x': 10000}]}], 'e': {'we': 12}, 'f': 100}
Note: Above i'm just copying the value of new_dict to orig_dict if i
see a list, but it should parse through list even and check whether
dict exists or not if exists then again do map
This code of mine is maybe not very readable, but at least it did finish the job. I dont have time right now, but if anyone want me to explain, i will edit this post later.
orig_dict = {"a":1, "b":[{"c":{"d":2}, "e":[{"q":12}, {"a":2}]}, {"h":[1,2,3,4]}], "e":{"we":12}}
new_dict = {"a":2, "b":[{"c":{"d":3}, "e":[{"q":120}, {"a":2}, {"x":10000}]}], "e":{"we":12}, "f":100}
for key, value in new_dict.items():
if isinstance(value, list):
for key1, value1 in new_dict['b'][0].items():
if orig_dict['b'][0].get(key1):
pass
if len(new_dict['b']) != len(orig_dict['b']):
del orig_dict['b'][len(new_dict['b']):]
length = len(new_dict['b'][0]['e'])
for i in range(length):
for k, v in new_dict['b'][0]['e'][i].items():
try:
if orig_dict['b'][0]['e'][i].get(k):
pass
else:
orig_dict['b'][0]['e'][i][k]=v
except:
orig_dict['b'][0]['e'].append({k:v})
elif orig_dict.get(key):
pass
else:
orig_dict[key]=value
print(orig_dict)

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