Get substring from a fact - prolog

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).

Related

Prolog Querying: Unifying a variable in a nested compund Term

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).

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.

Check if any element in List begins with a specific character

I've a list of words, for example [cola,fanta,pepsi] and I want to write a predicate that checks if any of the elements begins with the character specified.
My code so far is as follows:
chk_first_letter(Char,[]):-fail.
chk_first_letter(Char, [H|T]):-
perform_check(Char, H);
chk_first_letter(Char, T).
perform_check(Char,[First|_]):-memberchk(Char, First).
However consulting my file and calling chk_first_letter(p,[cola,fanta,pepsi]) gives me no even if pepsi begins with a p.
I've tried with Char==First instead of memberchk(Char,First) but it didn't work either. I'm not sure about the difference.
You have a list of atoms, and your perform_check/2 compares two atoms. An atom is not a list of characters! You need to use atom processing, for example:
perform_check(First, Word) :-
sub_atom(Word, 0, 1, _After, First).
http://gprolog.univ-paris1.fr/manual/html_node/gprolog043.html#sec200
There are a bunch of other built-ins in this section that could be used, for example for breaking the atom into characters or character codes (atom_chars/2 and atom_codes/2). But what sub_atom/5 also allows you to do easily:
prefixes of any length:
sub_atom(Word, 0, _Length, _After, Prefix).
suffixes:
sub_atom(Word, _Before, _Length, 0, Suffix).
First attempt:
chk_first_letter(Char, Atoms) :- member(A, Atoms), atom_chars(A, [Char|_]).
atom_chars/2 it's an ISO predicate.
Your code it's almost working, can be simplified this way:
chk_first_letter(Char, [H|T]):-
atom_chars(H, [Char|_]);
chk_first_letter(Char, T).
memberchk expects to be called with a list as the second argument. In your case, you're providing it with a single character.
And then you can probably do away with it altogether by taking advantage of unification:
perform_check(Char,[Char|_]).
This assumes your string type is a list of characters (whatever the character format). If you intend to operate on atoms directly, you could do it this way instead:
perform_check(Char,String) :- atom_concat(Char,_,String)
There would be a few more steps to make your code more idiomatic, but this seems to be the actual wrong part of it.

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