Reputation: 47
How can I know if a person X is descendant of a person Y given the descendancy degree?
I've tried this:
descendant(X, Y, 1) :- son(X, Y).
descendant(X, Y, Degree) :- son(X, Z) , descendant(Z, Y, Degree-1).
Where son(X, Y)
returns yes
if X is son of Y. If Degree == 1
it returns the correct answer but for descendant(X, Y, 2)
, for instance, should return yes
if X is grandson of Y but returns no
.
Upvotes: 1
Views: 74
Reputation: 18726
1) Naming: Does son(X,Y)
mean "X
is the son of Y
"—or vice-versa?
son_of(X,Y)
is better.
2) Exploit successor-arithmetics: We don't need to do general arithmetics here... we only need to count.
So let's start in the beginning...
child_of(abel, adam). % from source child_of(abel, eve). child_of(cain, adam). child_of(cain, eve). child_of(enoch, cain). child_of(irad, enoch). child_of(mehujael, irad). child_of(methushael, mehujael). child_of(lamech, methushael). child_of(jabal, lamech). child_of(jabal, adah). child_of(jubal, lamech). child_of(jubal, adah). child_of(tubal_cain, lamech). child_of(tubal_cain, zillah). child_of(naamah, lamech). child_of(naamah, zillah). child_of(seth, adam). child_of(seth, eve). child_of(enos, seth). child_of(kenan, enos). child_of(mahalalel, kenan). child_of(jared, mahalalel). child_of(enoch, jared). child_of(methuselah, enoch). child_of(lamech, methuselah). child_of(noah, lamech). child_of(shem, noah). child_of(ham, noah). child_of(japheth, noah).
Based on child_of/2
we first define ancestor_of/2
—this should be nothing new to you!
ancestor_of(Y, Z) :- child_of(Z, Y). % If Z is a child of Y ... % then Y is an ancestor of Z. ancestor_of(X, Z) :- child_of(Z, Y), % If Z is a child of Y ... ancestor_of(X, Y). % and X is an ancestor of Y ... % then X is an ancestor of Z.
Next, we add an additional parameter indicating the distance.
We use s/1
terms to represent natural numbers and add a new argument to ancestor_of/2
:
ancestor_of_dist(Y, Z, s(0)) :- child_of(Z, Y). % If Z is a child of Y ... % then Y is an ancestor of Z with distance = 1." ancestor_of_dist(X, Z, s(N)) :- child_of(Z, Y), % If Z is a child of Y ... ancestor_of_dist(X, Y, N). % and X is an ancestor of Y with distance N ... % then X is an ancestor of Z with distance N+1.
So ... who is grandparent of whom?
?- ancestor_of_dist(X, Z, s(s(0))). X = adam, Z = enoch ; X = eve, Z = enoch ; X = cain, Z = irad ; X = jared, Z = irad ; X = enoch, Z = mehujael ; ... ; X = lamech, Z = japheth ; false.
Upvotes: 5
Reputation: 18683
Prolog is not a functional language. Thus, the Degree-1
term is not interpreted and evaluated as an expression. For Prolog, Degree-1
is just a compound term with two arguments. That can be made clear using the standard write_canonical/1
predicate, which writes a term without using operator notation:
?- write_canonical(Degree-1).
-(_,1)
true.
Write instead:
descendant(X, Y, 1) :-
son(X, Y).
descendant(X, Y, Degree) :-
son(X, Z) ,
descendant(Z, Y, Degree0),
Degree is Degree0 + 1.
The is/2
standard built-in predicate unifies the left argument with the value of the arithmetic expression in the right argument.
P.S. Note that the alternative descendant/3
predicate definition I suggest will solve the problem you described but it is not an efficient definition as it is not a tail-recursive definition. I.e. the recursive call in the second clause is not the last call in the clause body. This issue can be easily solved, however, by using an accumulator.
Upvotes: 3