How to group Python dictionary values which are dictionaries themselves - python

I'd like to group by the values of the following dictionary:
my_dict = {"Q1": {0: "no", 1: "yes"}, "Q2": {0: "no", 1: "yes"},
"Q3": {1: "animal", 2: "vehicle"}, Q4: {1: "animal", 2: "vehicle"}}
The result should look like this:
result = {("Q1", "Q2"): {0: "no", 1: "yes"},
("Q3", "Q4"): {1: "animal", 2: "vehicle"}}
I've tried the solutions listed here:
Grouping Python dictionary keys as a list and create a new dictionary with this list as a value
Using collections.defaultdict does not work because the result would imply that the dictionaries which I use as a key for grouping end up as keys of the result dictionary like that:
result = {{0: "no", 1: "yes"}: ["Q1", "Q2"] ,
{1: "animal", 2: "vehicle"}: ["Q3", "Q4"]}
Of course this does not work because keys of dictionaries have to be immutible. So I would require something like a frozendict which is not available in the standard library of python.
Using itertools.groupby also does not work because it requires the data to be sorted. But operator.itemgetter cannot sort dictionaries. It says:
TypeError: '<' not supported between instances of 'dict' and 'dict'
Therefore, I'd like to know a Pythonic way of solving this problem! Thank you for your help :)

Instead of using frozendict, you can use frozenset's of the dictionaries' items:
intermediate_dict = defaultdict(list)
for k, v in my_dict.items():
intermediate_dict[frozenset(v.items())].append(k)
result = {tuple(v): dict(k) for k, v in intermediate_dict.items()}
Output:
{('Q1', 'Q2'): {0: 'no', 1: 'yes'}, ('Q3', 'Q4'): {1: 'animal', 2: 'vehicle'}}
The above does not assume or require sorted input, making it O(n) for all cases, while sorting is O(n log n).

Assuming a sorted dictionary by value, you can use itertools.groupby:
{tuple(g): k for k, g in groupby(my_dict, key=my_dict.get)}
Code:
from itertools import groupby
my_dict = {"Q1": {0: "no", 1: "yes"}, "Q2": {0: "no", 1: "yes"},
"Q3": {1: "animal", 2: "vehicle"}, "Q4": {1: "animal", 2: "vehicle"}}
print({tuple(g): k for k, g in groupby(my_dict, key=my_dict.get)})
# {('Q1', 'Q2'): {0: 'no', 1: 'yes'}, ('Q3', 'Q4'): {1: 'animal', 2: 'vehicle'}}

So I would require something like a frozendict which is not available in the standard library of python.
Could you elaborate on this? While frozendict is not in the language standard, there's an extension available that you could install: https://pypi.org/project/frozendict/
Alternatively, you can turn the dictionaries into a tuple of (key-sorted) (key, value) items to get an immutable, canonical and reversible representation that can be used as a dictionary key.
(Note that if the dictionaries can have further mutable values inside them, you might need to do this recursively.)
Edit: Or use a frozenset() for the items, as the other answer points out. Note that this also requires recursively ensuring the values of the inner dictionary are immutable.

Here is another way using both frozenset and groupby
from operator import itemgetter
from itertools import groupby
first = itemgetter(0)
second = itemgetter(1)
my_hashes = sorted([(k, hash(frozenset(v))) for k, v in my_dict.items()], key=second)
d = dict()
for k, v in groupby(my_hashes, key=second):
items = list(v)
d[tuple(map(first, items))] = my_dict.get(first(first(items)))
print(d)
{('Q3', 'Q4'): {1: 'animal', 2: 'vehicle'}, ('Q1', 'Q2'): {0: 'no', 1: 'yes'}}

Related

How to convert dict of dict to dict of specified format?

