简体   繁体   中英

SWI-prolog : how to make a circular list by using some facts

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]).
  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] [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. Eg 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.

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 trivial 1 : 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM