Collapsing dictionary by merging matching keys and key,value pairs - python

So I am trying to find a way to "merge" a dependency list which is in the form of a dictionary in python, and I haven't been able to come up with a solution. So imagine a graph along the lines of this: (all of the lines are downward pointing arrows in this directed graph)
1 2 4
\ / / \
3 5 8
\ / \ \
6 7 9
this graph would produce a dependency dictionary that looks like this:
{3:[1,2], 5:[4], 6:[3,5], 7:[5], 8:[4], 9:[8], 1:[], 2:[], 4:[]}
such that keys are nodes in the graph, and their values are the nodes they are dependent on.
I am trying to convert this into a total ancestry list in terms of a tree, so that each node is a key, and its value is a list of ALL nodes that lead to it, not just it's immediate parents. The resulting dictionary would be:
{3:[1,2], 5:[4], 6:[3, 5, 1, 2, 4], 7:[5, 4], 8:[4], 9:[8, 4], 1:[], 2:[], 3:[]}
Any suggestions on how to solve this? I have been banging my head into it for a while, tried a recursive solution that I haven't been able to get working.

You can use a chained dict comprehension with list comprehension for up to two nodes.
>>> {k: v + [item for i in v for item in d.get(i, [])] for k,v in d.items()}
{3: [1, 2],
5: [4],
6: [3, 5, 1, 2, 4],
7: [5, 4],
8: [4],
9: [8, 4],
1: [],
2: [],
4: []}
For unlimited depth, you can use a recursive approach
def get_ant(node, d):
if node:
return d.get(node,[]) + [item for x in d.get(node, []) for item in get_ant(x, d) ]
return []
Then,
>>> get_ant(6, d)
[3, 5, 1, 2, 10, 4]
To get all cases:
>>> {k: get_ant(k, d) for k in d.keys()}
{3: [1, 2, 10],
5: [4],
6: [3, 5, 1, 2, 10, 4],
7: [5, 4],
8: [4],
9: [8, 4],
1: [10],
2: [],
4: []}

Here's a really simple way to do it.
In [22]: a
Out[22]: {1: [], 2: [], 3: [1, 2], 4: [], 5: [4], 6: [3, 5], 7: [5], 8: [4], 9: [8]}
In [23]: final = {}
In [24]: for key in a:
...: nodes = set()
...:
...: for val in a[key]:
...: nodes.add(val)
...: if val in a:
...: nodes.update(set(a[val]))
...:
...: final[key] = list(nodes)
In [25]: final
Out[25]:
{1: [],
2: [],
3: [1, 2],
4: [],
5: [4],
6: [3, 1, 2, 5, 4],
7: [5, 4],
8: [4],
9: [8, 4]}

Related

Creating a random dictionary in python

I would like to create a random dictionary starting from the following array:
list = [1,2,3,4,5]
In particular, I would like to have as keys of the dictionary all the elements of the array and as corresponding keys, randomly pick some values from that array except the value that corresponds to the key
An example of expected output should be something like:
Randomdict = {1: [2, 4], 2: [1,3,5] 3: [2] 4: [2,3,5] 5: [1,2,3,4]}
And last but not least all keys should have at least 1 value
It can be done with the random module and comprehensions:
from random import sample, randrange
d = {i: sample([j for j in lst if i != j], randrange(1, len(lst) - 1))
for i in lst}
If you first use random.seed(0) for reproducible data, you will get:
{1: [3, 2], 2: [4, 3], 3: [2, 4], 4: [3, 1]}
{1: [3], 2: [1], 3: [4, 1], 4: [1, 3]}
{1: [3, 2], 2: [3, 4], 3: [4], 4: [2, 3]}
Something like this? Might needs some tweaks
from random import randrange, sample
q = [1, 2, 3, 4, 5]
a = {}
for i in q:
n = randrange(len(q)-1)
a[i] = sample(q, n)
print(a)

Match dictionary keys and values in new dictionary

