Nesting dictionary algorithm - python

Suppose I have the following dictionary:
{'a': 0, 'b': 1, 'c': 2, 'c.1': 3, 'd': 4, 'd.1': 5, 'd.1.2': 6}
I wish to write an algorithm which outputs the following:
{
"a": 0,
"b": 1,
"c": {
"c": 2,
"c.1": 3
},
"d":{
"d": 4,
"d.1": {
"d.1": 5,
"d.1.2": 6
}
}
}
Note how the names are repeated inside the dictionary. And some have variable level of nesting (eg. "d").
I was wondering how you would go about doing this, or if there is a python library for this? I know you'd have to use recursion for something like this, but my recursion skills are quite poor. Any thoughts would be highly appreciated.

You can use a recursive function for this or just a loop. The tricky part is wrapping existing values into dictionaries if further child nodes have to be added below them.
def nested(d):
res = {}
for key, val in d.items():
t = res
# descend deeper into the nested dict
for x in [key[:i] for i, c in enumerate(key) if c == "."]:
if x in t and not isinstance(t[x], dict):
# wrap leaf value into another dict
t[x] = {x: t[x]}
t = t.setdefault(x, {})
# add actual key to nested dict
if key in t:
# already exists, go one level deeper
t[key][key] = val
else:
t[key] = val
return res
Your example:
d = {'a': 0, 'b': 1, 'c': 2, 'c.1': 3, 'd': 4, 'd.1': 5, 'd.1.2': 6}
print(nested(d))
# {'a': 0,
# 'b': 1,
# 'c': {'c': 2, 'c.1': 3},
# 'd': {'d': 4, 'd.1': {'d.1': 5, 'd.1.2': 6}}}

Nesting dictionary algorithm ...
how you would go about doing this,
sort the dictionary items
group the result by index 0 of the keys (first item in the tuples)
iterate over the groups
if there are is than one item in a group make a key for the group and add the group items as the values.

Slightly shorter recursion approach with collections.defaultdict:
from collections import defaultdict
data = {'a': 0, 'b': 1, 'c': 2, 'c.1': 3, 'd': 4, 'd.1': 5, 'd.1.2': 6}
def group(d, p = []):
_d, r = defaultdict(list), {}
for n, [a, *b], c in d:
_d[a].append((n, b, c))
for a, b in _d.items():
if (k:=[i for i in b if i[1]]):
r['.'.join(p+[a])] = {**{i[0]:i[-1] for i in b if not i[1]}, **group(k, p+[a])}
else:
r[b[0][0]] = b[0][-1]
return r
print(group([(a, a.split('.'), b) for a, b in data.items()]))
Output:
{'a': 0, 'b': 1, 'c': {'c': 2, 'c.1': 3}, 'd': {'d': 4, 'd.1': {'d.1': 5, 'd.1.2': 6}}}

Related

Adding to a dictionary based on key and value from lists?

I have a dictionary defined as:
letters = {'a': 2, 'b': 1, 'c': 5}
I want to add values to this dictionary based on two lists: one which contains the keys and another which contains the values.
key_list = [a, c]
value_list = [2, 5]
This should give the output:
{a: 4, b: 1, c: 10}
Any ideas on how I can accomplish this? I am new to working with the dictionary structure so I apologise if this is extremely simple.
Thanks.
You can zip the two lists and then add to the dictionary as so;
letters = {'a': 2, 'b': 1, 'c': 5}
key_list = ['a', 'c']
value_list = [2, 5]
for k,v in zip(key_list, value_list):
letters[k] = letters.get(k, 0) + v
Using the dictionary's get() method as above allows you to add letters that aren't already in the dictionary.
for i in range(len(key_list)):
letters[key_list[i]] += value_list[i]
You can simply add or modify values from a dictionary using the key
For example:
letters = {'a': 2, 'b':1 , 'c': 5}
letters['a'] += 2
letters['c'] += 5
print(letters)
output = {'a': 4, 'b': 1, 'c': 10}

Python Find permutable list in a dict list

