-1
loop1(a).
loop1(b).
loop1(c).
loop1(d).

circular(d, a).
circular(b, c).
circular(b, d).
circular(a, b).

so when I call:

in_cycle(a, Cycle). 

it will return:

[a,b,d,a]

if I call:

in_cycle(b, Cycle).

it will return:

[b,d,a,b].

however, if you call:

in_cycle(c, Cycle).

it will return:

false. (because no loop is included).

here is my try:

in_cycle(C,Cycle) :- circular(C,Cycle1),in_cycle(Cycle1,Cycle).

I know this predicate has very serious problem : it won't stop...I really want to know what kind of base case I should add so this predicate will stop ? Should i add a condition so prolog will stop when it find the same alphbet ?

It would be grateful if someone could help me!

-------updated-------

check([Y,_|Z]) :- check([Y|Z]).

in_cycle(C, [C]).
in_cycle(C, [C, C1|Cycle]) :-  circular(C, C1),check([C,C1|Cycle]),
                               in_cycle(C1, [C1|Cycle]).
MeiLin Xu
  • 33
  • 4

2 Answers2

1
  1. What is the shortest cycle you could have in your fact database? Would circular(a, a). be cycle [a]? Knowing what the shortest cycle is might help you find (one of) the finishing condition(s) for your predicate.
  2. To be able to find a list, your predicate needs to build it. Your in_cycle/2 never mentions any lists. You need to use the [Head | Tail] construct somewhere in there to be able to add elements to a list.
  3. You already know something about what your cyclical list looks like. The first and last element are the same, and they are the same as the symbol you're trying to find the cycle for. Use that information.
  4. To be able to tell when you completed a cycle, you need to remember which symbol you started with. To be able to do that with recursion, you need to keep state. So you're going to need an additional predicate where you keep that state. E.g. something involving a predicate like in_cycle(Current_symbol, Cycle, Start_symbol). You can then call that from your in_cycle/2.

Let's have a look at your try:

in_cycle(C, Cycle) :-
    circular(C, Cycle1),
    in_cycle(Cycle1, Cycle).

You can use the trace command at the prompt to see what's happening: trace, in_cycle(a, X).

Press Space to step through your program. Press h for help, and a to exit. Use notrace. to get out of trace mode again.

As you step through this, you'll find that your predicate is nicely looping through the cycle, but at no point does the X ever become a list. That's bad.

Let's try and make this build a list. As I mentioned in point (3), you already know something about the list. The first element of the list is the same as the first argument to in_cycle. Even more, the second element of the list is the same as the element you'll find with circular/2. So we know a cycle has at least two elements. How about this?

in_cycle(First, [First, Next|Cycle]) :-
    circular(First, Next),
    in_cycle(Next, [Next|Cycle]).

If you trace this now, you'll see something is happening with X, but still not actually anything useful. Cycle remains a mystery and we're just looping through the facts forever. You need some end condition here. Let's try a simple one:

in_cycle(First, [First]).
in_cycle(First, [First, Next|Cycle]) :-
    circular(First, Next),
    in_cycle(Next, [Next|Cycle]).

Whoa! in_cycle(a, X) suddenly gives results! All possible lists using circular connections starting with a, it seems. That's not exactly what we want, but maybe we're getting closer?

One problem with this is that in_cycle(Next, [Next|Cycle]) is not actually correct!

If you do in_cycle(a, X), you already know that X should become [a, b, d, a], so filling those values into in_cycle(First, [First, Next|Cycle]), you get:

First = a
Next = b
Cycle = [d, a]

When you get to in_cycle(Next, [Next|Cycle]), that means it's in_cycle(b, [b, d, a]). But [b, d, a] is not a cycle! You need to be able to distinguish these two situations somehow. One way of doing that is to call a separate predicate like I mentioned in (4) to keep track of what your starting element was.

mercator
  • 28,290
  • 8
  • 63
  • 72
  • mercator: thanks for your answer, but I still confused: so for No.1, there is no exception, only the test cases I mentioned above are correct. for No.2, can i find all results then combine them together using findall? for NO.3 ,I know that is an important point, but what does it look like? for NO4, what does in_cycle/2 mean? – MeiLin Xu Mar 15 '19 at 08:56
  • I've clarified (1) in my answer. With `in_cycle/2` I mean your `in_cycle` predicate with 2 arguments. (2) I'm not sure. I don't think that would be the best way if it's possible? (3) I'll try and add an example later. (4) I mean your `in_cycle` predicate with 2 arguments, like how SWI-Prolog's documentation says [`findall/3`](http://www.swi-prolog.org/pldoc/doc_for?object=findall/3) – mercator Mar 15 '19 at 12:02
  • there wont have case like circular(a,a). – MeiLin Xu Mar 15 '19 at 20:57
  • I used trace, then I realized this loop won't stop unless I add a statement like current symbol equal to start symbol, then this predicate stops. but the problem is how to write it...there is no way to save the start symbol. – MeiLin Xu Mar 16 '19 at 07:19
  • then I tried to use a new predicate to check the list's first and last element are equal or not...still not working – MeiLin Xu Mar 16 '19 at 08:04
  • What makes you think that `check` checks that the first and last element in the list are the same? Trace e.g. `check([a, b, c, d])` and see what happens. Also have a look at https://stackoverflow.com/questions/28154749/prolog-and-list-unification – mercator Mar 16 '19 at 09:50
  • Ok, it didn't check anything, and even bring error. I still feel confsued for how to fix this predicate. – MeiLin Xu Mar 19 '19 at 01:33
1

A node is in a cycle, if you can find a path back to that very node. Using path/4:

in_cycle(C, [C|Cs]) :-
   circular(C, A),
   path(circular, Cs, A,C).

Now, does this predicate terminate? How can we test this in a systematic manner? How can we ensure that we do not forget any special case? For pure, monotonic programs as this one, testing for termination is trivial1: Simply take the most general query! That is:

?- in_cycle(C, Cs).
   C = d, Cs = "dabd"     % [d,a,b,d]
;  C = b, Cs = "bdab"
;  C = a, Cs = "abda"
;  false.                % it terminates!

(See this answer how to get "bdab" in place of [b,d,a,b]).

What is so nice in Prolog is that above query constitutes a proof of termination. Each query you can pose is included in above query. And since the more general query already terminates, any more specific query will terminate too! Any!

And all of this holds even for any variable free facts for circular/2. However, this proof cannot be carried out so easily as the proof for a specific set of facts.


1 Note that trivial means belonging to the trivium.

false
  • 10,264
  • 13
  • 101
  • 209
  • is there any other ways to do this problem? their predicate are so compliacted... – MeiLin Xu Mar 15 '19 at 21:04
  • 1
    You need detection of cycles, that's what `path/4` is for. – false Mar 15 '19 at 22:17
  • Okay, but there is also a problem, when you call in_cycle(a,X). it will return the correct resul,t but it won't stop (not return true or false) – MeiLin Xu Mar 16 '19 at 07:49
  • Above definition terminates with any ground (variable free) facts for `circular/2`. How do you come to the conclusion that it does not stop? – false Mar 16 '19 at 13:49
  • @Mei: That depends on your precise [tag:prolog-toplevel]. Some do not ask for `;` if no answer substitutions are shown – false Mar 19 '19 at 06:46