0
votes

I want to make a Prolog program where I have a "Person" that "Watched" "Movies". And I want to write it like watched(person's code, [movie code 1, movie code 2]) so I can write a single list of watched movies per person instead of writing lots of lines.

I have these movie/2 facts:

% movie(code, movie's name).
movie(1, 'Thor 1').
movie(2, 'Thor 2').
movie(3, 'Captain America').
movie(4, 'Captain America 2').

these person/2 facts:

% person(code, persons's name).
person(1, 'ana').
person(2, 'john').

and these watched/2 facts:

% watched(person's code, movie's code).
watched(1, [1,2,3]).
watched(2, [1,2,4]).

As you see, Ana for example watched the movies 'Thor 1', 'Thor 2' and 'Captain America'.

I tried to query the following:

?- watched(P, [X,Y,Z]), movie(Z,N), Z is X, Z is Y.

but prolog won't accept it.

Is there a way to assign two variables to one? Like Z is X, Z is Y

Also, if I query:

(_, [ _ ,_,Z| _ ]) = (_, [1, 2, 3, 4]), movie(Z,N).

prolog will return a movie, but only one, and (_, [ _, _|Z]) won't work to return more than one movie.

2
What behavior do you expect to get when you put 'Z is X, Z is Y'?Steven
I expect Z to return movies, as X, Y and Z watched(P, [X,Y,Z]) watched(1, [1,2,3]).J. Doe
Is what you want to achieve to be able to query something like watched_movie('ana', 'Thor 2')? I'm sorry, but I honestly can't tell from your attempts what your actual goal is.Steven
Yes, but I want to relate more than one movie at the sime time.J. Doe

2 Answers

1
votes

Ok, so you want to be able to query something of the form watched_movie('ana', 'Thor 2'). Let's look at the steps involved.

  • The header will look like watched_movie(P, M) :-, where P is the person's name and M the movie name.
  • The parameters are names, but the data uses IDs. So let's convert the names to IDs:

    person(PId, P),    
    movie(MId, M),
    

    This looks at the database to get the corresponding IDs and names.

  • From these pairs of IDs, the predicate should only succeed if this person has actually watched the movie. To do this, simply get the list of watched movies from the database and check if it contains the movie ID.

    watched(PId, Movies),
    member(MId, Movies).
    

    (member is a built-in prolog predicate; but I encourage you to write it yourself as exercise.)

And that is all. Put it all together and the predicate looks like this:

watched_movie(P, M) :-
    person(PId, P),    
    movie(MId, M),
    watched(PId, Movies),
    member(MId, Movies).

Now you can query everything you want.

?- watched_movie('ana','Thor 2').
true.

?- watched_movie(P, 'Thor 2').
P = ana;
P = john.

?- watched_movie('ana', M).
P = 'Thor 1';
P = 'Thor 2';
P = 'Captain America'.

?- watched_movie(P, M).
M = 'Thor 1',
P = ana;
...

As for your attempts:

  • watched(P, [X,Y,Z]), movie(Z,N), Z is X, Z is Y.

    Here you query for people that have watched exactly 3 movies (because the list is exactly that long), but then when you put Z is X, Z is Y you essentially say that all 3 movies have to be the same. There is no such data in your database, so the query fails.

  • (_, [ _ ,_,Z| _ ]) = (_, [1, 2, 3, 4]), movie(Z,N).

    Although this is valid prolog, the only thing the first part of this query really does is say that Z == 3. The second part gets the name of this movie with ID 3, and since there is only one you get exactly that result.

1
votes

The answer of @Steven covers everything there is to answer about this question, but reading the comments it looks like OP is still confused about the different solutions found by Prolog:

"Yes, but I want to relate to more than one movie at the sime time."

"So, the only way to make a database with watched is to write lots of lines? So, if someone watched 6 movies, it would be: watched(1, 1). watched(1, 2). watched(1, 3). watched(1, 4). watched(1, 5).?"

It seems as if what you are trying to accomplish is to obtain all the watched movies for a given person, rather than getting separate answers. To do so, you can use extra-logical all-solution predicates. Let's look at its use for your code.

Using @Steven's predicate (trivially edited):

person_watched_movie(P,M) :-
    person(PId,P),
    watched(PId,MovieIds),
    member(MId,MovieIds),
    movie(MId,M).

We now simply wrap around the findall/3 predicate like so:

person_watched_movies(Person,Movies) :-
    findall(Movie,person_watched_movie(Person,Movie),Movies).

Now you can obtain the list of movies someone/everyone/.. watched at once:

?- person_watched_movies('ana',M).
M = ['Thor 1', 'Thor 2', 'Captain America'].

Let's think about a general query for a second, what should happen when we ask Prolog to find us any movies watched by any person? Well, it should give us any movies available in the facts that were watched at least once - note that duplicates may appear here since multiple people could have watched the same movie. Let's test it!

?- person_watched_movies(X,Y).
Y = ['Thor 1', 'Thor 2', 'Captain America', 'Thor 1', 'Thor 2', 'Captain America 2'].

Great! Moving on, we can also use this predicate to quickly obtain the list of watched movies of some specific people:

Using the disjunction operator:

?- (X='ana' ; X='john'), person_watched_movies(X,Y).
X = ana,
Y = ['Thor 1', 'Thor 2', 'Captain America'] ;
X = john,
Y = ['Thor 1', 'Thor 2', 'Captain America 2'].

Or equivalently, using member/2 and a list:

?- member(X,['ana','john']), person_watched_movies(X,Y).
X = ana,
Y = ['Thor 1', 'Thor 2', 'Captain America'] ;
X = john,
Y = ['Thor 1', 'Thor 2', 'Captain America 2'].

Finally, if you would like to simultaneously retrieve the person who watched the movie, you could make use of the -/2 notation as shown below:

person_watched_movies(Person,Movies) :-
    findall(Person-Movie,person_watched_movie(Person,Movie),Movies).

When given the same general query we used before, this will instead output:

?- person_watched_movies(X,Y).
Y = [ana-'Thor 1', ana-'Thor 2', ana-'Captain America', john-'Thor 1', john-'Thor 2', john-'Captain America 2'].

Hope this helps!