This solution uses the chr
library, which is actually a somewhat different language than Prolog. I am learning CHR now myself and this is actually my second program or so written with it. It may be overly similar to that or the improved version I got on the mailing list. So take it with a grain of salt.
CHR seems to me to be a good fit for programs that start with a lot of possibilities and then cancel out bad possibilities. Perhaps the cancelling can continue until you converge on a solution. That's the way this program works, we generate all the possible name/surname sets and then use our impossibility constraints to reduce the possible surname sets until they converge to one, whereupon we remove them from the other name/surname sets until everything converges.
The first two lines introduce the library and the constraints we'll be using:
:- use_module(library(chr)).
:- chr_constraint name/1, surnames/1, impossible/2, matched/2, possible/2.
The first constraint generates the possibilities we need to walk over.
gen_possibilities @
name(Name), surnames(Surnames)
==> possible(Name, Surnames).
The idea here is that we have the surnames in a list already and we're just making a pair called possible
between each name and the totality of possible surnames.
impossibility @
impossible(Name, Surname), possible(Name, Surnames)
<=> select(Surname, Surnames, Remaining)
| possible(Name, Remaining).
Once we are told something is impossible, we can simply remove it from the list of possible surnames for that name. select/3
is useful for removing single items from a list and giving you the remainder. We use a guard here in case the surname is already not present in the possible list of surnames, as may happen after the fourth constraint is applied.
only_possiblity @
possible(Name, [Surname])
<=> matched(Name, Surname).
Once that list of possibilities dwindles to a single name, we have successfully made a match.
remove_matched @
matched(_, Surname), possible(Name, _)
==> impossible(Name, Surname).
If we have made a match, then that surname becomes impossible for all the other names.
An additional cleanup constraint may be added but is not required, if an impossibility constraint cannot be absorbed because the name was already taken care of:
irrelevant_impossibility @
possible(Name, Surnames)
\ impossible(Name, Surname)
<=> \+ memberchk(Surname, Surnames)
| true.
Now we can run the program. I have taken the liberty of inlining the logic for the 5th/6th grade and different fathers. I felt like the intent was easy enough to figure out by reading and I didn't think I'd gain much by modeling it specifically. Kudos to @RobertBaron for going to the trouble in his solution!
main :-
name(misha),
name(vova),
name(petya),
surnames([ivanov, semyonov, gerasimov]),
impossible(misha, gerasimov), % stated impossible
impossible(vova, gerasimov), % different grades
impossible(vova, ivanov). % different fathers
The result of executing this is:
?- main.
name(petya),
name(vova),
name(misha),
surnames([ivanov, semyonov, gerasimov]),
matched(petya, gerasimov),
matched(misha, ivanov),
matched(vova, semyonov).
I believe this fulfill the constraints set before you.