Given
listOfDict = [{'ref': 1, 'a': 1, 'b': 2, 'c': 3},
{'ref': 2, 'a': 4, 'b': 5, 'c': 6},
{'ref': 3, 'a': 7, 'b': 8, 'c': 9}]
Lets' consider a list of permutable integer
[7,8,9]=[7,9,8]=[8,7,9]=[8,9,7]=[9,7,8]=[9,8,7] # (3!)
Each of this list has a unique mapping ref, so how given for (8,7,9) can I get ref=3 ?
Also in real case I might until 10 (a,b,c,d,e,f,g,h,i,j)...
You can generate a dictionary that maps the values as frozenset to the value of ref:
listOfDict = [{'ref': 1, 'a': 1, 'b': 2, 'c': 3},
{'ref': 2, 'a': 4, 'b': 5, 'c': 6},
{'ref': 3, 'a': 7, 'b': 8, 'c': 9}]
keys = ['a', 'b', 'c']
out = {frozenset(d[k] for k in keys): d['ref'] for d in listOfDict}
# {frozenset({1, 2, 3}): 1,
# frozenset({4, 5, 6}): 2,
# frozenset({7, 8, 9}): 3}
example:
check = frozenset((8,7,9))
out[check]
# 3
but I don't know in advance the name of the other keys!
Then use this approach:
out = {}
for d in listOfDict:
d2 = d.copy() # this is to avoid modifying the original object
out[frozenset(d2.values())] = d2.pop('ref')
out
or as a comprehension:
out = dict(((d2:=d.copy()).pop('ref'), frozenset(d2.values()))[::-1]
for d in listOfDict)
Here is a commented solution to your problem. The idea is to compare the sorted list of the values in a, b, c etc with the sorted values in list_of_ints. The sorted values will be the same for all permutations of a given set of numbers.
def get_ref(list_of_ints):
# Loop through dictionaries in listOfDict.
for dictionary in listOfDict:
# Get list of values in each dictionary.
vals = [dictionary[key] for key in dictionary if key != "ref"]
if sorted(vals) == sorted(list_of_ints):
# If sorted values are equal to sorted list of ints, return ref.
return dictionary["ref"])
By the way, I believe it would be cleaner to structure this data as a dict of dicts in the following way:
dicts = {
1: {'a': 1, 'b': 2, 'c': 3},
2: {'a': 4, 'b': 5, 'c': 6},
3: {'a': 7, 'b': 8, 'c': 9}
}
The code would then be:
def get_ref(list_of_ints):
for ref, dictionary in dicts.items():
if sorted(dictionary.values()) == sorted(list_of_ints):
return ref
Assuming that all integers in the permutations are unique, the code can be simplified further using sets instead of sorted lists.
Since its a list of dict I can call each dict as it self by using for loop
and record the first number on ref
for i in listOfDict:
ref_num=i["ref"]
and to turn dictunary to list we simply use:
z=list(i.values())
then the last step is to find if its the same input list if so we print/return the ref number
if z[1:]==InputList:
return ref_num
and the code should be like this:
listOfDict = [
{"ref": 1,
"a": 1,
"b": 2,
"c": 3},
{"ref": 2,
"a": 4,
"b": 5,
"c": 6},
{"ref": 3,
"a": 7,
"b": 8,
"c": 9},]
def find_ref_Num(InputList):
for i in listOfDict:
ref_num=i["ref"]
z=list(i.values())
if z[1:]==InputList:
return ref_num
print ("your ref number is: "+str(find_ref_Num([7,8,9])))

Python: how to create a parameter grid with dynamic number of parameters

Suppose that the range of parameters of interest are given a dictionary that contains the range for each parameter of interest:
G = {'a': [1,2], 'b': [3], 'c': [1, 2.5] }
The goal is to extract every parameter configuration on this grid. In the example above, there are 4 such, corresponding to 2 values of a, and two values of b:
G1 = {'a': 1, 'b': 3, 'c': 1 }
G2 = {'a': 2, 'b': 3, 'c': 1 }
G3 = {'a': 1, 'b': 3, 'c': 2.5 }
G4 = {'a': 2, 'b': 3, 'c': 2.5 }
It's straightforward to write two nested for loops to produce all such configurations, it becomes less trivial how to do it for a general case, when there are a variable number of lists in G.
The only solution that comes to my mind is to create a multi-index vector vec=[0,0] which is as long as the number of parameters, and increment to iterate over all possible configurations: [0,0] -> [1,0] -> [0,1] -> [1,1]:
G = {'a': [1,2], 'b': [3], 'c': [1, 2.5] }
def get_configs(G):
keys = list(G.keys())
lists = list(G.values())
sizes = [len(l) for l in lists]
num_confs = np.prod(sizes)
index = [0]*(len(G)+1)
configs = []
while len(configs)<num_confs:
configs.append( {keys[i]: lists[i][index[i]] for i in range(len(G))})
index[0] += 1
cur = 0
while len(configs)<num_confs and index[cur]>=sizes[cur]:
index[cur]=0
cur += 1
index[cur] += 1
return configs
configs = get_configs(G)
print(configs)
However, the solution seems a bit over-complicated and ugly. Is there a clean solution using python?
Here is a generalizable implementation using itertools.product:
from itertools import product
def dict_configs(d):
for vcomb in product(*d.values()):
yield dict(zip(d.keys(), vcomb))
Usage:
>>> G = {'a': [1,2], 'b': [3], 'c': [1, 2.5] }
>>> for config in dict_configs(G):
... print(config)
...
{'a': 1, 'b': 3, 'c': 1}
{'a': 1, 'b': 3, 'c': 2.5}
{'a': 2, 'b': 3, 'c': 1}
{'a': 2, 'b': 3, 'c': 2.5}

