Merging n'th level multidimensional dictionary - python

Ok so what I have is:
foo = {1: {'f1': 'c1'}, 2: {'f2': 'c2'}}
What I want to get using list comprehension(if possible of course) is merge all 2nd level dictionary values this:
bar = {'f1': 'c1', 'f2': 'c2'}
What I tried is this:
bar = {k:v for k, v in tmp.items() for tmp in list(foo.values())}
Now this gets me:
NameError: name 'tmp' is not defined
This seamed like logical solution but apparently its a no go, could you please suggest a solution using list comprehension if possible, or some other one liner, or if that would not be possible an insight into why I get this error will be good knowledge to have.
Thanks,

You can use a dictionary comprehension:
foo = {1: {'f1': 'c1'}, 2: {'f2': 'c2'}}
bar = {k: v for d in foo.values() for k, v in d.items()}
{'f1': 'c1', 'f2': 'c2'}
You were close, but the order of your nesting is incorrect. This is tricky and not intuitive to everyone. Read your comprehension as you would write a normal for loop.
There is also no need to make a list out of your dictionary values.
Related: Understanding nested list comprehension.

Related

How to best reverse a DAG with adjacency list representation?

I have the following DAG adjacency list representation:
# this reads: b depends on a, c depends on a and d depends on b and c
graph = {'b': {'a'}, 'c': {'a'}, 'd': {'b', 'c'}}
I want to get the following inverted representation:
graph = {'a': {'b', 'c'}, 'b': {'d'}, 'c': {'d'}}
Using Python 3.x I can do the following, basically turn the graph into a list of tuples and the convert the list of tuples to a dictionary (collapsing different values into a set):
inverted_graph = {}
for k, v in [(v, k) for k in graph for v in graph[k]]:
inverted_graph.setdefault(k, set()).add(v)
Is there a simpler / faster way to do it without double looping?
I'm not sure why you wrote this triple loop. Two loops should be sufficient. You don't need an extra loop to swap k and v when you can just use them in opposite places.
Also, a defaultdict can simplify the code.
from collections import defaultdict
inverted_graph = defaultdict(set)
for k in graph:
for v in graph[k]:
inverted_graph[v].add(k)

Dictionary Comprehension in Python for key:[1,2,3] [duplicate]

This question already has answers here:
is it possible to reverse a dictionary in python using dictionary comprehension
(5 answers)
Closed 2 years ago.
While I've been improving my Python skills I have one question.
My code is below:
# def invertDictionary(dict):
# new_dict = {}
# for key, value in dict.items():
# if value in new_dict:
# new_dict[value].append(key)
# else:
# new_dict[value]=[key]
# return new_dict
def invertDictionary(dict):
new_dict = {value:([key] if value else [key]) for key, value in dict.items()}
return new_dict;
invertDictionary({'a':3, 'b':3, 'c':3})
I am trying to get output like {3:['a','b','c']}. I have achieved that using a normal for-loop; I just want to know how to get these results using a Dictionary Comprehension. I tried but in append it's getting an error. Please let me know how to achieve this.
Thanks in Advance!
You missed that you also need a list comprehension to build the list.
Iterate over the values in the dict, and build the needed list of keys for each one.
Note that this is a quadratic process, whereas the canonical (and more readable) for loop is linear.
d = {'a':3, 'b':3, 'c':3, 'e':4, 'f':4, 'g':0}
inv_dict = {v: [key for key, val in d.items() if val == v]
for v in set(d.values())}
result:
{0: ['g'],
3: ['a', 'b', 'c'],
4: ['e', 'f']
}
Will this do?
while your original version with a regular for loop is the best solution for this, here is a variation on #Prune answer that doesn't goes over the dict multiple times
>>> import itertools
>>> d = {'a':3, 'b':3, 'c':3, 'e':4, 'f':4, 'g':0}
>>> {group_key:[k for k,_ in dict_items]
for group_key,dict_items in itertools.groupby(
sorted(d.items(),key=lambda x:x[-1]),
key=lambda x:x[-1]
)
}
{0: ['g'], 3: ['a', 'b', 'c'], 4: ['e', 'f']}
>>>
first we sorted the items of the dict by value with a key function to sorted using a lambda function to extract the value part of the item tuple, then we use the groupby to group those with the same value together with the same key function and finally with a list comprehension extract just the key
--
as noted by Kelly, we can use the get method from the dict to get the value to make it shorter and use the fact that iteration over a dict give you its keys
>>> {k: list(g) for k, g in itertools.groupby(sorted(d, key=d.get), d.get)}
{0: ['g'], 3: ['a', 'b', 'c'], 4: ['e', 'f']}
>>>
You could use a defalutdict and the append method.
from collections import defaultdict
dict1 = {'a': 3, 'b': 3, 'c': 3}
dict2 = defaultdict(list)
{dict2[v].append(k) for k, v in dict1.items()}
dict2
>>> defaultdict(list, {3: ['a', 'b', 'c']})