I have a dictionary of dictionaries as shown below:
d = {0: {1: ["hello"], 2: ["How are you"]}, 1: {1: ["!"], 2: ["?"]}}
and I would want it be in required format:
result = {1:["hello", "!"], 2: ["How are you", "?"]}
However, I get this in the following format using the code below:
new_d = {}
for sub in d.values():
for key, value in sub.items():
new_d.setdefault(key, []).append(value)
The result is not of required structure and it causes a list of lists.
{1: [['hello'], ['!']], 2: [['How are you'], ['?']]}
Any help here would be highly appreciated. Thanks.
use extend instead of append:
new_d.setdefault(key, []).extend(value)
The extend() method adds all the elements of an iterable (list, tuple, string etc.) to the end of the list.
If you want to solve this problem with using append() function try this code:
new_d = {}
for sub in d.values():
for key, value in sub.items():
# Control key exist...
if(key in new_d.keys()):
new_d[key].append(value[0])
else:
new_d[key] = value
You can either use .extend(value) instead of .append(value)
or you can add a basic for loop to flatten the list of all dictionary values as shown below.
new_d = {}
for sub in d.values():
for key, value in sub.items():
new_d.setdefault(key, []).extend(value)
for i in range (0,len(d)):
new_d[i+1] = [item for sublist in new_d.get(i+1) for item in sublist]
print(new_d)
The accepted answer by #Gabip correctly identifies that your only mistake was using append instead of extend.
That mistake being corrected, I'd also like to suggest a slightly different approach using dict comprehensions:
d = {0: {1: ["hello"], 2: ["How are you"]}, 1: {1: ["!"], 2: ["?"]}}
new_d = {key: d[0].get(key, []) + d[1].get(key, []) for key in d[0]}
# {1: ['hello', '!'], 2: ['How are you', '?']}
Or a more robust version that takes keys from both d[0] and d[1], in case some keys are in d[1] but not in d[0]:
d = {0: {1: ["hello"], 2: ["How are you"]}, 1: {1: ["!"], 2: ["?"], 3: ['>>>']}}
new_d = {key: d[0].get(key, []) + d[1].get(key, []) for key in set(d[0].keys()) | set(d[1].keys())}
# {1: ['hello', '!'], 2: ['How are you', '?'], 3: ['>>>']}
Finally, this wasn't explicitly part of your question, but I suggest using str.join to join the strings:
d = {0: {1: ["hello"], 2: ["How are you"]}, 1: {1: ["!"], 2: ["?"]}}
new_d = {key: ''.join(d[0].get(key, []) + d[1].get(key, [])) for key in d[0]}
# {1: 'hello!', 2: 'How are you?'}

How can i combine a list of dicts to a list of dicts combining like keys

I want to take this input:
mycounter = [{6: ['Credit card']}, {2: ['Debit card']}, {2: ['Check']}]
#[{6: ['Credit card']}, {2: ['Debit card']}, {2: ['Check']}]
And achieve this Desired Output:
[{6: ['Credit card']}, {2: ['Debit card', 'Check']}]
My attempt was the following, but it's not matching desired output. Any help here is appreciated. Thx.
temp = list(zip([*map(lambda d: next(iter(d.keys())), mycounter)], [*map(lambda d: next(iter(d.values())), mycounter)]))
c = collections.defaultdict(list)
for a,b in temp:
c[a].extend(b)
final = [dict(c)]
# Close, but not quite the desired output since it's should be two dict objects, not one
# [{6: ['Credit card'], 2: ['Debit card', 'Check']}]
My searches on stackoverflow found solutions that combine with giving None values, but nothing like what i'm asking for. My question has a different input than another similar question earlier.
For the sake of giving options, here is an alternate version that uses defaultdict, which I find easier to use especially as complexity increases:
from collections import defaultdict
result = defaultdict(list)
for d in mycounter:
for k, v in d.items():
result[k] += v
In : result
Out: defaultdict(list, {6: ['Credit card'], 2: ['Debit card', 'Check']})
Defaultdict behaves like dict for the most part but if necessary it can be converted to one with:
In : dict(result)
Out: {6: ['Credit card'], 2: ['Debit card', 'Check']}
One option would be to make a single dictionary then break it into single key-value pairs after:
mycounter = [{6: ['Credit card']}, {2: ['Debit card']}, {2: ['Check']}]
res = {}
for d in mycounter:
for k, v in d.items():
res.setdefault(k, []).extend(v)
[{k:v} for k, v in res.items()]
# [{6: ['Credit card']}, {2: ['Debit card', 'Check']}]
This is very similar to another question; the only difference is that you want each key/value pair in its own dictionary. Here's an adapted solution using comprehensions and itertools:
from itertools import chain
def merge_dicts(*dicts):
return [
{ k: list(chain.from_iterable( d[k] for d in dicts if k in d )) }
for k in set(chain.from_iterable(dicts))
]

