0

I searched around and couldn't find the answer. I'm having trouble making a genealogy list.

So, I have some is_a relations, for example:

is_a(cow, animal).
is_a(calf, cow).
is_a(dog, animal).
.... etc.

I want to have a procedure that does the following:

toAnimal(cow, X).

that outputs
X= [calf, cow, animal].

Basically, if I give it an input(cow), then it will go from cow until animal and add every step to the list.

So far, I have this:

toAnimal(A, B) :- is_a(A,B).
toAnimal(A, B) :- is_a(A, X), toAnimal(X, B).

The output of this is would be

X= cow;
X = animal;
false

How would I get this to become a list?

EDIT:

descend(X,Y) :- is_a(X,Y).
descend(X,Y) :- is_a(X,Z), descend(Z,Y).
toAnimal(X,Y):-
findall(X, descend('animal', X), Y).

I have updated it to this after looking at the suggestion. However, how do I get the list to print? I'm still new to prolog. The findall page said that it would return the list, but it is not doing so for me.

toAnimal(calf, Y)
outputs:
false.

EDIT:

It now returns an empty list. I'm not sure what the issue is here. I have not changed the code at all, so the output should not change but it has.

EDIT:

Thanks MrBratch for the response. I made the suggested changes, but I now have another issue. For example, if I have the relations:

is_a(calf, cow).
is_a(calf, animal).
is_a(cow, cool).
is_a(cool, awesome).

But I ONLY want the path from calf to awesome. The code will give me the ALL possible paths from calf,x. For example,

descend(X,Y) :- is_a(X,Y).
descend(X,Y) :- is_a(X,Z), descend(Z,Y).
toAwesome(A,Y) :-
    findall(X, descend(calf, X), Y).

will give me a list Y that has

[cow,animal,cool,awesome].

but what I want is

[calf,cow,cool,awesome].

How do I filter the other paths? and also add the starting point? I suppose I can append calf to the beginning as the head, but how do I ignore the other paths?

EDIT:

Thanks for the help I figured it out, but I lose the end path and start path. For example, L contains cow,cool. But calf and awesome are not there. I tried appending but I don't really understand the syntax. I'm not allowed to do append(X,L,anewlist)?

descend(X,Y) :- is_a(X,Y).
descend(X,Y) :- is_a(X,Z), descend(Z,Y).
toAnimal(A,B) :-
    setof(X, (descend(A,X), descend(X,'awesome')), B).
 -->   append(A, L,anewlist).
 ??    Is this line not allowed here? How else would I do it? or is there a simpler way to just add it from the beginning
Gordon
  • 3
  • 3
  • You can use `findall/3` or `setof/3`. See the prolog documentation on how those work. – lurker Jan 31 '14 at 18:59
  • You aren't using `B` as a list, instead it will unify with a ground term (i.e. `animal`) – C.B. Jan 31 '14 at 19:03
  • I made an edit to my post, I'm not sure how to get the list to print now. Thanks for the responses! – Gordon Jan 31 '14 at 19:14
  • The trick for that kind of functions you want is to add an extra parameter that's not really used by the original caller but is used by the functions as a temporary place to store such list, and in that extra param put each step into the head of a list: [X | Y] – Joe Pineda Jan 31 '14 at 19:44
  • That's not the correct way to use `findall/3`. Since you're using `findall/3`, you're not supposed to instantiate the first parameter, which is the whole point. Otherwise, you're just choosing a specific `X`. What `findall` does if find ALL values of `X` which meet a specific criteria. Like `findall(X, descendent(cow,X), L)` will create a list `L` consisting of all `X` that make `descendent(cow,X)` true. Or you can write `toAnimal(Y) :- findall(X, descendent(Y,X), L).` and then query `toAnimal(cow).` – lurker Jan 31 '14 at 20:00
  • Thanks for the responses, I've made changes again but I am running into another issue. I forgot to mention in the beginning that I want a specific path and not all possible paths. How do I filter this? – Gordon Jan 31 '14 at 20:15
  • *"But I ONLY want the path from calf to awesome."* What actually *characterizes* how you want to limit the response in the general case? Unless you provide that information, we'll only progressively spiral in on what you're after. Are you only looking for the longest/deepest connection? Or something else? – lurker Jan 31 '14 at 20:50
  • I figured out the path problem. The only issue now is to put the head and end of path into the list – Gordon Jan 31 '14 at 21:02

