Search code examples
prologprolog-setof

Don't repeat solutions in Prolog


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?


Solution

  • 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.