Search code examples
prologzebra-puzzle

Prolog if else syntax


I can't seem to get my if else statement to work.

  1. John, Fred and Harry are men, Mary, Julie, Susan and Anne are women.
  2. John has blonde hair while Fred and Harry have dark hair.
  3. Julie and Susan are blonde, Mary and Anne are brunette.
  4. Rich is each person who owns the gold - Fred and Julie in our example.
  5. Male like only female and vice versa. Moreover, John and Harry like rich persons,John likes blonde and Fred likes brunette.
  6. Both Mary and Julie like dark hair persons, Julie likes rich persons at the same time.
male(john).
male(fred).
male(harry). 

female(mary).
female(julie).
female(susan).
female(anne).

hasblonde(X):-(male(X),X = john);(female(X),X = susan);(female(X),X = julie).

hasdarkhair(X):-(male(X),X = harry);(male(X),X = fred).

hasbrunette(X):-(female(X),X = mary);(female(X),X = anne).

isrich(X):-(female(julie),X=julie);(male(fred),X=fred).


likes(male(X),female(Y));likes(female(X),male(Y)):-likes(X,Y).    
likes(X,Y):-
 ((X==julie)->
    ((hasdarkhair(Y))->
        (female(X), male(Y));
        male(X));
    female(X),male(Y));
 ((X==julie)->
    ((isrich(Y))->
        (female(X), male(Y));
        male(X));
    female(X),male(Y));
 ((X=mary)->
    ((hasdarkhair(Y))->
        (female(X), male(Y));
        male(X));
    female(X),male(Y));
 ((X=john)->
    ((isrich(Y))->
        (female(X), male(Y));
        female(X));
    male(X),female(Y));
((X=harry)->
    ((isrich(Y))->
        (female(X), male(Y));
        female(X));
    male(X),female(Y));    
 ((X=fred)->
        ((hasbrunette(Y))->
            (female(X), male(Y));
            female(X));
    male(X),female(Y)).

i thought (Statement)->(if true run this statement);(if false run this statement). was the right approach to this in Prolog. Why is it that no matter what i write for

likes(MaleName,FemaleName) 
likes(FemaleName,MaleName)

it returns true?


