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

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}

Related

Nesting dictionary algorithm

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}}}

Add two dictionaries in python and subtract result from another

I have three dictionaries:
X = {'a':2, 'b':3,'e':4}
Y = {'c':3, 'b':4,'a':5, 'd':7}
Z = {'c':8, 'b':7,'a':9, 'e':10,'f':10}
I want to add elements of X and Y if they are present in both dicts and then subtract them from z i.e. Z-X+Y
How can I do that ?
expected result:
res = {'a':2,'b':0,'c':5,'d':7,'e':6,'f':10}
What I tried:
from collections import Counter
xy = Counter(X) + Counter(Y)
res = Counter(Z) - xy
which return:
Counter({'c': 5, 'a': 2, 'e': 6, 'f': 10})
as you can see b and d are missing from my attempt
Your expected result is actually an operation of symmetric difference in terms of sets, but since collections.Counter doesn't support such an operation, you can emulate it with:
xy = Counter(X) + Counter(Y)
z = Counter(Z)
res = z - xy | xy - z
res becomes:
Counter({'f': 10, 'd': 7, 'e': 6, 'c': 5, 'a': 2})
But if you do want keys with value of 0, which Counter would hide from its output, you would have to iterate through a union of the keys of the 3 dicts:
{k: res.get(k, 0) for k in {*X, *Y, *Z}}
This returns:
{'a': 2, 'd': 7, 'e': 6, 'b': 0, 'f': 10, 'c': 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}

How to multiple values of keys with different numbers in dictionary?

Im trying to multiple some values from dictionary
example
price_list = {'a': 3, 'b': 2, 'c': 5, 'd': 10}
when i type
total=sum(price_list.values())
print("Total sum is ",total)
it result 20
But now i want to multiple a with 3, b with 5, c with 2 and d with 3 and my desired output to be 59. What is easiest way to do that?
Assuming your numbers are stored in the list, iterate through the values, and multiply with your required number like so
price_dict = {'a': 3, 'b': 2, 'c': 5, 'd': 10}
numbers_dict = {'a': 3, 'b': 5, 'c': 2, 'd': 3}
result = 0
for key, value in price_dict.items():
result += numbers_dict[key] * value
print(result)
#59
You can just perform operations on the dictionary item like you would any other variable:
# multiply 'a' by 3
price_list['a'] *= 3
Try this:
price_list = {'a': 3, 'b': 2, 'c': 5, 'd': 10}
numbers = [3,5,2,3]
for k,n in list(zip(price_list, numbers)):
price_list[k] *= n
then the price list will change, you can use sum as you did to calculate the result.

Iterate over X dictionary items in Python

How can I iterate over only X number of dictionary items? I can do it using the following bulky way, but I am sure Python allows a more elegant way.
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
x = 0
for key in d:
if x == 3:
break
print key
x += 1
If you want a random sample of X values from a dictionary you can use random.sample on the dictionary's keys:
from random import sample
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
X = 3
for key in sample(d, X):
print key, d[key]
And get output for example:
e 5
c 3
b 2

Categories