Relationship graph in Python - python

I want to calculate number of friends for each person given a relationship graph without using any libraries. The graph is represented as lists of lists:
graph = [[A,B],[A,C],[C,B],[B,D],[E]]
Expected dictionary output: {'A':2, 'B':3, 'C':2, 'D':1, 'E':0}
Note: Since E has no friends, E should be 0

Straightforward solution without changing input format
>>> graph = [['A', 'B'], ['A', 'C'],['C', 'B'], ['B', 'D'], ['E']]
>>> from collections import defaultdict
>>> friends_counter = defaultdict(int)
>>> for friends in graph:
... for person in friends:
... friends_counter[person] += len(friends) - 1
>>> dict(friends_counter)
{'A': 2, 'B': 3, 'C': 2, 'D': 1, 'E': 0}

You could use a python library specific to graphs called NetworkX. I changed the data to be easier to load.
import networkx as nx
graph = [['A','B'],['A','C'],['C','B'],['B','D']]
G = nx.Graph()
G.add_edges_from(graph)
G.add_node('E')
dict(G.degree)
# {'A': 2, 'B': 3, 'C': 2, 'D': 1, 'E': 0}
Edit: this answer was given before the "without using any libraries" caveat was added.

Got the solution. Is there a better way to do this?
graph = [['A','B'],['A','C'],['C','B'],['B','D'],['E']]
dct ={}
for v in graph:
for x in v:
if x in v:
if x in dct.keys():
dct[x] += 1
else:
dct[x]= len(v)-1
print(dct)
{'A': 2, 'B': 3, 'C': 2, 'D': 1, 'E': 0}

so you can do like this
graph = [["A","B"],["A","C"],["C","B"],["B","D"],["E"]]
ans = {}
for n in graph:
if len(n) == 1:
ans[n[0]] = ans.get(n[0], 0)
else:
l, r = n
ans[l] = ans.get(l, 0) + 1
ans[r] = ans.get(r, 0) + 1
print(ans)
# {'A': 2, 'B': 3, 'C': 2, 'D': 1, 'E': 0}

Related

Convert each list element into a nested dictionary key

There is this list of string that I need to use to create a nested dictionary with some values ['C/A', 'C/B/A', 'C/B/B']
The output will be in the format {'C': {'A': [1, 2, 3], 'B': {'A': [1, 2, 3], 'B': [1, 2, 3]}}}
I've tried to use the below code to create the nested dictionary and update the value, but instead I get {'C': {'A': [1, 2, 3], 'C': {'B': {'A': [1, 2, 3], 'C': {'B': {'B': [1, 2, 3]}}}}}} as the output which is not the correct format. I'm still trying to figure out a way. any ideas?
s = ['C/A', 'C/B/A', 'C/B/B']
new = current = dict()
for each in s:
lst = each.split('/')
for i in range(len(lst)):
current[lst[i]] = dict()
if i != len(lst)-1:
current = current[lst[i]]
else:
current[lst[i]] = [1,2,3]
print(new)
You can create a custom Tree class:
class Tree(dict):
'''
Create arbitrarily nested dicts.
>>> t = Tree()
>>> t[1][2][3] = 4
>>> t
{1: {2: {3: 4}}}
>>> t.set_nested_item('a', 'b', 'c', value=5)
>>> t
{1: {2: {3: 4}}, 'a': {'b': {'c': 5}}}
'''
def __missing__(self, key):
self[key] = type(self)()
return self[key]
def set_nested_item(self, *keys, value):
head, *rest = keys
if not rest:
self[head] = value
else:
self[head].set_nested_item(*rest, value=value)
>>> s = ['C/A', 'C/B/A', 'C/B/B']
>>> output = Tree()
>>> default = [1, 2, 3]
>>> for item in s:
... output.set_nested_item(*item.split('/'), value=list(default))
>>> output
{'C': {'A': [1, 2, 3], 'B': {'A': [1, 2, 3], 'B': [1, 2, 3]}}}
You do not need numpy for this problem, but you may want to use recursion. Here is a recursive function add that adds a list of string keys lst and eventually a list of numbers to the dictionary d:
def add(lst, d):
key = lst[0]
if len(lst) == 1: # if the list has only 1 element
d[key] = [1, 2, 3] # That element is the last key
return
if key not in d: # Haven't seen that key before
d[key] = dict()
add(lst[1:], d[key]) # The recursive part
To use the function, create a new dictionary and apply the function to each splitter string:
d = dict()
for each in s:
add(each.split("/"), d)
# d
# {'C': {'A': [1, 2, 3], 'B': {'A': [1, 2, 3], 'B': [1, 2, 3]}}}