Create two dictionaries by iterating through a function that returns a tuple of two elements in Python

I want to create two dictionaries in python by dictionary comprehension at the same time. The two dictionaries share the same key set, but have different values for each key. Therefore, I use a function to return a tuple of two values, and hoping a dictionary comprehension can create these two dictionaries at the same time.
Say, I have a function
def my_func(foo):
blablabla...
return a, b
And I will create two dictionaries by
dict_of_a, dict_of_b = ({key:my_func(key)[0]}, {key:my_func(key)[1]} for key in list_of_keys)
Is there any better code to improve it? In my opinion, my_func(key) will be called twice in each iteration, slowing down the code. What is the correct way to do it?
With ordered slicing:
def myfunc(k):
return k + '0', k + '1'
list_of_keys = ['a', 'b', 'c']
groups = [(k,v) for k in list_of_keys for v in myfunc(k)]
dict_of_a, dict_of_b = dict(groups[::2]), dict(groups[1::2])
print(dict_of_a) # {'a': 'a0', 'b': 'b0', 'c': 'c0'}
print(dict_of_b) # {'a': 'a1', 'b': 'b1', 'c': 'c1'}
for key in list_of_keys:
dict_of_a[key],dict_of_b[key] = my_func(key)
The regular loop is probably the best way to go. If you want to play with functools, you can write:
>>> def func(foo): return foo[0], foo[1:]
...
>>> L = ['a', 'ab', 'abc']
>>> functools.reduce(lambda acc, x: tuple({**d, x: v} for d, v in zip(acc, func(x))), L, ({}, {}))
({'a': 'a', 'ab': 'a', 'abc': 'a'}, {'a': '', 'ab': 'b', 'abc': 'bc'})
The function reduce is a fold: it takes the current accumulator (here the dicts being built) and the next value from L:
d, v in zip(acc, func(x)) extracts the dicts one at a time and the matching element of the return value of func;
{**d, x: v} update the dict with the current value.
I don't recommend this kind of code since it's hard to maintain.
my_func(key) will be called twice in each iteration, slowing down the code
Dont worry about it. Unless you need to do thousands/millions of iterations and the script takes an unreasonably long time to complete, you shouldn't concern with negligible optimization gains.
That said, I'd use something like this:
if __name__ == '__main__':
def my_func(k):
return f'a{k}', f'b{k}'
keys = ['x', 'y', 'z']
results = (my_func(k) for k in keys)
grouped_values = zip(*results)
da, db = [dict(zip(keys, v)) for v in grouped_values]
print(da)
print(db)
# Output:
# {'x': 'ax', 'y': 'ay', 'z': 'az'}
# {'x': 'bx', 'y': 'by', 'z': 'bz'}
You cannot create two dicts in one dict comprehension.
If your primary goal is to just call my_func once to create both dicts, use a function for that:
def mkdicts(keys):
dict_of_a = {}
dict_of_b = {}
for key in keys:
dict_of_a[key], dict_of_b[key] = my_func(key)
return dict_of_a, dict_of_b

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

is it possible to reverse a dictionary in python using dictionary comprehension

I want to reverse dictionary key, value pairs using a dictionary comprehension, but if the new dictionary has more than one value for a key then it is getting replaced with the last value.
Is it possible to append to the values in the new dictionary if a key is repeated, using a comprehension?
Input:
test_di = {'a':'1', 'b':'2', 'c':'3', 'd':'2'}
Code:
{v:k for k,v in test_di.items()}
Output of this code:
{'1': 'a', '3': 'c', '2': 'd'}
Desired output:
{'1': ['a'], '3': ['c'], '2': ['b','d']}
It's not possible to do it in a reasonable way (i.e. O(N) time) with a dictionary comprehension. The comprehension simply can't handle duplicated values.
However, it's quite easy with a regular loop:
d = {}
for key, value in old_d.items():
d.setdefault(value, []).append(key)
A defaultdict would be the most efficient approach:
from collections import defaultdict
test_di = {'a':'1', 'b':'2', 'c':'3', 'd':'2'}
d = defaultdict(list)
for v,k in test_di.items():
d[k].append(v)
print(d)
Your question is confusing, but it sounds like you want to store multiple values at a single key in a dictionary.
This is not possible, you may want to look at storing a dictionary of lists.
You can use an OrderedDict and reverse the keys. A normal dict does not keep track of the order of the keys.
from collections import OrderedDict
dict1 = OrderedDict([('Name', 'Jon'), ('Race', 'Latino'), ('Job', 'Nurse')])
reversed_dict1 = OrderedDict([(key, dict1.get(key)) for key in reversed(dict1)])
Yes, it is possible (but it’s not the most efficient solution):
d = {'a':'1', 'b':'2', 'c':'3', 'd':'2'}
{v1: [k1 for k1, v2 in d.items() if v1 == v2] for v1 in d.values()}
# {'1': ['a'], '2': ['b', 'd'], '3': ['c']}

Categories