问题
If I have the facts in following format:
person(name,age).
How can I write a query to find the youngest person?
I have tried using recursion but I kept getting stuck in infinite loops. So far from all the reading I done I found out I need to use the ! cut operator. Any help would be very much appreciated.
回答1:
You definitely do not need to use the cut operator. I'm hard-pressed to imagine what that solution would look like.
The simplest thing to do is to make a query of this sort:
youngest(person(Name,Age)) :-
person(Name, Age),
\+ (person(Name2,Age2), Name2 \= Name, Age2 < Age).
This is regrettably not very efficient, since it may have to search the database once for each person, leading to O(N^2) performance. But it should be clear why it works.
A faster solution is to use setof/3
.
youngest(person(Name, Age)) :-
setof(Age-Name, person(Name,Age), [Age-Name|_]).
We're relying on the fact that setof/3
is going to sort the list and that this will result in the youngest person being moved to the start of the result list for this to work. It performs better, but it doesn't read all that clearly.
There is a standard library you can use to solve these sorts of problems with SWI, but I'm not sure if you're using SWI and I haven't used it myself, but you might look into it. It's called aggregate.
Another approach would be to materialize the database directly with findall/3
and then find the minimum directly with a predicate written just to do that. Such a solution would probably look something like this:
youngest(Person) :-
findall(person(Name,Age), person(Name,Age), [P1|Rest]),
youngest(P1, Rest, Person).
youngest(Person, [], Person).
youngest(person(Name, Age), [person(N2,A2)|Rest], Person) :-
Age < A2 -> youngest(person(Name, Age), Rest, Person)
; youngest(person(N2, A2), Rest, Person).
However, this seems like a lot of work, even though it probably gives you the best performance (should be linear time).
回答2:
Just to add to the (very complete) answer by Daniel (+1): library(aggregate) can do such search - and more:
youngest(Person) :-
aggregate(min(Age,Pers), person(Pers,Age), min(_, Person)).
I think it's worth studying, because of the analogy of Prolog to databases, and the missing aggregation operators in this language.
来源:https://stackoverflow.com/questions/16349912/prolog-find-minimum-value-query