Pythonic way of switching nested dictionary keys with nested values

In short I'm working with a nested dictionary structured like this:
nested_dict = {'key1':{'nestedkey1': 'nestedvalue1'}}
I'm trying to find a pythonic way of switching the keys with the nested values, so it would look like this:
nested_dict = {'nestedvalue1':{'nestedkey1': 'key1'}}
I'm also trying to rename the nested key values, so ultimately the dictionary would look like this:
nested_dict = {'nestedvalue1':{'NEWnestedkey1': 'key1'}}
This is closer to what I'm working with:
original_dict = {
'buford': {'id': 1},
'henley': {'id': 2},
'emi': {'id': 3},
'bronc': {'id': 4}
}
I want it to look like this:
new_dict = {
1: {'pet': 'buford'},
2: {'pet': 'henley'},
3: {'pet': 'emi'},
4: {'pet': 'bronc'}
}
Is there a way to do this in one line using a dictionary comprehension? I'm trying to get the very basics here and avoid having to use things like itertools.
You can use a dictionary comprehension to achieve this, 'swapping' things round as you build it:
new_dict = {v['id']: {'pet': k} for k, v in original_dict.items()}
To expand it to a for loop, it'd look something like:
new_dict = {}
for k, v in original_dict.items():
new_dict[v['id']] = {'pet': k}
Note that both cases obviously rely on the 'id' value being unique, or the key will be overwritten for each occurrence.
For a more generic solution, you can try this:
def replace(d, change_to = 'pet'):
return {b.values()[0]:{change_to:a} for a, b in d.items()}
Output:
{1: {'pet': 'buford'}, 2: {'pet': 'henley'}, 3: {'pet': 'emi'}, 4: {'pet': 'bronc'}}

Compact list of dictionaries into into single dictionary?

