I think the problem is that you simply do not have any logic written to return the cycle.
Cycles that originate from a node
This is not that hard, you already use an accumulator, you simply need a mechanism that once the cycle is found, return your accumulator as cycle. You can do this by providing an additional parameter to cycle
:
cycle(Node,Cycle) :-
cycle(Node,[],Cycle).
cycle(Curr,Visited,Cycle) :-
member(Curr,Visited),
!,
reverse([Curr|Visited],Cycle).
cycle(Curr,Visited,Cycle) :-
edge(Curr,Next),
cycle(Next,[Curr|Visited],Cycle).
In this implementation I used reverse/2
since this will give you the correct order of the nodes (edge-wise). If you are not interested in this (for instance you only wish to analyze the cycle, and you can analyze this the opposite way, you can simply substitute Cycle
in the first clause of cycle/3
with [Curr|Visited]
.
The problem is however that if you call this predicate, it returns:
?- cycle(d,Cycle).
Cycle = [d, c, b, a, e, d] ;
Cycle = [d, c, b, a, e, c] ;
Cycle = [d, a, e, d] ;
Cycle = [d, a, e, c, b, a].
It thus not searches for cycles where d
is part of the cycle per se, it searches for all cycles that can originate from d
. This is probably not what you want.
Cycles with a given node
You can however rewrite the predicate: you will need to store the node from which you originate:
cycle(Node,Cycle) :-
edge(Node,Next),
cycle(Node,Next,[Node],Cycle).
So here we look for every edge/2
originating from Node
and we call cycle/4
with the initial Node
(to check when we have found a cycle), the Next
node, the list of visited [Node]
s and the Cycle
parameter to return found cycles.
Now there are three possible options for cycle/4
:
We arrive at the original node, in that case we have found a cycle where Node
is part from, we do processing similar to the first version:
cycle(Curr,Curr,Visited,Cycle) :-
!,
reverse([Curr|Visited],Cycle).
We arrive in a node we already visited: Curr
is an element of Visited
, in that case we need to break off search: otherwise we will cycle for an infinite amount of times:
cycle(_,Curr,Visited,_) :-
member(Curr,Visited),
!,
fail.
fail
is a predicate that states the branch fails. This can be useful, because now we specify how it can fail.
Finally we simply visited another edge and try to find the next node:
cycle(Node,Curr,Visited,Cycle) :-
edge(Curr,Next),
cycle(Node,Next,[Curr|Visited],Cycle).
The full version is:
cycle(Node,Cycle) :-
edge(Node,Next),
cycle(Node,Next,[Node],Cycle).
cycle(Curr,Curr,Visited,Cycle) :-
!,
reverse([Curr|Visited],Cycle).
cycle(_,Curr,Visited,_) :-
member(Curr,Visited),
!,
fail.
cycle(Node,Curr,Visited,Cycle) :-
edge(Curr,Next),
cycle(Node,Next,[Curr|Visited],Cycle).
Which generates:
?- cycle(d,Cycle).
Cycle = [d, c, b, a, e, d] ;
Cycle = [d, a, e, d] ;
All are thus cycles starting from d
and visiting d
again. We can make the code a bit more efficient by compressing the second and third scenario in one clause:
cycle(Node,Cycle) :-
edge(Node,Next),
cycle(Node,Next,[Node],Cycle).
cycle(Curr,Curr,Visited,Cycle) :-
!,
reverse([Curr|Visited],Cycle).
cycle(Node,Curr,Visited,Cycle) :-
\+ member(Curr,Visited),
edge(Curr,Next),
cycle(Node,Next,[Curr|Visited],Cycle).
Where \+
means not.