Find out if two dictionaries result in a loop

Viewed 83

I have two dicts, where the dict inside the dict stands for the values in the other dict:

dict1 = {1:{1,3}, 2:{2}}
dict2 = {1:{5}, 3:{1}}

I am trying to find out, if the combination would result in a loop, for example:

dict1: 1 -> dict2: 3 -> dict1: 1; This would result in a loop, so my script should throw an error.

dict1: 2 -> not in dict2; Not a loop would be okay

dict1: 1 -> dict2: 1 -> 5 not in dict 1; Not a loop would be okay

Any ideas how I could solve that problem? Thanks in advance!

4 Answers

If you just want to know if there is a loop between the two dictionaries you can use this code:

for key, values in dict1.items():
    if any([v in dict2 and key in dict2[v] for v in values]):
        print('loop found')

Otherwise, if you want to know where the specific loop is, you need to break out the iteration over values in the above code:

for key, values in dict1.items():
    for v in values:
        if v in dict2 and key in dict2[v]:
            print('loop found from ' + str(key) + ' through value ' + str(v) + ' back to ' + str(key))

For your sample data the output is either loop found for the first code snippet or loop found from 1 through value 3 back to 1 for the second.

Effectively, your question is about finding a cycle (of any length, if I understand correctly) in a bipartite graph.

With some minor pre-processing, you can use the networkx package to find if there is any cycle in your graph. networkx deals with bipartite graphs, but let's just rename out nodes and not need to use the bipartite trait directly.

To pre-process the dictionaries, we add a prefix 'a' to the nodes in the first dict and a prefix 'b' to the nodes in the second (and not forget to add the prefixes to the edge targets):

dict1 = {'a'+str(k): set(map(lambda x: 'b'+str(x), v)) for k, v in dict1.items()}
dict2 = {'b'+str(k): set(map(lambda x: 'a'+str(x), v)) for k, v in dict2.items()}

Now our dictionaries look like this:

{'a1': {'b3', 'b1'}, 'a2': {'b2'}, 'b1': {'a5'}, 'b3': {'a1'}}
{'b1': {'a5'}, 'b3': {'a1'}}

We merge them together to create a single dictionary:

dict3 = dict1
dict3.update(dict2)

And turn that dictionary into a (directed!) graph:

import networkx as nx
g = nx.DiGraph(dict3)

If you're so inclined, you can draw the network (with nx.draw_networkx(g)) to see what came out: networkx graph

Well, we can see the cycle between 'a1' and 'b3'. To find them automatically - and this is the point of this answer - we can just use the following command:

nx.simple_cycles(g)

which in this case returns a generator object which yields the cycles (in this case, (list(nx.simple_cycles(g)) is [['b3', 'a1']].

But you don't want the whole list; you just want your code to throw an exception if the list is not empty. So, you can use:

assert(not(next(nx.simple_cycles(g), None))

And you're done!

If I understood the problem right, you could do something like:

for idx_set in dict1.values():
    in_dict2 = (idx for idx in idx_set if idx in dict2)
    for idx in in_dict2:
        for idx2 in dict2[idx]:
            if idx2 in dict1:
                print('detected a loop for %s' % idx_set)


This looks pretty verbose but it's relatively easy to understand what's going on.

This should do it, Put an Error raise where I have 1/0 //SW

dict1 = {1:{1,3}, 2:{2}}

dict2 = {1:{5}, 3:{1}}

start_key = 1

a = list(dict1[start_key])

b = list(dict2)

if a == b:
    
    1/0
Related