Prolog Querying: Unifying a variable in a nested compund Term - prolog

I have a set of Prolog facts which I am attempting to query, but I am having trouble figuring out how to unify variables that are more deeply nested. An example of a fact in the database is as follows:
character(super,mario, type(human)).
character(donkey,kong, type(ape)).
videogame(year(1991), console(nintendo), title([super,mario,bros]), characters([character(super,mario), character(princess,peach)])).
videogame(year(1999), console(nintendo), title([super,smash,bros]), characters([character(super,mario), character(donkey,kong)])).
So for example, I want to query which characters appear in more than 1 video game. So I know that I want to create two instances of videogame and ensure that the same character appears (by name) in both games, and that those games are not the same game (by title).
Thus I would do something like:
?- videogame(_, _, title(Title1), characters(?????)), videogame(_, _, title(Title2), characters(?????)), not(Title1 == Title2).
However, my issue has been finding a way to create variables within the characters portion such that the "first" and "last" name of the characters unifies with a character that appears in more than one videogame.
I have tried things along the lines of the following (to fill in the question marks above in the full query) to try and unify the first and last name of the character, but they produce the error:
characters(character[First,Last])
characters([First, Last])
ERROR: Syntax error: Operator expected
I have also tried the following, which just returns false.
?- videogame(_, _, title(Title1), _), videogame(_, _, title(Title2), _), member(actor(First,Last,_), cast), not(Title1 == Title2).
I would appreciate some help so that I can gain a better understanding of how query in Prolog even in the presence of more nested terms.

with intersection/3 from library(lists) I would write
?- videogame(_,_,title(Title1),characters(Cs1)), videogame(_,_,title(Title2),characters(Cs2)), Title1 #< Title2, intersection(Cs1,Cs2,Common).

Related

Prolog beginner. How to take list as parameter and pass it on

I am a total beginner at Prolog. I am struggling with creating a rule, which takes a list as parameter and passes the list onto another rule. Here is my code:
combine([], '').
combine([L|List], Total) :-
combine(List, CombinedRest),
atom_concat(L, CombinedRest, Total).
findHeadline([W|Words], Combined) :-
combine(Words, Combined).
findHeadline2([Words], Combined) :-
combine(Words, Combined).
findHeadline works as expected, but findHeadline2 does not. Here is the output:
1 ?- findHeadline([billig, enkeltmand], Combination).
Combination = enkeltmand.
2 ?- findHeadline2([billig, enkeltmand], Combination).
false.
The output I was expecting from findHeadline was:
Combination = billigenkeltmand.
How can it be that this does not work?
I tried to utilize trace in SWI-prolog, but it gave me no clue whatsoever, as the findHeadline rule just exits immediately and does not call the combine rule at all.
It is not very clear what it is exactly that you are after. If you just want to concatenate a list of atoms to get one atom, use atomic_list_concat/2 available in SWI-Prolog:
?- atomic_list_concat([foo, bar, baz], C).
C = foobarbaz.
At the moment, your findHeadline2/2 reads:
"Take a list containing exactly one element, and combine/2 that element."
This is not what you are after, I have the feeling.
Your findHeadline/2, on the other hand, says:
"Take a list of at least one element, and combine/2 all elements except the first".
This is important: never ever ignore compilation warnings. You get code that does something, but you can be almost certain that it does not do what you want it to do, which is bad, or that if someone else reads your code, they will be confused, which is also bad.

Not in Prolog and use of Bagof

