Search code examples
recursionprolog

Recurse for Kth cousin N times removed in Prolog


I'm trying to calculate a recursive relation in prolog, and honestly my head is absolutely about to explode from it.

I know the relationship between the cousin, Nth cousin and removed in words, but I'm having a very hard time getting it to work in this less-than-common language with the definitions I have; I have seen similar relations made with other posts, but they don't quite make use of the optimal relationships I've already made.

Below are the working relationships I have that are relevant to making this happen.

The Nth cousin relation would be, recurse up to a parent relationship N times. E.G. literally your parent, then recurse down N times from siblings at that level to find any cousins.

For Nth cousin Kth removed this relationship becomes more complicated to me and I lose the concept completely. As there are a few strange things preventing a clear cut relationship in my mind. Either way I'm having trouble proceeding from here.

strange things


is_child_of(child_person1, parent_person1).
is_child_of(child_person1, parent_person2).
is_child_of(child_person2, parent_person1).
is_child_of(child_person2, parent_person2).
is_child_of(cousin_person, cousin_parent1).
is_child_of(cousin_person, cousin_parent2).


is_child_of(child_person4, parent_person1).
is_child_of(child_person4, parent_person2).
is_child_of(child_person3, parent_person1).
is_child_of(child_person3, parent_person2).

is_parent_of(parent_person1, child_person2).
is_parent_of(parent_person1, child_person3).
is_parent_of(parent_person1, child_person4).
is_parent_of(parent_person1, child_person1).

is_parent_of(parent_person2, child_person2).
is_parent_of(parent_person2, child_person3).
is_parent_of(parent_person2, child_person4).
is_parent_of(parent_person2, child_person1).
is_parent_of(grandparent_1, parent_person2).
is_parent_of(grandparent_2, parent_person2).
is_parent_of(cousin_parent2, cousin_person).
is_parent_of(cousin_parent1, cousin_person).

is_sibling_of(cousin_parent1, parent_person1).
is_sibling_of(parent_person1, cousin_parent1).

is_sibling_of(X, Y) :- 
    is_parent_of(Z, X), 
    is_parent_of(Z, Y).
%
is_grandparent_of(Y,X) :-
    is_parent_of(Y, PRNT),
    is_parent_of(PRNT, X).

%if they have a parent, they are therefore that person's child
is_child_of(X, Y) :-
    is_parent_of(Y, X).

Below is some code I've been working on for the Nth cousin, but not removed, relationship. It does not work and I'm confused on how to get it working as well, but it's closer than not being close at all right?


Nth_cousin(N, M, PERSON) :- 
    N>0 -> 
    N is N-1,
is_parent_of(PARENT, PERSON),
    Nth_cousin(N, M, PARENT)
    ; M > 0 ->
    M is M -1,
    is_child_of(CHILD, PERSON),
    loop(N, M, CHILD)
    ; 

Solution

  • There is a lot to do with your example and since your names confuse me, I allowed myself to go with another family tree from the Simpsons:

    simpsons

    The factbase is just a part of the family tree:

    is_child_of(bart, homer).
    is_child_of(bart, marge).
    is_child_of(lisa, homer).
    is_child_of(lisa, marge).
    is_child_of(maggie, homer).
    is_child_of(maggie, marge).
    is_child_of(marge, jackie).
    is_child_of(patty, jackie).
    is_child_of(selma, jackie).
    is_child_of(ling, selma).
    

    Two notes here: the is_child_of is sufficient to describe most family trees. It is not necessary to add another fact like is_siblings_of(homer,herb) because it can be expressed through the is_child_of predicate and the rule for is_siblings_of. You should separate fact predicates and rule predicates, otherwise your code becomes messy fast because you need to include special cases.

    Lets start with the siblings:

    is_sibling_of(X, Y) :- 
        is_child_of(X, Z), 
        is_child_of(Y, Z).
    

    is a good start but with this rule I can be my own sibling, so add an unequal to both. Please note: the unequal operator \= may vary for different Prolog interpreters.

    is_sibling_of(S1,S2) :-
        is_child_of(S1, P), 
        is_child_of(S2, P),
        S1\=S2.
    

    Next is a generic anchestor predicate. Parents are anchestors grade 1, grandparents have grade 2 and so on. The idea is to program it through recursion: define it hard for grade 1, all other grades define themselves through a middleperson, in my case the parent of the child. So my anchestor grade 3 is the anchestor grade 2 for my mom, and the anchestor grade 1 for her mom.

    nth_anchestor_of(Child,Xparent,1) :- 
        is_child_of(Child, Xparent).
    nth_anchestor_of(Child,Xparent,N) :- 
        NN is N-1, NN>0, 
        is_child_of(Child, InBetween), 
        nth_anchestor_of(InBetween,Xparent,NN).
    

    Now it is just a little step to the cousin of grade N. This one defines as follows: cousins of grade N have anchestors grade N which are siblings:

    nth_cousin_of(Kid1,Kid2,N) :- 
        nth_anchestor_of(Kid1,Xparent1,N), 
        nth_anchestor_of(Kid2,Xparent2,N), 
        is_sibling_of(Xparent1,Xparent2).  
    

    In my culture a cousin is assumed to be grade 1:

    is_cousin_of(Kid1,Kid2) :- nth_cousin_of(Kid1,Kid2,1).
    

    Lets try it!

    ?- is_cousin_of(bart,ling).
        true
    
    ?- is_cousin_of(bart,X).
        X = ling;
        false
    
    ?- is_cousin_of(homer,X).
        false
    
    ?- is_cousin_of(ling,X).
        X = bart;
        X = lisa;
        X = maggie;
        false
    
    ?- is_cousin_of(X,Y). 
        X = bart,
        Y = ling;
        X = lisa,
        Y = ling;
        X = maggie,
        Y = ling;
        X = ling,
        Y = bart;
        X = ling,
        Y = lisa;
        X = ling,
        Y = maggie;
        false
    

    Like a charm!

    Please note that adding more family members may lead to multiple instances of the same cousin-pair. If you add Marge's father the last query will doublicate all of its answers. You can avoid that using cuts (symbol !) which you will learn in more advances lectures/tutorials.