Read Multiple Dictionaries into a nested list

I am reading below list into a Counter and I want to group all keys into a nested list as shown below
import collections
A=["cool","lock","cook"]
B=[]
d={}
for i in A:
B.append(collections.Counter(i))
print(B)
## B value is [Counter({'o': 2, 'c': 1, 'l': 1}), Counter({'l': 1, 'o': 1, 'c': 1, 'k': 1}), Counter({'o': 2, 'c': 1, 'k': 1})]
for i in B:
for j in i.keys():
d.setdefault( d[j],[]).append(i.values())
print(d)
I am getting a Key Error, I have used Setdefault() but able to get it work.
Needed output:
{'o':[2,1,2],'c':[1,1,1],'l':[1,1],'k':[1,1] }
Here is how:
import collections
A = ["cool", "lock", "cook"]
B = []
d = {}
for i in A:
B.append(collections.Counter(i))
for i in B:
for j in i:
if j in d:
d[j].append(i[j])
else:
d[j] = [i[j]]
print(d)
Output:
{'c': [1, 1, 1], 'o': [2, 1, 2], 'l': [1, 1], 'k': [1, 1]}
You may even use map when defining B to improve the efficiency:
import collections
A = ["cool", "lock", "cook"]
B = map(collections.Counter, A)
d = {}
for i in B:
for j in i:
if j in d:
d[j].append(i[j])
else:
d[j] = [i[j]]
print(d)

merging two python dicts and keeping the max key, val in the new updated dict

