Search code examples
pythonnetworkxautomata

Efficiently check if path is valid in graph using NetworkX?


I would like to find an efficient way to check if a given "Walk" through a graph is valid.

Such a function should accept a Graph (nodes, edges) and a string of node names, and should output True if nodes can be visited in the given sequence according to the graph and false if not.

Preferably, I would use the NetworkX library, since I already use it to store and represent the graph.

Something resembling this:

""" G:
    Q1 -> Q1
    Q1 -> Q2
    Q2 -> Q2
"""
accepts(G, ["Q1", "Q1", "Q2"]) 
>> True
accepts(G, ["Q2", "Q2", "Q2"]) 
>> True
accepts(G, ["Q2", "Q2", "Q1"]) 
>> False
accepts(G, ["Q1", "Q2", "Q1"]) 
>> False
accepts(G, ["Q1", "Q1", "Q2", "Q1"]) 
>> False

This would be used for a automata class. To basically check for membership of a string given a language represented by the graph.


Solution

  • While @newbie's sentiment that "You only need to check if all the edges of the path are valid" is correct, the implementation he gives is far from efficient; graph.edges() rebuilds a list of all the graph's edges from scratch for every edge in the path. Instead of (u, v) in g.edges() you should always use g.has_edge(u, v), which is a constant-time operation. Although it seems you're working with DiGraph at the moment, the latter method also has the added benefit of working for both Graph and DiGraph, while the earlier method only works for DiGraph. (This is because a Graph's edges() method only returns the edges in one (random) direction, so checking for the edge in one direction only does not suffice; g.has_edge() does the right thing here for both Graph and DiGraph)

    Long story short, use:

    def accepts(g, path):
        return all([g.has_edge(path[i], path[i+1]) for i in range(len(path)-1)])
    

    Or, a bit more concisely:

    def accepts(g, path):
        return all(map(g.has_edge, path, path[1:]))