Reputation: 20675
When I run a query, it can return zero, one or more than one results. How can I "save" these results so that I can compare them later, or how can I compare them on the fly?
For example, I have these facts:
father(john, mark).
father(john, michael).
age(michael, 10).
age(mark, 12).
Now if I run a query like this
?- father(john, X).
it will return mark
and michael
, but how can I compare the age of mark
and michael
? In fact, how can I even know that the query will return more than one results?
The idea is that I want to get the eldest son of a father
Upvotes: 1
Views: 1035
Reputation: 60004
just a footnote on Daniel answer:
eldest(Father, Child) :-
findall(A/C, (father(Father, C), age(C, T), A is -T), L),
sort(L, [_/Child|_]).
I used the trick of negating the age to get the list in reverse order.
Here a 'one liner', does just the same as above:
eldest(Father, Child) :-
setof(A/C, T^(father(Father, C), age(C, T), A is -T), [_/Child|_]).
edit if your Prolog has library(aggregate), there is a cleaner way to get the result:
eldest(Father, Child) :-
aggregate(max(A, C), (father(Father, C), age(C, A)), max(_, Child)).
Upvotes: 1
Reputation: 22803
What you're running into here is the fact that on backtracking, Prolog releases the variable bindings it found when it produced the previous result. This feature causes a lot of consternation in the early days, so know that you're not alone, we were all there once.
The first thing you should do is try to let go of the need to tell Prolog how to get the result, and instead focus on telling Prolog how to distinguish the result you want logically. This is logic programming, after all. :) When you say "how can I compare the age of mark
and michael
?" you're hiding the real question behind the assumption that once you know how to hold onto things you can find the answer yourself. But you don't want to find the answer yourself, you want Prolog to find it!
Let's take an example. Say you want to find out who is the youngest child of whom. You can do this logically:
?- father(Father, Child),
age(Child, YoungestAge),
\+ (father(Father, Child2),
age(Child2, Younger),
Younger < YoungestAge).
Father = john,
Child = michael,
YoungestAge = 10.
This would be inefficient with a large database, unfortunately, since Prolog will have to search every age/2
fact to find all the children of a particular parent. Presumably Prolog will index these predicates and that may be enough to save us depending on how you use it, but it doesn't look like a strategy you can apply universally. But you can't beat the logical reading of this query: supposing I have a father Father of child Child, and supposing that Child's age is YoungestAge, there is no Child2 of this same Father whose age is Younger than YoungestAge.
Frequently, you do need all the solutions, and for that there are three predicates: setof/3
, bagof/3
and findall/3
. They all have basically the same API with slightly different semantics. For instance, if you want all the children for a parent, you can use setof
to get them:
?- setof(Child, father(john, Child), Children).
Children = [mark, michael].
You'll need a bigger fact database to see the effect of the differences between the two, but that's a different question. In short, setof/3
will get you a sorted list of unique answers whereas bagof/3
will get you an unsorted list with all the answers. findall/3
does the same thing, with a difference in the way it treats variables. In my experience, setof/3
and findall/3
tend to be used a lot more than bagof/3
.
If the work you're doing necessitates it, or if efficiency demands it, you can use these meta-predicates to find all the possible solutions and walk through the list doing whatever processing you need to do.
As for the question "how can I even know that the query will return more than one result?" the answer is basically you can generate them all and see how many you had (findall(..., Answers), length(Answers, Count)
) or you can use once/1
to ensure that you get a single solution. once
is great for making non-deterministic predicates deterministic; the effect is basically the same as putting a cut right after the clause. For instance:
?- father(john, X).
X = mark ;
X = michael.
?- once(father(john, X)).
X = mark.
?- father(john, X), !.
X = mark.
In general it is recommended that you use once/1
over explicit cuts, if possible.
Let me know if this helps.
Upvotes: 2