I trying to match keys and values in a dictionary together and put them into a new dictionary.
My initial dictionary looks like this:
d = {1: [5,6], 3: [4], 8: [2,3]}
I know that I can access keys and values using d.keys() and d.values().
My goal is to find all items which relate. I think it is best explained if I illustrate my desired output.
I wish to create a new dict that gives me:
finaldict = {1: [5,6], 2: [3,4,8], 3: [2,4,8], 4:[2,3,8] 5:[1,6], 6:[1,5], 8:[2,3,4]}
That is, I want to get keys of all numbers, and give values to what number they are related to.
My attempt:
d = {1: [5,6], 3:[4],8:[2,3]}
print(d)
keys = list(d.keys())
vals = list(d.values())
for i in range(0,len(d.keys())):
current_vals = list(vals[i])
length = len(current_vals)
for v in current_vals:
if v in d.keys(): #if the dictionary exists, then append
add = [keys[i],current_vals]
d[v].append(add)
else:
add2 = [keys[i],current_vals]
empty = []
for a in add2:
if type(a) == int:
empty.append(a)
if type(a) == list:
for b in a:
empty.append(b)
d[v] = empty
keys = list(d.keys())
vals = list(d.values())
for i in range(0,len(keys)):
if keys[i] in vals[i]:
vals[i].remove(keys[i])
finaldict = {}
for j in range(0,len(keys)):
finaldict[keys[j]] = vals[j]
print("Final dict:\n",finaldict)
My attempt gives me the output
Final dict:
{1: [5, 6], 3: [4, [8, [2, 3]]], 8: [2, 3], 5: [1, 6], 6: [1, 5], 4: [3], 2: [8, 3]}
As you can see, finaldict[3] has the values [4, [8, [2, 3,]]].
The values themselves are wrong (3 should not be there), and also I want this to be a single list, and not on the format it is now. There are also other issues, such as finaldict[2] having the values [8, 3] when it should, in fact, have the values [3, 4, 8] and finaldict[4] only having [3], when it should have [2, 3, 8].
Not the most beautiful code I've ever written, but here it goes.
I create sets with all the numbers that are related to each other, then create a dictionary of each of those numbers as key, with the other ones as values.
d = {1: [5,6], 3: [4], 8: [2,3]}
pools = []
for key,values in d.items():
for pool in pools:
if key in pool or any(val in pool for val in values):
pool.add(key)
[pool.add(val) for val in values]
break
else:
pools.append(set((key, *values)))
#pools = [{1, 5, 6}, {8, 2, 3, 4}]
finaldict = {k:set.difference(v, set((k,))) for pool in pools for k, v in zip(pool, [pool]*len(pool))}
print(finaldict)
You can use recursion:
d = {1: [5,6], 3: [4], 8: [2,3]}
def groups(v, seen = [], c = []):
if (k:=[i for a, b in d.items() for i in ({a, *b} if v in {a, *b} else []) if i not in {*seen, v}]):
for i in k:
yield from groups(i, seen=seen+[v], c = c+([v] if seen else []))
else:
yield c+[v]
r = {i:[*{j for k in groups(i) for j in k}] for a, b in d.items() for i in [a, *b]}
Output:
{1: [5, 6], 5: [1, 6], 6: [1, 5], 3: [8, 2, 4], 4: [8, 2, 3], 8: [2, 3, 4], 2: [8, 3, 4]}
You can build up the resulting dictionary by merging related groups and assigning the same merged group to every key that is part of it:
d = {1: [5,6], 3: [4], 8: [2,3]}
related = dict()
for key,group in d.items(): # merge/assign groups to keys
merged = set(group).union({key},*(related.get(k,[]) for k in group))
related.update((k,merged) for k in merged)
related = {k:list(g-{k}) for k,g in related.items()} # exclude key from group
print(related)
{1: [5, 6], 5: [1, 6], 6: [1, 5], 3: [8, 2, 4], 4: [8, 2, 3],
2: [8, 3, 4], 8: [2, 3, 4]}

Removing items from list of dictionaries and appending them back

