问题
Suppose you have a database with the following content:
son(a, d).
son(b, d).
son(a, c).
son(b, c).
So a and b are sons of d and c. Now you want to know, given a bigger database, who is brother to who. A solution would be:
brother(X, Y) :-
son(X, P),
son(Y, P),
X \= Y.
The problem with this is that if you ask "brother(X, Y)." and start pressing ";" you'll get redundant results like:
- X = a, Y = b;
- X = b, Y = a;
- X = a, Y = b;
- X = b, Y = a;
I can understand why I get these results but I am looking for a way to fix this. What can I do?
回答1:
Prolog will always try to find every possible solution available for your statements considering your set of truths. The expansion works as depth-first search:
son(a, d).
son(b, d).
son(a, c).
son(b, c).
brother(X, Y) :-
son(X, P),
son(Y, P),
X \= Y.
brother(X, Y)
_______________________|____________________________ [son(X, P)]
| | | |
X = a, P = d X = b, P = d X = a, P = c X = a, P = b
| | | |
| ... ... ...
|
| (X and P are already defined for this branch;
| the algorithm now looks for Y's)
|__________________________________________ [son(Y, d)]
| |
son(a, d) -> Y = a son(b, d) -> Y = b
| |
| | [X \= Y]
X = a, Y = a -> false X = a, Y = b -> true
|
|
solution(X = a, Y = b, P = d)
But, as you can see, the expansion will be performed in all the branches, so you'll end up with more of the same solution as the final answer. As pointed by @Daniel Lyons, you may use the setof
built-in.
You may also use the !
-- cut operator -- that stops the "horizontal" expansion, once a branch has been found to be valid, or add some statement that avoids the multiple solutions.
For further information, take a look at the Unification algorithm.
回答2:
You can eliminate one set with a comparison:
brother(X, Y) :-
son(X, P),
son(Y, P),
X \= Y, X @< Y.
?- brother(X, Y).
X = a,
Y = b ;
X = a,
Y = b ;
false.
Since X and Y will be instantiated both ways, requiring X be less than Y is a good way to cut the solutions in half.
Your second problem is that X and Y are brothers by more than one parent. The easiest solution here would be to make your rules more explicit:
mother(a, d).
mother(b, d).
father(a, c).
father(b, c).
brother(X, Y) :-
mother(X, M), mother(Y, M),
father(X, F), father(Y, F),
X \= Y, X @< Y.
?- brother(X, Y).
X = a,
Y = b ;
false.
This method is very specific to this particular problem, but the underlying reasoning is not: you had two copies because a
and b
are "brothers" by c
and also by d
—Prolog was right to produce that solution twice because there was a hidden variable being instantiated to two different values.
A more elegant solution would probably be to use setof/3
to get the solutions. This can work even with your original code:
?- setof(X-Y, (brother(X, Y), X @< Y), Brothers).
Brothers = [a-b].
The downside to this approach is that you wind up with a list rather than Prolog generating different solutions, though you can recover that behavior with member/2
.
回答3:
First, I would advise against updating the Prolog database dynamically. For some reasons, consider the article "How to deal with the Prolog dynamic database?".
You could use a combination of the builtin setof/3
and member/2
, as @DanielLyons has suggested in his answer.
As yet another alternative, consider the following query which uses setof/3
in a rather unusual way, like this:
?- setof(t,brother(X,Y),_).
X = a, Y = b ;
X = b, Y = a.
回答4:
This should work. But I think it can be improved (I am not a Prolog specialist):
brother(X, Y) :-
son(X, P1),
son(Y, P1),
X @< Y,
(son(X, P2), son(Y, P2), P1 @< P2 -> false; true).
回答5:
If you're using Strawberry Prolog compiler,you won't get all the answers by typing this:
?- brother(X, Y),
write(X), nl,
write(Y), nl.
In order to get all the answers write this:
?- brother(X, Y),
write(X), nl,
write(Y), nl,
fail.
I hope it helps you.:)
回答6:
I got to an answer.
% Include the dictionary
:- [p1]. % The dictionary with sons
:- dynamic(found/2).
brother(X, Y) :-
% Get two persons from the database to test
son(X, P),
son(Y, P),
% Test if the two persons are different and were not already used
testBrother(X, Y).
% If it got here it's because there is no one else to test above, so just fail and retract all
brother(_, _) :-
retract(found(_, _)),
fail.
testBrother(X, Y) :-
X \= Y,
\+found(X, Y),
\+found(Y, X),
% If they were not used succed and assert what was found
assert(found(X, Y)).
It always returns fails in the end but it succeeds with the following.
- brother(X, Y). % Every brother without repetition
- brother('Urraca', X). % Every brother of Urraca without repetition
- brother('Urraca', 'Sancho I'). % True, because Urraca and Sancho I have the same father and mother. In fact, even if they only had the same mother or the same father it would return true. A little off context but still valid, if they have three or more common parents it would still work
It fails with the following:
- brother(X, X). % False because it's the same person
- brother('Nope', X). % False because not is not even in the database
- brother('Nope', 'Sancho I'). % False, same reason
So like this I can, for example, ask: brother(X, Y), and start pressing ";" to see every brother and sister without any repetition.
I can also do brother(a, b) and brother(b, a), assuming a and b are persons in the database. This is important because some solutions would use @< to test things and like so brother(b, a) would fail.
So there it is.
来源:https://stackoverflow.com/questions/15042152/dont-repeat-solutions-in-prolog