Search code examples
prologtransitive-closure

swi-ProLog Finding all possible starting nodes which can arrive specific ending node


I am new to logic programming. I am trying to write a program which can find all nodes which can reach to an specific node.

Here is my code:

link(0, 1).  
link(3, 4). 
link(1, 2).  
link(2, 0). 
link(2, 1).  
link(3, 2).  
link(4, 3).

So show above by graph, it should look like below:

Graph

I want to do something like this:

?- go(X,0).
X=1;
X=2;
X=3;
X=4;
....

Which mean that from all 1,2,3 and 4 can go to 0.

[1->2->0],[2->0],[3->2->0],[4->3->2->0]...

so I try

go(X,Y) :- link(X,Y).
go(X,Y) :- link(X,Z),go(Z,Y).

?- go(X,0).
X=2;
X=0;
X=0;
X=0;
X=0;
....

but i don't want 0 as one of the output, it is meaningless to show I can go to 0 when I am already in 0. Also, it keeps repeating. I try to fix this problem by:

go(X,Y) :- link(X,Y).
go(X,Y) :- (X\==Y),link(X,Z),go(Z,Y).

?- go(X,0).
X = 2 ;
false.

I'm not sure why it will stop at X=2. I try to draw the ProLog search tree and i still don't know why my code wont continue go for other facts(link(3,4)). Seem it stop at some point and no keep going for the green part below:

Search Tree

I try to test it using go(1,0). to go(4,0). go(1,0) and go(2,0) success But go(3,0) and go(4,0) return Error: Stack limit. I found that the recursion keep going and going between node 3 and 4. But I don't really know how to solve it.

I am so confused now, please tell me what is my mistake, or how to implement this function in a correct way? Thank you.


Solution

  • The problem is that your graph is a cyclic, rather than acyclic graph. That means that starting from some node X, you [eventually] will return to X.

    That means that (for starters) unless you handle the cycles somehow, you will be stuck in an endless recursive loop (until you run out of stack space).

    As you traverse the graph, you need to maintain some extra state (a list of nodes that you have already visited). To do this in Prolog, it is a common idiom to use a helper predicate, with the same name but additional arguments that carry the extra state.

    So, try something like this:

    % the public predicate. We seed the visited list here
    % with our starting node.
    %
    % Not to worry if it is an unbound
    % variable. It will become bound/unbound as necessary.
    traverse(A,B) :- traverse(A,B,[A]).
    
    traverse(A,B,V) :-    % We can get from A to B, if...
      link(A,B),          % - A and B are directly connected, and
      \+ member(B,V)      % - we haven't already visited B
      .                   % - easy!
    traverse(A,B,V) :-    % Otherwise, we can get from A to B, if...
      link(A,X),          % - we can get to some intermediate node X,
      \+ member(X,V)      % - that we have not yet visited, and
      traverse(X,B,[X|V]) % - we can get from X to B (adding X to the visited list
      .