I have a list of dictionaries like the following:
l = [{0: [1L, 743.1912508784121]}, {0: [2L, 148.34440427559701]}, {0: [5L, 1275.9155165676464]}, {0: [6L, 128.46132477853394]}, {0: [8L, 1120.5549823618721]}, {0: [9L, 1000.4359061629533]}, {0: [10L, 1000.4359061629533]}, {0: [11L, 1148.2027994669606]}, {0: [12L, 222.1206974476257]}, {0: [15L, 1024.0437005257695]}, {1: [8L, 606.0185176629063]}, {1: [13L, 115.54464589045607]}, {1: [14L, 1057.134622491455]}, {1: [16L, 1000.346200460439]}, {1: [17L, 285.73897308106336]}, {2: [3L, 941.8651982485691]}, {2: [4L, 1001.6313224538114]}, {2: [7L, 1017.0693313362076]}, {2: [11L, 427.7241587977401]}]
in this specific case the list has 19 dictionaries with 3 different keys (0,1,2).
What I'm trying to do is to transform it into a single dictionary where the values of each key is made by another dictionary.
So for example, extracting 4 elements of the list, I'd like to compact this:
l = [{0: [1L, 743.1912508784121]}, {0: [2L, 148.34440427559701]}, {1: [13L, 115.54464589045607]}, {1: [14L, 1057.134622491455]}]
into:
d = {0:{1L: 743.1912508784121, 2L: 148.34440427559701}, 1:{13L: 115.54464589045607, 14L: 1057.134622491455}}
I hope I made myself clear
This'll work, hopefully the code should be fairly self-explanatory.
Note that you'll need to use dictionary.iteritems() in Python 2.x, as dictionary.items() is Python 3.x only.
d ={}
for dictionary in l:
for key, (k, v) in dictionary.items():
if key not in d:
d[key] = {}
d[key][k] = v
You may get this result using collections.defaultdict as:
from collections import defautdict
my_dict = defaultdict(dict)
for d in l:
for k, (v1, v2) in d.items():
my_dict[k][v1] = v2
where my_dict will hold the final value as:
{0: {1L: 743.1912508784121, 2L: 148.34440427559701, 5L: 1275.9155165676464, 6L: 128.46132477853394, 8L: 1120.5549823618721, 9L: 1000.4359061629533, 10L: 1000.4359061629533, 11L: 1148.2027994669606, 12L: 222.1206974476257, 15L: 1024.0437005257695}, 1: {8L: 606.0185176629063, 16L: 1000.346200460439, 13L: 115.54464589045607, 14L: 1057.134622491455, 17L: 285.73897308106336}, 2: {11L: 427.7241587977401, 3L: 941.8651982485691, 4L: 1001.6313224538114, 7L: 1017.0693313362076}}
Note: Since dict can have unique keys, it will have the value of nested dict based on the last value in the list.

Common items between all the keys of a dictionary

I have a dictionary of "259136 keys" and each of those keys, have 1 or more than one values.
My objective is "to find keys that have at least one value common with another key in the list of keys?"
I have tried different ways to deal with this problem but I was looking for a faster solution. I tried
for each key compare with the 259135 keys to check the above condition
reversing the dictionary from key value to value key, so now the value becomes key and this way I will have two dictionaries and I can go to first one and based on the values in the first one pull out all the values from the second one.
Use a dict of sets:
d={ 'k1': [1,2,3],
'k2': [2],
'k3': [10],
'k4': [3,2]
}
com_keys={}
for k, v in d.items():
for e in v:
com_keys.setdefault(e, set()).add(k)
print com_keys
# {1: set(['k1']), 10: set(['k3']), 3: set(['k1', 'k4']), 2: set(['k2', 'k1', 'k4'])}
Then if you only want the ones that have more than one key in common, just filter with a dict comprehension (or the like for older Pythons):
>>> {k:v for k,v in com_keys.items() if len(v)>1 }
{2: set(['k2', 'k1', 'k4']), 3: set(['k1', 'k4'])}
It get a little more challenging if your dict is a non-homogenous combination of containers that support iteration (lists, tuples, etc) with 'single items' that either do not support iteration (ints, floats) or things that you do not want to iterate with a for loop (strings, unicode, other dicts, etc)
For example, assume you have a combination of lists and 'single items' that are ints and strings:
import collections
d={ 'k1': [1,2,3],
'k2': 2,
'k3': [10],
'k4': [3,2],
'k5': 'string',
'k6': ['string',2]
}
com_keys={}
for k, v in d.items():
if not isinstance(v, basestring) and isinstance(v, collections.Iterable):
for e in v:
com_keys.setdefault(e, set()).add(k)
else:
com_keys.setdefault(v, set()).add(k)
print com_keys
# {1: set(['k1']), 10: set(['k3']), 3: set(['k1', 'k4']), 2: set(['k2', 'k1', 'k6', 'k4']), 'string': set(['k6', 'k5'])}
print {k:v for k,v in com_keys.items() if len(v)>1 }
# {2: set(['k2', 'k1', 'k6', 'k4']), 3: set(['k1', 'k4']), 'string': set(['k6', 'k5'])}

Categories