4

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?

false
  • 10,264
  • 13
  • 101
  • 209
SuperCell
  • 241
  • 1
  • 12

5 Answers5

5

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, ... - ...|...].
  • 1
    This is great. My program already works flawlessly, but this organization is too good to pass over. I'm gonna redesign it once more. Thanks for the clarity. – SuperCell Jul 21 '15 at 09:36
  • 3
    I don't have 15 points of reputation to be able to vote, but when I do i'll make my way back here. – SuperCell Jul 21 '15 at 09:44
  • What about safety checks that the arguments are "sufficiently instantiated"? – false Jul 22 '15 at 11:10
  • @false For example? As I meant it, this should be a program that has two predicates as an interface: `person_likes/2` and `possible_pair/2`, and a database with predicates like `person_hair/2` or `person_preferences/3`. The rest are meant to be "private". –  Jul 22 '15 at 11:14
  • As for `possible_pair/2`. – false Jul 22 '15 at 11:16
  • @false I am not sure. It will either succeed with solution(s) or fail, even with the most general query. What am I missing? –  Jul 22 '15 at 11:22
  • @false Because everyone is heterosexual, there is no way that a person likes themselves. I cannot think of any other reason why it would not terminate. I know for a fact that your comments are always for a good reason. –  Jul 22 '15 at 11:24
  • @false or to put it otherwise, `person_likes(A, B)` is semideterministic for what I can see. –  Jul 22 '15 at 11:28
1

before steering on your question, I would note that there are several problems in your code:

About syntax: p1 ; p2 :- p3. is invalid.

?- [user].
p1;p2:-p3.
ERROR: user://1:9:
    No permission to modify static procedure `(;)/2'
    ...

Prolog uses a specific 'encoding' of logical formulae, so called Horn clauses

They are important in automated theorem proving by first-order resolution, because the resolvent of two Horn clauses is itself a Horn clause, and the resolvent of a goal clause and a definite clause is a goal clause.

About modelling the problem: I think it's important to minimize the syntactic differences between the 'natural language' formulae and the computational ones. Every change must be debugged :-)

So, why not to define

rich(Person) :- owns(Person, gold).
owns(fred, gold).
owns(julie, gold).

You can find many questions and answers about Zebra puzzle, I will not repeat them here, so please look [zebra-puzzle] in the Stack Overflow search box. You will see that if/then/else is never required - with good reasons. There are far easier ways to express such basic problems in Prolog.

CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • 1
    Good answer (+1). BTW, this doesn't even look like a "Zebra puzzle" sort of question, it is more about finding the correct way to model the data, as in what are the facts, what are the rules. Your example with `rich/1` and `owns/1` is right on the money. –  Jul 21 '15 at 07:11
  • 1
    you can embed tags with the syntax: `[tag:zebra-puzzle]` – mat Jul 21 '15 at 08:00
1

So i fixed it, but now I have to rework it with what you said above. This way works, but it seems to only get the first answer and not search for another. Example: Output

1 ?- likes(julie,X).
X = harry ;
false.

Program edits:

likes(male(X),female(Y)):-likes(X,Y).
likes(female(X),male(Y)):-likes(X,Y).
likes(X,Y):-
 ((X=julie)->
    ((hasdarkhair(Y))->
        (female(X), male(Y));
        male(X));

  ((X=julie)->
    ((isrich(Y))->
        (female(X), male(Y));
        male(X));

 ((X=mary)->
    ((hasdarkhair(Y))->
        (female(X), male(Y));
        male(X));
    female(X),male(Y))));

 ((X=john)->
    ((isrich(Y))->
        (male(X),   female(Y));
        female(X));
  ((X=john)->
    ((isblonde(Y))->
        (male(X),   female(Y));
        female(X));
 ((X=harry)->
    ((isrich(Y))->
        (male(X),   female(Y));
        female(X));
 ((X=fred)->
    ((hasbrunette(Y))->
        (male(X),   female(Y));
        female(X));
    male(X),female(Y))))).

When it should return

X=Harry
X=Fred

because

likes(julie,fred)
true 

//returns

SuperCell
  • 241
  • 1
  • 12
  • If/then/else 'operator' has been introduced *on purpose* to avoid alternatives worked out, that Prolog does by default. Maybe, think of Prolog as 'programmable' SQL on richer domains than atomic values... – CapelliC Jul 21 '15 at 08:05
1

Changing the code to:

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(X,Y):-((X=julie),(hasdarkhair(Y);isrich(Y)),(female(X),male(Y))).
likes(X,Y):-((X=mary),hasdarkhair(Y),female(X),male(Y)).
likes(X,Y):-((X=john),(isrich(Y);hasblonde(Y)),male(X),female(Y)).
likes(X,Y):-((X=harry),isrich(Y),male(X),female(Y)).
likes(X,Y):-((X=fred),hasbrunette(Y),male(X),female(Y)).
likes(X,Y):-((X=susan);(X=anne)),((male(X),female(Y));(female(X),male(Y))).
    ownscar(john).
love(X,Y):-likes(X,Y),likes(Y,X).

Is that better?

SuperCell
  • 241
  • 1
  • 12
  • But I'm using the semi colon as a or statement to save having to repeat code. Can u give me an example of where it can do better without? – SuperCell Jul 21 '15 at 08:59
0

I'd approach the problem by defining a set of people, all of whom have a required set of attributes (gender, hair color and financial status), and a set of optional likes (gender, hair color and financial status). We'll go with the convention that a don't care state for a particular like is indicated by the anonymous variable _:

person( john  , is( male   , blonde   , poor ) , likes( female , blonde   , rich ) ) .
person( fred  , is( male   , brunette , rich ) , likes( female , brunette , _    ) ) .
person( harry , is( male   , brunette , poor ) , likes( female , _        , rich ) ) .

person( mary  , is( female , brunette , poor ) , likes( male   , brunette , _    ) ) .
person( julie , is( female , blonde   , rich ) , likes( male   , brunette , rich ) ) .
person( susan , is( female , blonde   , poor ) , likes( male   , _        , _    ) ) .
person( anne  , is( female , brunette , poor ) , likes( male   , _        , _    ) ) .

That lets you determine whether an attraction is present very simply:

likes( P1 , P2 ) :-
  person( P1 , _            , likes(G2,H2,S2) ) ,
  person( P2 , is(G2,H2,S2) , _               )
  .

If you want to show mutual attraction, you can simply extend this a bit:

mutual_attraction( P1 , P2 ) :-
  person( P1 , is(G1,H1,S1) , likes(G2,H2,S2) ) ,
  person( P2 , is(G2,H2,S2) , likes(G1,H1,S1) )
  .

It also allows for gender-flexible likes — just use the anonymous variable for gender to indicate don't care.

This approach does come with the restriction that likes are single-valued — there's no convenient way to say, for instance, that john like red hair or blonde hair, but not brown hair.

Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • This is also a good way to approach it (+1). The difference is that you don't "normalize" the data, but you explicitly describe the upsides and downsides of the approach. –  Jul 22 '15 at 06:04