Solution

  • Building upon the answer by CapelliC, because apparently his answer was not explicit enough. As for the if-else syntax and use, see towards the end of the answer.

    First off, what you have in your problem statement is information that you want to represent in the form of a Prolog program. In Prolog, you have predicates, which can describe relationships between their arguments, or state known truths about their arguments. Here for example is a table of facts; it states that there are seven people we know exist:

    person(john).
    person(fred).
    person(harry).
    person(mary).
    person(julie).
    person(susan).
    person(anne).
    

    Alright. We now want to state that some of them are male, and some are female.

    % John, Fred and Harry are men, Mary, Julie, Susan and Anne are women.
    male(john).
    male(fred).
    male(harry).
    
    female(mary).
    female(julie).
    female(susan).
    female(anne).
    

    These are two more tables of facts. Now you want to add to your database the information about their hair color:

    % John has blonde hair while Fred and Harry have dark hair.
    % Julie and Susan are blonde, Mary and Anne are brunette.
    person_hair(john, blond).
    person_hair(fred, dark).
    person_hair(harry, dark).
    person_hair(julie, blond).
    person_hair(susan, blond).
    person_hair(mary, dark).
    person_hair(anne, dark).
    

    This is a table with two columns if you will: the first is the person, the second one is a description of the hair color. The word "brunette" is usually used to describe a dark-haired woman, so we could add a rule that states that:

    % A brunette is a female with dark hair
    brunette(X) :-
        female(X),
        person_hair(X, dark).
    

    Some of the people we have own gold, and owning gold in our program makes a person rich:

    person_owns(fred, gold).
    person_owns(julie, gold).
    
    is_rich(X) :-
        %person(X),
        person_owns(X, gold).
    

    In our strictly heterosexual program, men like women and women like men:

    person_likes(M, F) :-
        male(M),
        female(F).
    person_likes(F, M) :-
        female(F),
        male(M).
    

    As you can calculate, this gives us 3 x 4 + 4 x 3 = 24 possible solutions for person_likes(A, B) without any further constraints:

    ?- bagof(A-B, person_likes(A, B), R), length(R, Len).
    R = [john-mary, john-julie, john-susan, john-anne, fred-mary, fred-julie, fred-susan, fred-anne, ... - ...|...],
    Len = 24.
    

    This is a very general rule: it describes a relationship between free variables, which makes it somewhat different from our person_owns/2 relationship, for example. Is it a really useful? Why not:

    is_heterosexual(H) :-
        person(H).
    

    But this only states that every person in our program is heterosexual; it doesn't let us derive the rule that a heterosexual is someone who likes the opposite sex. Maybe it is even better to re-name it, to better express its meaning (and I will use an if-then-else construct, to show how it is normally done):

    opposite_sex(X, Y) :-
        (   male(X)
        ->  female(Y)
        ;   female(X)
        ->  male(Y)
        ).
    

    For our purposes, this could have just as well written as above:

    opposite_sex(M, F) :-
        male(M), female(F).
    opposite_sex(F, M) :-
        male(M), female(F).
    

    With this, we can write a rule person_likes/2 with a general pre-condition stating that the other has to be of the opposite sex:

    person_likes(X, Y) :-
        opposite_sex(X, Y),
        fits_personal_taste(X, Y).
    

    We can now make rules for the personal tastes of each person. For Julie:

    fits_personal_taste(julie, X) :-
        is_rich(X),
        person_hair(X, dark).
    

    This creates a small problem, however. You need to make sure that for each person the program knows about there is a rule in this form. We don't know of any preferences for Anne, so we would have to have a rule:

    % Anyone (male) would fit Anne's tastes
    fits_personal_taste(anne, _).
    

    It would be nicer if we could instead have a table with an entry for each person that does have preferences, for example:

    person_preferences(julie, [is_rich, person_hair(dark)]).
    person_preferences(harry, [is_rich]).
    % and so on
    

    This would allow us to write fits_personal_taste/2 something like this:

    fits_personal_taste(X, Y) :-
        (   person_preferences(X, Ps)
        ->  maplist(fits_preference(Y), Ps)
        ;   true
        ).
    

    This is the intended use of an if-else construct in Prolog: an exclusive OR.

    If a person has preferences, check if the candidate fits all of them; otherwise succeed.

    How would fits_preference/2 look like though? It would take a person as the first argument, a term with the preference in the second, and would have to somehow check if that preference is met for that person. One somewhat hacky solution would be to use the so called Univ operator =.. to take the a term of the form person_hair(Color), make a term of the form person_hair(Person, Color), and call it:

    fits_preference(Person, Preference) :-
        Preference =.. [F|Args],
        Preference1 =.. [F,Person|Args],
        call(Preference1).
    

    It is maybe better that person_preferences directly maps a person to callable terms:

    person_preferences(julie, P, [is_rich(P), person_hair(P, dark)]).
    person_preferences(harry, P, [is_rich(P)]).
    % and so on
    

    With this, fits_personal_taste/2 becomes:

    fits_personal_taste(X, Y) :-
        (   person_preferences(X, Y, Ps)
        ->  maplist(call, Ps)
        ;   true
        ).
    

    When person_preferences/3 is called in the condition part of the statement, each preference in the list of preferences is bound to a concrete person; we then call each to check if it can be proven to be true for the facts in our program.

    Finally, a helper predicate possible_pair/2 that states that both people need to like each other:

    possible_pair(X, Y) :-
        person_likes(X, Y),
        person_likes(Y, X),
        X @< Y.
    

    The last line will make sure that we don't get the same pair twice by postulating that the two persons should be in strict order.

    With this, I get:

    ?- bagof(A-B, possible_pair(A, B), R).
    R = [fred-mary, anne-fred].
    

    Or, for a list of one-directional "likes",

    ?- bagof(A-B, person_likes(A, B), R), write(R).
    [john-julie,fred-mary,fred-anne,harry-julie,susan-john,anne-john,mary-fred,julie-fred,susan-fred,anne-fred,mary-harry,susan-harry,anne-harry]
    R = [john-julie, fred-mary, fred-anne, harry-julie, susan-john, anne-john, mary-fred, julie-fred, ... - ...|...].