Suppose we have a list of dictionaries:
[{0: [0, 1, 2, 3], 1: [4]}, {2: [5, 6, 7, 8], 3: [9]}, {4: [10, 11, 12]}]
and we wish to split the dictionaries in this list according to some tuple for example (0, 5, 10) such that for each ith value in the tuple, if this value is present in any innermost sublist of the ith dictionary it gets split into its own list. In addition, the dictionaries are renumbered continuously from 0. Thus the output of the above would be.
[{0:[0], 1: [1, 2, 3], 2: [4]}, {3:[5], 4: [6, 7, 8], 5: [9]}, {6:[10], 7: [11, 12]}]
since 0 is a part of the first dictionary, it would get split. Since 5 is a part of the second dictionary, it would get split. Similarly, since 10 is the 3rd value in the tuple, and it is a part of the third dictionary it would get split.
I have written the following code:
for i in range(0, len(newlist)):
for key, value in newlist[i].items():
if x[i] in value:
value.remove(x[i])
newlist[i][key].append(x[i])
This produces [{0: [1, 2, 3, 0], 1: [4, 0]}, {2: [6, 7, 8, 5], 3: [9, 5]}, {4: [11, 12, 10]}] which is not the desired output, it appends to each list for the key.
How do I add just as a single list as in the desired output and how do I renumber the lists as desired using list comprehension or otherwise?
you can use a unified index idx to track the current key number in dict, and collect all the components splitted, and merge them into a new dict.
I have edit your example input to show more complex situation.
newlist = [{0: [0, 1, 2], 1: [3]}, {2: [4, 5, 6, 7, 8], 3: [9]}, {4: [10], 5:[11, 12]}]
x = [0, 5, 10]
for l in newlist:
components = []
for key, value in sorted(l.items()): # sort items because the storage of dict is unordered
for split_val in x:
if split_val in value: # split the list if in value
index = value.index(split_val)
components += [value[:index], [split_val], value[index + 1:]]
break
else:
components.append(value)
cur_dict = {}
for component in components:
if component: # only add non-empty component
cur_dict[idx] = component
idx += 1
result.append(cur_dict)
output:
[{0: [0], 1: [1, 2], 2: [3]}, {3: [4], 4: [5], 5: [6, 7, 8], 6: [9]}, {7: [10], 8: [11, 12]}]
I was able to obtain a combined dictionary using the following logic
li = [{0: [0, 1, 2, 3], 1: [4]}, {2: [5, 6, 7, 8], 3: [9]}, {4: [10, 11, 12]}]
values_list = []
#Iterate through values of all dictionaries and join them end to end
for dct in li:
values_list.extend(dct.values())
print(values_list)
#[[0, 1, 2, 3], [4], [5, 6, 7, 8], [9], [10, 11, 12]]
dct = {}
idx = 0
#Iterate through the list
for item in values_list:
#If we have more then one item in the list, idx goes to first item, idx+1 goes to rest, idx gets incremented by 2
if len(item) > 1:
dct[idx] = [item[0]]
dct[idx+1] = item[1:]
idx+=2
# If we have one item in the list, idx goes to first item, idx gets incremented by 1
else:
dct[idx] = [item[0]]
idx+=1
print(dct)
#{0: [0], 1: [1, 2, 3], 2: [4], 3: [5], 4: [6, 7, 8], 5: [9], 6: [10], 7: [11, 12]}

merge python dictionary keys based on common elements

Let's say we have a dictionary like this:
{0: [2, 8], 1: [8, 4], 3: [5]}
Then we encounter a key value pair 2 , 8 . Now, as the value 2 and 8 has already appeared for key 0, I need to merge the first two keys and create a new dictionary like the following:
{0: [2, 8, 4], 3: [5]}
I understand that it's possible to do a lot of looping and deleting. I'm really looking for a more pythonish way.
Thanks in advance.
your coworkers will hate you later but here
>>> d = {0: [2, 8], 1: [8, 4], 3: [5]}
>>> x = ((a,b) for a,b in itertools.combinations(d,2) if a in d and b in d and set(d[a]).intersection(d[b]))
>>> for a,b in x:d[min(a,b)].extend([i for i in d[max(a,b)] if i not in d[min(a,b)]]) or d.pop(max(a,b))
[8, 4]
>>> d
{0: [2, 8, 4], 3: [5]}
d = {0: [2, 8],
1: [8, 4],
3: [5]}
revmap = {}
for k,vals in d.items():
for v in vals:
revmap[k] = v
k,v = 2,8
d[revmap[k]].extend([i for i in d[revmap[v]] if i not in d[revmap[k]]])
d.pop(revmap[v])

Slice list to ordered chunks

I have dictionary like:
item_count_per_section = {1: 3, 2: 5, 3: 2, 4: 2}
And total count of items retrieved from this dictionary:
total_items = range(sum(item_count_per_section.values()))
Now I want to transform total_items by values of dictionary following way:
items_no_per_section = {1: [0,1,2], 2: [3,4,5,6,7], 3:[8,9], 4:[10,11] }
I.e. slice total_items sequencially to sublists which startrs from previous "iteration" index and finished with value from initial dictionary.
You don't need to find total_items at all. You can straightaway use itertools.count, itertools.islice and dictionary comprehension, like this
from itertools import count, islice
item_count_per_section, counter = {1: 3, 2: 5, 3: 2, 4: 2}, count()
print {k:list(islice(counter, v)) for k, v in item_count_per_section.items()}
Output
{1: [0, 1, 2], 2: [3, 4, 5, 6, 7], 3: [8, 9], 4: [10, 11]}
dict comprehension of itertools.isliced iter of total_items:
from itertools import islice
item_count_per_section = {1: 3, 2: 5, 3: 2, 4: 2}
total_items = range(sum(item_count_per_section.values()))
i = iter(total_items)
{key: list(islice(i, value)) for key, value in item_count_per_section.items()}
Outputs:
{1: [0, 1, 2], 2: [3, 4, 5, 6, 7], 3: [8, 9], 4: [10, 11]}
Note: this works for any total_items, not just range(sum(values)), assuming that was just your sample to keep the question generic. If you do just want the numbers, go with #thefourtheye's answer

Categories

Resources