I need a method where I can merge two dicts keeping the max value when one of the keys, value are in both dicts.
dict_a maps "A", "B", "C" to 3, 2, 6
dict_b maps "B", "C", "D" to 7, 4, 1
final_dict map "A", "B", "C", "D" to 3, 7, 6, 1
I did get the job half done but I didn't figure out how to keep the max value for the 'C' key, value pair.
Used itertools chain() or update().
OK so this works by making a union set of all possible keys dict_a.keys() | dict_b.keys() and then using dict.get which by default returns None if the key is not present (rather than throwing an error). We then take the max (of the one which isn't None).
def none_max(a, b):
if a is None:
return b
if b is None:
return a
return max(a, b)
def max_dict(dict_a, dict_b):
all_keys = dict_a.keys() | dict_b.keys()
return {k: none_max(dict_a.get(k), dict_b.get(k)) for k in all_keys}
Note that this will work with any comparable values -- many of the other answers fail for negatives or zeros.
Example:
Inputs:
dict_a = {'a': 3, 'b': 2, 'c': 6}
dict_b = {'b': 7, 'c': 4, 'd': 1}
Outputs:
max_dict(dict_a, dict_b) # == {'b': 7, 'c': 6, 'd': 1, 'a': 3}
What about
{
k:max(
dict_a.get(k,-float('inf')),
dict_b.get(k,-float('inf'))
) for k in dict_a.keys()|dict_b.keys()
}
which returns
{'A': 3, 'D': 1, 'C': 6, 'B': 7}
With
>>> dict_a = {'A':3, 'B':2, 'C':6}
>>> dict_b = {'B':7, 'C':4, 'D':1}
Here is a working one liner
from itertools import chain
x = dict(a=30,b=40,c=50)
y = dict(a=100,d=10,c=30)
x = {k:max(x.get(k, 0), y.get(k, 0)) for k in set(chain(x,y))}
In[83]: sorted(x.items())
Out[83]: [('a', 100), ('b', 40), ('c', 50), ('d', 10)]
This is going to work in any case, i.e for common keys it will take the max of the value otherwise the existing value from corresponding dict.
Extending this so you can have any number of dictionaries in a list rather than just two:
a = {'a': 3, 'b': 2, 'c': 6}
b = {'b': 7, 'c': 4, 'd': 1}
c = {'c': 1, 'd': 5, 'e': 7}
all_dicts = [a,b,c]
from functools import reduce
all_keys = reduce((lambda x,y : x | y),[d.keys() for d in all_dicts])
max_dict = { k : max(d.get(k,0) for d in all_dicts) for k in all_keys }
If you know that all your values are non-negative (or have a clear smallest number), then this oneliner can solve your issue:
a = dict(a=3,b=2,c=6)
b = dict(b=7,c=4,d=1)
merged = { k: max(a.get(k, 0), b.get(k, 0)) for k in set(a) | set(b) }
Use your smallest-possible-number instead of the 0. (E. g. float('-inf') or similar.)
Yet another solution:
a = {"A":3, "B":2, "C":6}
b = {"B":7, "C":4, "D":1}
Two liner:
b.update({k:max(a[k],b[k]) for k in a if b.get(k,'')})
res = {**a, **b}
Or if you don't want to change b:
b_copy = dict(b)
b_copy.update({k:max(a[k],b[k]) for k in a if b.get(k,'')})
res = {**a, **b_copy}
> {'A': 3, 'B': 7, 'C': 6, 'D': 1}

Performing circular shif on values in a dictionary

I have to construct a large dictionary which looks something like this
{'A' : {'A' : 1, 'B' : 0, 'C' : 0},
'B' : {'A' : 0, 'B' : 1, 'C' : 0}}
............................
Each inner dictionary is shifted by one position. Is there a way I can automate this process.
I can loop and use np.roll() function if it were ndarray so I am wondering if there's something similiar I could do with dictionaries.
You can use collections.deque for fast dictionary values (list) shifting (deque rotate function) but there is no specific function for dictionary, you should make it yourself, for example:
# shift dct (dict) values by n to right if n is positive
# and to left if n is negative; returns new dictionary
def dict_roll(dct, n):
shift_values = deque(dct.values())
shift_values.rotate(n)
return dict(zip(dct.keys(), shift_values))
Demo:
>>> d = {'A': {'A': 1, 'C': 0, 'B': 0}, 'B': {'A': 0, 'C': 0, 'B': 1}}
>>> for k in d:
... d[k] = dict_roll(d[k], 1)
...
>>> d
{'A': {'A': 0, 'C': 1, 'B': 0}, 'B': {'A': 1, 'C': 0, 'B': 0}}
>>> for k in d:
... d[k] = dict_roll(d[k], 1)
...
>>> d
{'A': {'A': 0, 'C': 0, 'B': 1}, 'B': {'A': 0, 'C': 1, 'B': 0}}
And another solution.
Solution 1:
out = dict()
syms = ['A', 'B', 'C']
for sym_outer in syms:
inner_dict = dict()
for sym_inner in syms:
inner_dict[sym_inner] = 0 if sym_inner != sym_outer else 1
out[sym_outer] = inner_dict
Solution 2:
This is a partial solution, but you get out['A']['A'] == 1 and out['A']['B'] == 0.
from collections import defaultdict
out = defaultdict(lambda: defaultdict(int))
for sym in ['A', 'B', 'C']:
out[sym][sym] = 1
This will create your string in r:
import string
r = {}
a = [1] + [0] * 25
for i in string.ascii_uppercase:
r[i] = dict(zip(string.ascii_uppercase,a))
a = a[-1:] + a[:-1]
print r
Bonus one-liner
If you are using Python 2.7+ or Python 3, then you can get a one-liner using dictionary comprehensions:
import string
{i:{j:1 if i==j else 0 for j in string.ascii_uppercase} for i in string.ascii_uppercase}

Transforming list to dict with list items as keys and list indices as values in python

I want to transform
l = ['a','b','c','d']
to
d = {'a': 0, 'b': 1, 'c': 2, 'd': 3}
The best solution I have so far is that one:
d = {l[i]:i for i in range(len(l))}
Is there more elegant way to do this?
d = {e:i for i, e in enumerate(l)}
Edit: As #LeonYoung suggested, if you want to be compatible with python < 2.7 (despite the tag), you must use
d = dict((e, i) for i, e in enumerate(l))
With itertools, just for fun
>>> from itertools import count, izip
>>> L = ['a', 'b', 'c', 'd']
>>> dict(izip(L, count()))
{'a': 0, 'c': 2, 'b': 1, 'd': 3}

Categories

Resources