How to best reverse a DAG with adjacency list representation? - python

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)

Related

How to extract values from list of dictionary that match the keys in another list

I have a list of keys:
l_keys = ['a', 'c', 'd']
And I have a list of dictionary:
l_dict = [{'a': 1, 'b': 2, 'c': 3}, {'a':4, 'd':5}]
The result I want to get is:
[{'a': 1, 'c': 3}, {'a': 4, 'd': 5}]
.
I can achieve this result in the following way
[{k: d[key] for k in l_keys if k in l_dict} for d in l_dict]
.
Explain:
I actually go through every object in l_dict and then I go through every key in l_keys and check if that key is in the current object and if so I retrieve it and its value
My question is if there is a better, professional and faster way in terms of time complexity to do that.
Firstly, your list comprehension should be: [{k: d[k] for k in l_keys if k in d} for d in l_dict]
If you know that len(l_keys) will usually be smaller than the dicts in l_dict, your way is the most efficient. Otherwise, it would be better to check whether each key in the dict is in l_keys: [{k: d[k] for k in d if k in l_keys} for d in l_dict] l_set = set(l_keys): [{k: d[k] for k in d if k in l_set} for d in l_dict]
This page might be helpful when it comes to time complexity: https://wiki.python.org/moin/TimeComplexity#dict

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

Python remove duplicate value in a combined dictionary's list

I need a little bit of homework help. I have to write a function that combines several dictionaries into new dictionary. If a key appears more than once; the values corresponding to that key in the new dictionary should be a unique list. As an example this is what I have so far:
f = {'a': 'apple', 'c': 'cat', 'b': 'bat', 'd': 'dog'}
g = {'c': 'car', 'b': 'bat', 'e': 'elephant'}
h = {'b': 'boy', 'd': 'deer'}
r = {'a': 'adam'}
def merge(*d):
newdicts={}
for dict in d:
for k in dict.items():
if k[0] in newdicts:
newdicts[k[0]].append(k[1])
else:
newdicts[k[0]]=[k[1]]
return newdicts
combined = merge(f, g, h, r)
print(combined)
The output looks like:
{'a': ['apple', 'adam'], 'c': ['cat', 'car'], 'b': ['bat', 'bat', 'boy'], 'e': ['elephant'], 'd': ['dog', 'deer']}
Under the 'b' key, 'bat' appears twice. How do I remove the duplicates?
I've looked under filter, lambda but I couldn't figure out how to use with (maybe b/c it's a list in a dictionary?)
Any help would be appreciated. And thank you in advance for all your help!
Just test for the element inside the list before adding it: -
for k in dict.items():
if k[0] in newdicts:
if k[1] not in newdicts[k[0]]: # Do this test before adding.
newdicts[k[0]].append(k[1])
else:
newdicts[k[0]]=[k[1]]
And since you want just unique elements in the value list, then you can just use a Set as value instead. Also, you can use a defaultdict here, so that you don't have to test for key existence before adding.
Also, don't use built-in for your as your variable names. Instead of dict some other variable.
So, you can modify your merge method as:
from collections import defaultdict
def merge(*d):
newdicts = defaultdict(set) # Define a defaultdict
for each_dict in d:
# dict.items() returns a list of (k, v) tuple.
# So, you can directly unpack the tuple in two loop variables.
for k, v in each_dict.items():
newdicts[k].add(v)
# And if you want the exact representation that you have shown
# You can build a normal dict out of your newly built dict.
unique = {key: list(value) for key, value in newdicts.items()}
return unique
>>> import collections
>>> import itertools
>>> uniques = collections.defaultdict(set)
>>> for k, v in itertools.chain(f.items(), g.items(), h.items(), r.items()):
... uniques[k].add(v)
...
>>> uniques
defaultdict(<type 'set'>, {'a': set(['apple', 'adam']), 'c': set(['car', 'cat']), 'b': set(['boy', 'bat']), 'e': set(['elephant']), 'd': set(['deer', 'dog'])})
Note the results are in a set, not a list -- far more computationally efficient this way. If you would like the final form to be lists then you can do the following:
>>> {x: list(y) for x, y in uniques.items()}
{'a': ['apple', 'adam'], 'c': ['car', 'cat'], 'b': ['boy', 'bat'], 'e': ['elephant'], 'd': ['deer', 'dog']}
In your for loop add this:
for dict in d:
for k in dict.items():
if k[0] in newdicts:
# This line below
if k[1] not in newdicts[k[0]]:
newdicts[k[0]].append(k[1])
else:
newdicts[k[0]]=[k[1]]
This makes sure duplicates aren't added
Use set when you want unique elements:
def merge_dicts(*d):
result={}
for dict in d:
for key, value in dict.items():
result.setdefault(key, set()).add(value)
return result
Try to avoid using indices; unpack tuples instead.

Should dict store key in order by inc?

I have a dict with integers as keys. Tell me please, does a dict store data with sorted keys or not?
I wrote a little code to test (as follows):
>>>
>>> d = {1: 'a', 3: 'a'}
>>> d
{1: 'a', 3: 'a'}
>>> d[2] = 'a'
>>> d
{1: 'a', 2: 'a', 3: 'a'}
>>>
But I am not sure that this behavior is standard and works all the time.
Dictionaries in python are not sorted. Read more on dicts here:
http://docs.python.org/library/stdtypes.html?highlight=dict#dict
But you can use sorted python built-in method to sort keys:
for k in sorted(myDict):
myDict[k] # do something
Or Look here for collections.OrderedDict implementation
You can also mix sorted method and OrderedDict to use it later(sure this will only word in case you will not add new items into it - otherwise it simply better to use sorted method):
d = {1: 'a', 3: 'a'}
from collections import OrderedDict
sorted_d = OrderedDict((k, d[k]) for k in sorted(d))
A little bit more experimenting would have soon shown you that they are not sorted:
>>> d = {1: 'a', 3: 'a', 8: 'a'}
>>> d
{8: 'a', 1: 'a', 3: 'a'}
but even that is implementation dependant. Don't depend on the order at all.
The internal dict keeps no sorting order for the keys. If you want a fast C-implementation for C python have a look at sorteddict which is included in my ordereddict package for CPython:
http://anthon.home.xs4all.nl/Python/ordereddict/

Categories