How to take a linear combination of several dictionaries in Python?

Here's some code to take a linear combination of two dictionaries:
def linearcombination(a1,d1,a2,d2):
return {k:a1*d1.get(k,0)+a2*d2.get(k,0) for k in {**d1,**d2}.keys()}
choosy1={"a":1,"b":2,"c":3}
choosy2={"a":1,"d":1}
choosy=linearcombination(1,choosy1,10,choosy2)
choosy is:
{'a': 11, 'c': 3, 'd': 10, 'b': 2}
How can I generalise it to allow linear combinations of arbitrary numbers of dictionaries?
Solution using sum in a dict comprehension over a set of keys:
from itertools import chain
def linear_combination_of_dicts(dicts, weights):
return {
k: sum( w * d.get(k, 0) for d, w in zip(dicts, weights) )
for k in set(chain.from_iterable(dicts))
}
Example:
>>> dicts = [{'a': 1, 'b': 2, 'c': 3}, {'a': 1, 'd': 1}]
>>> weights = [1, 10]
>>> linear_combination_of_dicts(dicts, weights)
{'c': 3, 'd': 10, 'a': 11, 'b': 2}
Here's an approach with pandas to handle dict key alignment:
def lc(coeffs, dicts):
return (pd.concat(pd.Series(d).fillna(0)*a for a,d in zip(coeffs,dicts))
.sum(level=0)
.to_dict()
)
lc([1,10], [choosy1, choosy2])
# {'a': 11, 'b': 2, 'c': 3, 'd': 10}

Python set an item value based other items in a dictionary

A = {0:{a:1, b:7}, 1:{a:5,b:5}, 2:{a:4,b:6}}
I want to attach an item guess to each sub dictionary based on the value b accounting of all b's in each sub dictionary.
Saying, in Dictionary A:
0-b-7 percentage of b: 7/(7+5+6)
1-b-5 percentage of b: 5/(7+5+6)
2-b-6 percentage of b: 1 - 7/(7+5+6) - 5/(7+5+6)
The desired Dictionary should be like
A = {0:{a:1, b:7, 'guess': 7/(7+5+6)},
1:{a:5,b:5, 'guess': 5/(7+5+6)},
2:{a:4,b:6, 'guess': 1 - 7/(7+5+6) - 5/(7+5+6)}}
I don't know how to incorporate the other two b's for a specific subdictionary.
One approach is to precompute the sum of all bs and then use it to add a new key-value pair to your dictionary.
b_total = float(sum(A[k]['b'] for k in A))
for k in A:
A[k]['guess'] = A[k]['b'] / b_total
#{0: {'a': 1, 'b': 7, 'guess': 0.3888888888888889},
# 1: {'a': 5, 'b': 5, 'guess': 0.2777777777777778},
# 2: {'a': 4, 'b': 6, 'guess': 0.3333333333333333}}
A = {0:{"a":1, "b":7}, 1:{"a":5,"b":5}, 2:{"a":4,"b":6}}
char = "b"
denominator = 0
# =========================
# First Calculate the sum
# =========================
for key in A:
inner_map = A[key]
denominator += inner_map[char]
# ========================================
# Now insert the new key to the inner_map
# ========================================
for key in A:
inner_map = A[key]
inner_map["guess"] = inner_map[char]/denominator
print(A)
Output:
{0: {'a': 1, 'b': 7, 'guess': 0.3888888888888889}, 1: {'a': 5, 'b': 5, 'guess': 0.2777777777777778}, 2: {'a': 4, 'b': 6, 'guess': 0.3333333333333333}}
Try this:
def add_calc(my_dict):
total_guesses = sum(map(lambda x: my_dict.get(x).get('b'), my_dict))
for item in my_dict.itervalues():
item.update({'guess': 1.0 * item.get('b') / total_guesses})
return my_dict
d = add_calc(A)
{0: {'a': 1, 'b': 7, 'guess': 0.3888888888888889},
1: {'a': 5, 'b': 5, 'guess': 0.2777777777777778},
2: {'a': 4, 'b': 6, 'guess': 0.3333333333333333}}
I'm on Python 2 btw, you didn't specify version
You can use dictionary unpacking:
A = {0:{'a':1, 'b':7}, 1:{'a':5, 'b':5}, 2:{'a':4, 'b':6}}
results = {a:{**b, **{'guess':b['b']/float(sum(c['b'] for _, c in A.items()))}} for a, b in A.items()}
Output:
{0: {'guess': 0.3888888888888889, 'b': 7, 'a': 1}, 1: {'guess': 0.2777777777777778, 'b': 5, 'a': 5}, 2: {'guess': 0.3333333333333333, 'b': 6, 'a': 4}}

Categories