3 Answers3

1

Here it is. (NOTE: you don't need descend predicate to figure out the path of a particular branch of the tree)

is_a(calf, cow).
is_a(calf, animal).
is_a(cow, cool).
is_a(cool, awesome).
path(X,Y,[Z|T]) :- \+ is_a(X,Y), is_a(X,Z), path(Z,Y,T).
path(X,Y,[Y]) :- is_a(X,Y).
find_path(X,Y,[X|L]) :- path(X,Y,L).

Usage:

| ?- find_path(calf,awesome,L).

   L = [calf,cow,cool,awesome] ? ;
Ankur
  • 33,367
  • 2
  • 46
  • 72
  • Works flawlessly!! Please pardon me, what does "\+" do? If it's not asking for too much, could you please add a short description of how your solution works? – Joe Pineda Feb 01 '14 at 16:36
  • 1
    @JoePineda `\+` is "can not be proved". – Will Ness Feb 01 '14 at 17:25
  • Oh! Thanks! But in that case, wouldn't it be the same if you just moved the 2nd definition of "path" above the first one? Should it fail, then prolog would attempt the longer definition, in which case that check of "\+ is_a(X,Y) " would be redundant. – Joe Pineda Feb 01 '14 at 17:32
  • I mean something like this, tried it and at least for this mini-example works OK: path(X,Y,[Y]) :- is_a(X,Y). path(X,Y,[Z|T]) :- is_a(X,Z), path(Z,Y,T). – Joe Pineda Feb 01 '14 at 17:32
  • 1
    it's good to have your clauses mutually exclusive, no matter what's their order. to have a predicate's meaning be dependent on its clauses order is generally considered to be not a good practice for a novice. Having said that, do see http://www.swi-prolog.org/pldoc/man?predicate=send_arrow/2. – Will Ness Feb 01 '14 at 17:40
0

toAnimal(X,Y) :- setof(X, descend('animal', X), Y). should do it. Or findall/3.

Info and some examples of bagof, setof, findall.

But remember that you are asking for descend(animal, X) so it won't match the fact is_a(dog, animal) for example, which descend(X, animal) will. You need to make descend to search both sides, or simply be sure that your is_a facts say animal just on left side.

If you want to filter you could do

toAnimal(X,Y) :- setof(X, (descend('animal', X), not(X = animal)), Y).

but you are getting animal as a result because what I mentioned before.

Javier
  • 801
  • 3
  • 10
  • 24
  • Sorry I forgot to specify earlier that I want a specific path to a designated point. This does work for all paths though. I made an edit in my post about it. Thanks for the response though I appreciate the help. – Gordon Jan 31 '14 at 20:24
  • Look for how to cut backtracking with `!`. – Javier Jan 31 '14 at 20:37
0

This sample more or less does what you want:

is_a(cow, animal).
is_a(calf, cow).
is_a(dog, animal).
is_a(snoopy, dog).
is_a(lassie, collie).
is_a(collie, dog).

toAnimal3( X, [X,animal] , animal ):- is_a( X, animal).
toAnimal3( X, [X|R], R ):- is_a( X, Y), toAnimal3(Y, R, _).

:- initialization(main).
main :- toAnimal3( lassie, A, B), write(A), write(B).

When run, this is the output:

[lassie,collie,dog,animal][collie,dog,animal]

Tested it online using this Prolog online interpreter

POST EDIT: Ah, that was it! I should've written "[X,animal]" instead of "[X|animal]" for the first clause! Thanks galore to @mbratch , now the program does exactly what was intended.

Joe Pineda
  • 5,521
  • 3
  • 31
  • 40
  • 1
    `[X|animal]` is a list consisting of one element, the compound term `|(X,animal)`. It doesn't have much semantic meaning in this context If you want to have a list of two elements `X` and `animal`, you'd just write it `[X,animal]`. – lurker Feb 01 '14 at 02:28