Convert dict to list of dict for each combinations - python

I have a dict looks like this :
my_dict = {
"a":[1, 2, 3],
"b":[10],
"c":[4, 5],
"d":[11]
}
And I would like to obtain a list containig all combinations keeping keys and value like this:
result = [
{"a":1, "b":10, "c":4, "d":11},
{"a":1, "b":10, "c":5, "d":11},
{"a":2, "b":10, "c":4, "d":11},
{"a":2, "b":10, "c":5, "d":11},
{"a":3, "b":10, "c":4, "d":11},
{"a":3, "b":10, "c":5, "d":11}
]
Do someone have a solution for this ?
Is there any existing solution to do this, or how should I proceed to do it myself ?
Thank you.

A task for itertools.product:
>>> from itertools import product
>>> for dict_items in product(*[product([k],v) for k, v in my_dict.items()]):
... print(dict(dict_items))
{'a': 1, 'b': 10, 'c': 4, 'd': 11}
{'a': 1, 'b': 10, 'c': 5, 'd': 11}
{'a': 2, 'b': 10, 'c': 4, 'd': 11}
{'a': 2, 'b': 10, 'c': 5, 'd': 11}
{'a': 3, 'b': 10, 'c': 4, 'd': 11}
{'a': 3, 'b': 10, 'c': 5, 'd': 11}
Small explanation:
The inner product(...) will expand the dict to a list such as [[(k1, v11), (k1, v12), ...], [(k2, v21), (k2, v22), ...], ...].
The outer product(...) will reassemble the items lists by choosing one tuple from each list.
dict(...) will create a dictionary from a sequence of (k1, v#), (k2, v#), ... tuples.

Try:
def permute(d):
k = d.keys()
perms = itertools.product(*d.values())
return [dict(zip(k, v)) for v in perms]
Example usage:
>>> d = {'a': [1, 2, 3], 'b': [10], 'c': [4, 5], 'd': [11]}
>>> pprint(permute(d))
[{'a': 1, 'b': 10, 'c': 4, 'd': 11},
{'a': 1, 'b': 10, 'c': 5, 'd': 11},
{'a': 2, 'b': 10, 'c': 4, 'd': 11},
{'a': 2, 'b': 10, 'c': 5, 'd': 11},
{'a': 3, 'b': 10, 'c': 4, 'd': 11},
{'a': 3, 'b': 10, 'c': 5, 'd': 11}]

Assuming that you are only interested in my_dict having 4 keys, it is simple enough to use nested for loops:
my_dict = {
"a": [1, 2, 3],
"b": [10],
"c": [4, 5],
"d": [11]
}
result = []
for a_val in my_dict['a']:
for b_val in my_dict['b']:
for c_val in my_dict['c']:
for d_val in my_dict['d']:
result.append({'a': a_val, 'b': b_val, 'c': c_val, 'd': d_val})
print(result)
This gives the expected result.

You can use:
from itertools import product
allNames = sorted(my_dict)
values= list(product(*(my_dict[Name] for Name in allNames)))
d = list(dict(zip(['a','b','c','d'],i)) for i in values)
Output:
[{'a': 1, 'c': 4, 'b': 10, 'd': 11},
{'a': 1, 'c': 5, 'b': 10, 'd': 11},
{'a': 2, 'c': 4, 'b': 10, 'd': 11},
{'a': 2, 'c': 5, 'b': 10, 'd': 11},
{'a': 3, 'c': 4, 'b': 10, 'd': 11},
{'a': 3, 'c': 5, 'b': 10, 'd': 11}]

itertools.product produces the combinations of a list of iterators.
dict.values() gets the list needed.
For each combination, zip up the dict.keys() with the combination.
Use a list comprehension to collect them up:
from itertools import product
from pprint import pprint
my_dict = {
"a":[1, 2, 3],
"b":[10],
"c":[4, 5],
"d":[11]
}
result = [dict(zip(my_dict,i)) for i in product(*my_dict.values())]
pprint(result)
Output:
[{'a': 1, 'b': 10, 'c': 4, 'd': 11},
{'a': 1, 'b': 10, 'c': 5, 'd': 11},
{'a': 2, 'b': 10, 'c': 4, 'd': 11},
{'a': 2, 'b': 10, 'c': 5, 'd': 11},
{'a': 3, 'b': 10, 'c': 4, 'd': 11},
{'a': 3, 'b': 10, 'c': 5, 'd': 11}]

Related

How do I return a new dictionary if the keys in one dictionary, match the keys in another dictionary?

Currently, I have a dictionary, with its key representing a zip code, and the values are also a dictionary.
d = { 94111: {'a': 5, 'b': 7, 'd': 7},
95413: {'a': 6, 'd': 4},
84131: {'a': 5, 'b': 15, 'c': 10, 'd': 11},
73173: {'a': 15, 'c': 10, 'd': 15},
80132: {'b': 7, 'c': 7, 'd': 7} }
And then a second dictionary, which associates which state the zip code belongs to.
states = {94111: "TX", 84131: "TX", 95413: "AL", 73173: "AL", 80132: "AL"}
If the zip code in the dictionary states matches one of the keys in db then it would sum up those values and put it into a new dictionary like the expected output.
Expected Output:
{'TX': {'a': 10, 'b': 22, 'd': 18, 'c': 10}, 'AL': {'a': 21, 'd': 26, 'c': 17, 'b': 7}}
So far this is the direction I am looking to go into but I'm not sure when both the keys match, how to create a dictionary that will look like the expected output.
def zips(d, states):
result = dict()
for key, value in db.items():
for keys, values in states.items():
if key == keys:
zips(d, states)
Using collections module
Ex:
from collections import defaultdict, Counter
d = { 94111: {'a': 5, 'b': 7, 'd': 7},
95413: {'a': 6, 'd': 4},
84131: {'a': 5, 'b': 15, 'c': 10, 'd': 11},
73173: {'a': 15, 'c': 10, 'd': 15},
80132: {'b': 7, 'c': 7, 'd': 7} }
states = {94111: "TX", 84131: "TX", 95413: "AL", 73173: "AL", 80132: "AL"}
result = defaultdict(Counter)
for k,v in d.items():
if k in states:
result[states[k]] += Counter(v)
print(result)
Output:
defaultdict(<class 'collections.Counter'>, {'AL': Counter({'d': 26, 'a': 21, 'c': 17, 'b': 7}),
'TX': Counter({'b': 22, 'd': 18, 'a': 10, 'c': 10})})
You can just use defaultdict and count in a loop:
expected_output = defaultdict(lambda: defaultdict(int))
for postcode, state in states.items():
for key, value in d.get(postcode, {}).items():
expected_output[state][key] += value
Just as a complement of the answer of Rakesh, Here is an answer closer to your code:
res = {v:{} for v in states.values()}
for k,v in states.items():
if k in d:
sub_dict = d[k]
output_dict = res[v]
for sub_k,sub_v in sub_dict.items():
output_dict[sub_k] = output_dict.get(sub_k, 0) + sub_v
You can use something like this:
d = { 94111: {'a': 5, 'b': 7, 'd': 7},
95413: {'a': 6, 'd': 4},
84131: {'a': 5, 'b': 15, 'c': 10, 'd': 11},
73173: {'a': 15, 'c': 10, 'd': 15},
80132: {'b': 7, 'c': 7, 'd': 7} }
states = {94111: "TX", 84131: "TX", 95413: "AL", 73173: "AL", 80132: "AL"}
out = {i: 0 for i in states.values()}
for key, value in d.items():
if key in states:
if not out[states[key]]:
out[states[key]] = value
else:
for k, v in value.items():
if k in out[states[key]]:
out[states[key]][k] += v
else:
out[states[key]][k] = v
# out -> {'TX': {'a': 10, 'b': 22, 'd': 18, 'c': 10}, 'AL': {'a': 21, 'd': 26, 'c': 17, 'b': 7}}
You can use the class Counter for counting objects:
from collections import Counter
d = { 94111: {'a': 5, 'b': 7, 'd': 7},
95413: {'a': 6, 'd': 4},
84131: {'a': 5, 'b': 15, 'c': 10, 'd': 11},
73173: {'a': 15, 'c': 10, 'd': 15},
80132: {'b': 7, 'c': 7, 'd': 7} }
states = {94111: "TX", 84131: "TX", 95413: "AL", 73173: "AL", 80132: "AL"}
new_d = {}
for k, v in d.items():
if k in states:
new_d.setdefault(states[k], Counter()).update(v)
print(new_d)
# {'TX': Counter({'b': 22, 'd': 18, 'a': 10, 'c': 10}), 'AL': Counter({'d': 26, 'a': 21, 'c': 17, 'b': 7})}
You can convert new_d to the dictionary of dictionaries:
for k, v in new_d.items():
new_d[k] = dict(v)
print(new_d)
# {'TX': {'a': 10, 'b': 22, 'd': 18, 'c': 10}, 'AL': {'a': 21, 'd': 26, 'c': 17, 'b': 7}}
You can leverage dict's .items() method, which returns a list of tuples, and get the expected output in a simple one-liner:
new_dict = {value:d[key] for key, value in states.items()}
Output:
{'AL': {'b': 7, 'c': 7, 'd': 7}, 'TX': {'a': 5, 'b': 15, 'c': 10, 'd': 11}}
You might want to reconsider your choice of dict for how to store your data. If you store your data using pandas, aggregation is a lot easier.
df = pd.DataFrame(d).transpose()
df['states']=pd.Series(states)
df.groupby('states').sum()
>> a b c d
>>states
>>AL 21.0 7.0 17.0 26.0
>>TX 10.0 22.0 10.0 18.0

Python using lambda sort list or dicts by multiple keys

here is my list of dict:
l = [{'a': 2, 'c': 1, 'b': 3},
{'a': 2, 'c': 3, 'b': 1},
{'a': 1, 'c': 2, 'b': 3},
{'a': 1, 'c': 3, 'b': 2},
{'a': 2, 'c': 5, 'b': 3}]
and now I want to sort the list by keys and orders provided by the user. for instance:
keys = ['a', 'c', 'b']
orders = [1, -1, 1]
I tried to using lambda in sort()method but it failed in a weird way :
>>> l.sort(key=lambda x: (order * x[key] for (key, order) in zip(keys, orders)))
>>> l
[{'a': 2, 'c': 5, 'b': 3},
{'a': 1, 'c': 3, 'b': 2},
{'a': 1, 'c': 2, 'b': 3},
{'a': 2, 'c': 3, 'b': 1},
{'a': 2, 'c': 1, 'b': 3}]
Anyone know how to solve this?
You were almost there; your lambda produces generator expressions and those happen to be ordered by their memory address (in Python 2) and produce a TypeError: '<' not supported between instances of 'generator' and 'generator' exception in Python 3.
Use a list comprehension instead:
l.sort(key=lambda x: [order * x[key] for (key, order) in zip(keys, orders)])
Demo:
>>> l = [{'a': 1, 'c': 2, 'b': 3},
... {'a': 1, 'c': 3, 'b': 2},
... {'a': 2, 'c': 1, 'b': 3},
... {'a': 2, 'c': 5, 'b': 3},
... {'a': 2, 'c': 3, 'b': 1}]
>>> keys = ['a', 'c', 'b']
>>> orders = [1, -1, 1]
>>> l.sort(key=lambda x: [order * x[key] for (key, order) in zip(keys, orders)])
>>> from pprint import pprint
>>> pprint(l)
[{'a': 1, 'b': 2, 'c': 3},
{'a': 1, 'b': 3, 'c': 2},
{'a': 2, 'b': 3, 'c': 5},
{'a': 2, 'b': 1, 'c': 3},
{'a': 2, 'b': 3, 'c': 1}]

Split python dictionary to result in all combinations of values

my_dict = {'a':[1,2], 'b':[3], 'c':{'d':[4,5], 'e':[6,7]}}
I need to derive all the combinations out of it as below.
{'a':1, 'b':3, 'c':{'d':4, 'e':6}}
{'a':1, 'b':3, 'c':{'d':4, 'e':7}}
{'a':1, 'b':3, 'c':{'d':5, 'e':6}}
{'a':1, 'b':3, 'c':{'d':5, 'e':7}}
{'a':2, 'b':3, 'c':{'d':4, 'e':6}}
and so on. There could be any level of nesting here
Please let me know how to achieve this
Something that I tried is pasted below but definitely was reaching nowhere
def gen_combinations(data):
my_list =[]
if isinstance(data, dict):
for k, v in data.iteritems():
if isinstance(v, dict):
gen_combinations(v)
elif isinstance(v, list):
for i in range(len(v)):
temp_dict = data.copy()
temp_dict[k] = v[i]
print temp_dict
my_dict = {'a':[1,2], 'b':[3], 'c':{'d':[4,5], 'e':[6,7]}}
gen_combinations(my_dict)
Which resulted in
{'a': 1, 'c': {'e': [6, 7], 'd': [4, 5]}, 'b': [3]}
{'a': 2, 'c': {'e': [6, 7], 'd': [4, 5]}, 'b': [3]}
{'e': 6, 'd': [4, 5]}
{'e': 7, 'd': [4, 5]}
{'e': [6, 7], 'd': 4}
{'e': [6, 7], 'd': 5}
{'a': [1, 2], 'c': {'e': [6, 7], 'd': [4, 5]}, 'b': 3}
from itertools import product
my_dict = {'a':[1,2], 'b':[3], 'c':{'d':[4,5], 'e':[6,7]}}
def process(d):
to_product = [] # [[('a', 1), ('a', 2)], [('b', 3),], ...]
for k, v in d.items():
if isinstance(v, list):
to_product.append([(k, i) for i in v])
elif isinstance(v, dict):
to_product.append([(k, i) for i in process(v)])
else:
to_product.append([(k, v)])
return [dict(l) for l in product(*to_product)]
for i in process(my_dict):
print(i)
Output:
{'a': 1, 'b': 3, 'c': {'e': 6, 'd': 4}}
{'a': 2, 'b': 3, 'c': {'e': 6, 'd': 4}}
{'a': 1, 'b': 3, 'c': {'e': 6, 'd': 5}}
{'a': 2, 'b': 3, 'c': {'e': 6, 'd': 5}}
{'a': 1, 'b': 3, 'c': {'e': 7, 'd': 4}}
{'a': 2, 'b': 3, 'c': {'e': 7, 'd': 4}}
{'a': 1, 'b': 3, 'c': {'e': 7, 'd': 5}}
{'a': 2, 'b': 3, 'c': {'e': 7, 'd': 5}}
Upd:
Code that works as asked here:
from itertools import product
my_dict = {'a':[1,2], 'e':[7], 'f':{'x':[{'a':[3,5]}, {'a':[4]}] } }
def process(d):
to_product = [] # [[('a', 1), ('a', 2)], [('b', 3),], ...]
for k, v in d.items():
if isinstance(v, list) and all(isinstance(i, dict) for i in v):
# specific case, when list of dicts process differently...
c = product(*list(process(i) for i in v))
to_product.append([(k, list(l)) for l in c])
elif isinstance(v, list):
to_product.append([(k, i) for i in v])
elif isinstance(v, dict):
to_product.append([(k, i) for i in process(v)])
else:
to_product.append([(k, v)])
return [dict(l) for l in product(*to_product)]
for i in process(my_dict):
print(i)
Output:
{'f': {'x': [{'a': 3}, {'a': 4}]}, 'a': 1, 'e': 7}
{'f': {'x': [{'a': 3}, {'a': 4}]}, 'a': 2, 'e': 7}
{'f': {'x': [{'a': 5}, {'a': 4}]}, 'a': 1, 'e': 7}
{'f': {'x': [{'a': 5}, {'a': 4}]}, 'a': 2, 'e': 7}
Solve it with two steps.
First replace each dict with a list of dicts generated by gen_combinations, called recursively.
Second, make the inner join between all keys. Each key has a flat list now.

Pythonic way to group items in a list [duplicate]

This question already has an answer here:
Group list of dictionaries to list of list of dictionaries with same property value
(1 answer)
Closed 8 years ago.
Consider a list of dicts:
items = [
{'a': 1, 'b': 9, 'c': 8},
{'a': 1, 'b': 5, 'c': 4},
{'a': 2, 'b': 3, 'c': 1},
{'a': 2, 'b': 7, 'c': 9},
{'a': 3, 'b': 8, 'c': 2}
]
Is there a pythonic way to extract and group these items by their a field, such that:
result = {
1 : [{'b': 9, 'c': 8}, {'b': 5, 'c': 4}]
2 : [{'b': 3, 'c': 1}, {'b': 7, 'c': 9}]
3 : [{'b': 8, 'c': 2}]
}
References to any similar Pythonic constructs are appreciated.
Use itertools.groupby:
>>> from itertools import groupby
>>> from operator import itemgetter
>>> {k: list(g) for k, g in groupby(items, itemgetter('a'))}
{1: [{'a': 1, 'c': 8, 'b': 9},
{'a': 1, 'c': 4, 'b': 5}],
2: [{'a': 2, 'c': 1, 'b': 3},
{'a': 2, 'c': 9, 'b': 7}],
3: [{'a': 3, 'c': 2, 'b': 8}]}
If item are not in sorted order then you can either sort them and then use groupby or you can use collections.OrderedDict(if order matters) or collections.defaultdict to do it in O(N) time:
>>> from collections import OrderedDict
>>> d = OrderedDict()
>>> for item in items:
... d.setdefault(item['a'], []).append(item)
...
>>> dict(d.items())
{1: [{'a': 1, 'c': 8, 'b': 9},
{'a': 1, 'c': 4, 'b': 5}],
2: [{'a': 2, 'c': 1, 'b': 3},
{'a': 2, 'c': 9, 'b': 7}],
3: [{'a': 3, 'c': 2, 'b': 8}]}
Update:
I see that you only want the those keys to be returned that we didn't use for grouping, for that you'll need to do something like this:
>>> group_keys = {'a'}
>>> {k:[{k:d[k] for k in d.viewkeys() - group_keys} for d in g]
for k, g in groupby(items, itemgetter(*group_keys))}
{1: [{'c': 8, 'b': 9},
{'c': 4, 'b': 5}],
2: [{'c': 1, 'b': 3},
{'c': 9, 'b': 7}],
3: [{'c': 2, 'b': 8}]}
Note: This code assumes the the data is already sorted. If it is not, we have to sort it manually
from itertools import groupby
print {key:list(grp) for key, grp in groupby(items, key=lambda x:x["a"])}
Output
{1: [{'a': 1, 'b': 9, 'c': 8}, {'a': 1, 'b': 5, 'c': 4}],
2: [{'a': 2, 'b': 3, 'c': 1}, {'a': 2, 'b': 7, 'c': 9}],
3: [{'a': 3, 'b': 8, 'c': 2}]}
To get the result in the same format you asked for,
from itertools import groupby
from operator import itemgetter
a_getter, getter, keys = itemgetter("a"), itemgetter("b", "c"), ("b", "c")
def recon_dicts(items):
return dict(zip(keys, getter(items)))
{key: map(recon_dicts, grp) for key, grp in groupby(items, key=a_getter)}
Output
{1: [{'c': 8, 'b': 9}, {'c': 4, 'b': 5}],
2: [{'c': 1, 'b': 3}, {'c': 9, 'b': 7}],
3: [{'c': 2, 'b': 8}]}
If the data is not sorted already, you can either use the defaultdict method in this answer, or you can use sorted function to sort based on a, like this
{key: map(recon_dicts, grp)
for key, grp in groupby(sorted(items, key=a_getter), key=a_getter)}
References:
operator.itemgetter
itertools.groupby
zip, map, dict, sorted

Combining all combinations of two lists into a dict of special form

I have two lists:
var_a = [1,2,3,4]
var_b = [6,7]
I want to have a list of dicts as follows:
result = [{'a':1,'b':6},{'a':1,'b':7},{'a':2,'b':6},{'a':2,'b':7},....]
I think the result should be clear.
[{k:v for k,v in itertools.izip('ab', comb)} for comb in itertools.product([1,2,3,4], [6,7])]
>>> import itertools
>>> [{k:v for k,v in itertools.izip('ab', comb)} for comb in itertools.product([
1,2,3,4], [6,7])]
[{'a': 1, 'b': 6}, {'a': 1, 'b': 7}, {'a': 2, 'b': 6}, {'a': 2, 'b': 7}, {'a': 3
, 'b': 6}, {'a': 3, 'b': 7}, {'a': 4, 'b': 6}, {'a': 4, 'b': 7}]
from itertools import product
a = [1,2,3,4]
b = [6,7]
[dict(zip(('a','b'), (i,j))) for i,j in product(a,b)]
yields
[{'a': 1, 'b': 6},
{'a': 1, 'b': 7},
{'a': 2, 'b': 6},
{'a': 2, 'b': 7},
{'a': 3, 'b': 6},
{'a': 3, 'b': 7},
{'a': 4, 'b': 6},
{'a': 4, 'b': 7}]
If the name of variables is given to you, you could use.
>>> a = [1,2,3,4]
>>> b = [6,7]
>>> from itertools import product
>>> nameTup = ('a', 'b')
>>> [dict(zip(nameTup, elem)) for elem in product(a, b)]
[{'a': 1, 'b': 6}, {'a': 1, 'b': 7}, {'a': 2, 'b': 6}, {'a': 2, 'b': 7}, {'a': 3, 'b': 6}, {'a': 3, 'b': 7}, {'a': 4, 'b': 6}, {'a': 4, 'b': 7}]

Categories