I have database like this:
movie(matrix,wachowski,thriller).
movie(terminator, cameron, thriller).
movie(Gladiator, scott, costume).
movie(star wars, lucas, fantasy).
movie(star trek, abrams, fantasy).
And I want to know who direct fantasy film except Abrams.
I suppose I need to use 'not' predicate, but I don't know exactly how it works.
?- movie(X,not(abrams),fantasy).
But unfortunately it doesn't work.
One more query is what kind of films is not a thriller:
?- movie(X,_,not(thriller)).
Still not working.
Next problem is I need to use predicate direct(Director, listsOfMovie) based on bagof.
?- direct(Director, listsOfMovie) :- bagof(Director,movie(Director,listsOfMovie,_), listsOfMovie).
Still without success :(
Anyone can help?
Use of not
You can't use Prolog predicates like functions. not/1 is a predicate which accepts a query as an argument. So this isn't doing what you think:
movie(X,not(abrams),fantasy).
This is querying movie with a second argument of not(abrams). You don't have any facts or predicates that match movie(_, not(_), _) so it will always fail.
If you want to know which films were not thrillers, you might render it:
movie(X, _, Type),
Type \= thriller.`
Using not, it might be:
not( movie(X, _, thriller) ).
If you wanted the syntax of movie(_, not(_), _) to work, you could write a predicate for it:
movie( Name, not(Director), Type ) :-
movie(Name, D, Type),
D \= Director.
Now we have either a fact or a predicate head that matches the form, movie(_, not(_), _), and then the query, movie(X, not(abrams), Y) would work. But it's not normally done this way.
Using bagof/3
Let's look at your use of bagof. In the simplest case, bagof is supposed to take three arguments:
bagof(X, {query involving X}, ListOfSatisfingXs)
So bagof will run the {query involving X} generating each X that makes it true, creating ListOfSatisfingXs, a unique, sorted list of such instantiations of X. In other words, ListOfSatisfingXs is the unique, sorted values of X that make {query involving X} succeed.
In your case, you've gotten the arguments to bagof a bit mixed up:
direct(Director, listsOfMovie) :-
bagof(Director, movie(Director, listsOfMovie, _), listsOfMovie).
Here, you're reusing your Director argument as your bagof argument, which is not good (since it's not intended). Since you're looking for a list of movies, the first argument should represent the movie. Your query to movie is using listsOfMovie, your intended target argument to hold the list result, which it shouldn't. And finally, listsOfMovie is an atom, not a variable, since it doesn't start with a capital letter.
The corrected version would be:
director_movies(Director, ListOfMovies) :-
bagof(Movie, movie(Director, Movie, _), ListOfMovies).
Here, the bagof is getting the *Unique, sorted list of Movie values such that movie(Director, Movie, _) is true and providing that resulting list in ListOfMovies.

Why does my replace/2 program always return false?

My goal is to replace the '_' with logic variables in a given list. My code:
replace([], []).
replace(['_'|As], [_|Bs]) :-
replace(As, Bs).
replace([A|As], [B|Bs]) :-
A \= '_',
B = '#',
replace(As, Bs).
It will return a proper list but always ends up with false. Any help please?
Any input that matches replace(['_'|As], [_|Bs]), also matches replace([A|As], [B|Bs]). This means that while the first clause is executed, a choice point is left for the latter one.
After prolog has found the first result, it notices that there are still choice points open, so it will ask you if you want more results. If you say yes, it will not try to execute the other clause. This will always fail, since A \= '_' is never true.
Note that this behavior is not wrong. The 'false' doesn't mean that the program failed, it just means that no more results were found after those that had already been presented. In this case, you know that there will always only be one result, so you can tell prolog not to leave any choice point open by using the cut operator ! like this:
replace(['_'|As], [_|Bs]) :-
replace(As, Bs), !.
This essentially tells prolog that if this clause succeeded, the remaining possible matches should no longer be considered. As such, no choice points are left open, and once you get the first result, the execution is done and returns true.

Get substring from a fact

So I've got facts that are written like this document(Title,Topic). I want to make a rule where with two arguments.The first one is Keys which is a list of keywords and the second one is the document.
I want to get as a result the titles of the documents which cointain the keywords I've given.
This is what I wrote till now:
isInDoc([],'no'). %Recursion stops here. Don't know what to put as 2nd argument
isInDoc([H|T],document(Title,_)) :-
sub_string(case_insensitive,H,document(Title,_)),
isInDoc(T,document(Title,_)).
What I've thought is that I read the head of the list of keywords and see if it is a substring of the title of the document. When I type document(Title,_) in SWI-Prolog I get the titles of the documents. I can't think of any other way to get access to the title of the document. If I do a question I get this error ERROR: table: sub_string/3: Type error:'text' expected, found document(_G6503,_G6504).
Isn't document(Title,_) of type text?
in SWI-Prolog, sub_string/5 has been introduced recently, but works only on strings. The correct predicate to use is sub_atom/5 (it is also ISO standard):
isInDoc(Tokens, document(Title, _)) :-
member(Token, Tokens),
sub_atom(Title, _,_,_, Token).
4 ?- document(T,_), isInDoc([and], document(T,_)).
T = 'Rules and Uncertainty' ;
false.
5 ?- document(T,_), isInDoc([and, certa], document(T,_)).
T = 'Rules and Uncertainty' ;
T = 'Rules and Uncertainty' ;
false.
I use member/2 to 'try' all tokens, instead of writing a recursive rule. Btw, since you expect that isInDoc/2 will fail when any of the tokens cannot be found, you can drop altogether the base case (but since you used no, that will never match document(_, _), the effect is the same).
edit Maybe the snippet can be made more useful separating the matching of atoms from the document:
isInDoc(Tokens, document(Title, _)) :- contains(Tokens, Title).
contains(Tokens, Atom) :-
member(Token, Tokens),
sub_atom(Atom, _,_,_, Token).

Translate Prolog Words

I'm working on this this wonderful Prolog project and I'm stuck at this situation where I need to translate certain words into other words (e.g "i" into "you". "my into "your")
This is what I've done and I'm pretty sure it's kinda iffy. I enter the sentence and when It goes to convert it only changes the one word then goes on wacky on me. (e.g. "i feel happy" changes to "you" then it crashes.)
translate([]).
translate([H|T], [NewH|NewT]):-
means(H,NewH);
spit(T,NewT).
means(i,you).
means(my,your).
means(mine,yours).
Here's the fix:
translate([], []).
translate([H|T], [NewH|NewT]):-
means(H, NewH),
translate(T,NewT).
means(i, you) :- !.
means(my, your) :- !.
means(mine, yours) :- !.
means(X, X).
The changes are as follows:
I fixed the missing parameter to the first definition of translate (it's considered a completely independent function if the number of parameters don't match).
I'm calling translate on the rest of the list when the first item is translated.
I'm asserting that every word means itself unless specified otherwise. The :- ! part means if you match this, don't try the rest (this is to avoid lists always matching with themselves).
Example usage:
?- translate([this, is, i], X).
X = [this, is, you].

Resources