views:

472

answers:

4

Hi,

I'm trying to make a prolog function. The funtion reads in a sentance, and then tries to extract a key word. If a key word is found, it prints a message. I want it to also print a message if no keywords are found. Here is my example :

contains([word1|_]) :- write('word1 contained').
contains([Head|Tail]) :- Head \= word1, contains(Tail).
contains([word2|_]) :- write('word2 contained').
contains([Head|Tail]) :- Head \= word2, contains(Tail).
contains([word3|_]) :- write('word3 contained').
contains([Head|Tail]) :- Head \= word3, contains(Tail).

The above code will check and see if the extracted word is present. But it does not give an answer if the words 'word1,word2 or word3' are not contained. Does anybody know how I should go about implementing this?

I tried adding :

contains([_|_]) :- write('nothing contained'),nl.
contains([Head|Tail]) :- Head \= _, contains(Tail).

But clearly this is the wrong thing to do.

+2  A: 

In imperative language you'd use some kind of flag; for example:

found = False
for word in wordlist:
    if word in ('car', 'train', 'plane'):
        print "Found: " + word
        found = True
if not found:
    print "Nothing found."

You can implement this flag as another parameter to your clauses:

% entry point
contains(X) :- contains(X, false).

% for each word...
contains([Word|Rest], Flag) :-
    Word = car   -> (write('Car found.'),   nl, contains(Rest, true)) ;
    Word = train -> (write('Train found.'), nl, contains(Rest, true)) ;
    Word = plane -> (write('Plane found.'), nl, contains(Rest, true)) ;
    contains(Rest, Flag).

% end of recursion
contains([], true).
contains([], false) :- write('Nothing found.'), nl.

If you want to make distinct clause for each word (and abstract the loop), change the middle part to:

% for each word...
contains([Word|Rest], Flag) :-
    checkword(Word) -> NewFlag=true ; NewFlag=Flag,
    contains(Rest, NewFlag).

% and at the end:
checkword(car)   :- write('Car found.'), nl.
checkword(plane) :- write('Plane found.'), nl.
checkword(train) :- write('Train found.'), nl.
liori
The only problem with this solution is that because my function is called repeatedly until the program ends, when the function is used and i dont write 'word1', it says : 'nothing found.' For example if i type : word1 , i get : 'word1 contained' if i type : word2 , i get : 'nothing found' if i type : word3 , i get : 'nothing found' if i type word1 again, i get : 'word1 contained'. so it only works for the 1st condition, all the others seem to be ignored after that..
Philkav
Ha, I somehow misread your code... you want to find all occurences. I'll edit my answer.
liori
+4  A: 

The standard way to write the main part of your contains predicate is:

contains([word1|_]) :- !, write('word1 contained').
contains([word2|_]) :- !, write('word2 contained').
contains([word3|_]) :- !, write('word3 contained').
contains([Head|Tail]) :- contains(Tail).

Which means:

  • when you find a word, don't search any further (this is what the cut (!) operator is for).
  • when nothing else worked, recurse on tail.

To add an answer in case nothing is found, just add another cut on the recursive call, so that the later case is only called when nothing else (including recursion) worked:

contains([word1|_]) :- !, write('word1 contained').
contains([word2|_]) :- !, write('word2 contained').
contains([word3|_]) :- !, write('word3 contained').
contains([Head|Tail]) :- contains(Tail), !.
contains(_) :- write('Nothing found').
Jerome
Using cuts isn't considered nice.
liori
Well, using cuts does mean that your program is no longer a pure logic program and they certainly can make make reading and debugging code painful, but used sparingly and at the appropriate times they can yield more concise and efficient code. They're hard to avoid when writing a large codebase where efficiency is of concern.
humble coffee
Plus, using `write` means the program isn't purely logical anyway.
bcat
`write` might be only a placeholder.
liori
@liori, by the way, if-then and if-then-else constructs in prolog are actually implemented using a cut. I only mention this cos you seem to disapprove of using cuts but you have if-then-else in your answer.
humble coffee
Ok, I checked SWI-Prolog's documentation... you're right. I was under assumption it is defined more like `((If, Then); (\+If, Else))`.
liori
Not that it really matters much, but since`\+` is negation-by-failure then chances are it's also implemented with a cut.
humble coffee
+1  A: 

Here is how I would do this:

contains(Words) :-
    findall(Word,has(Words,Word),Sols),
    print_result(Sols).

% Word is a target word in the list Words
has(Words,Word) :-
    member(Word,Words), 
    member(Word,[word1,word2,word3]).

print_result([]) :- write('Nothing found.\n').
print_result([X|Xs]) :- print_sols([X|Xs]).

print_sols([]).
print_sols([X|Xs]) :-
    concat(X, ' contained.\n',Output),
    write(Output),
    print_sols(Xs).

The advantage of this approach is that it uses a higher level of abstraction, making the predicate easier to read. Since there is just one list of target words, it also becomes easier to maintain, rather than having to add a separate clause for each new word.

The trick is with the has predicate which uses member/2 twice; once to select an item from the input list, and a second time to test that it is one of the target words. Using this as an argument to findall/3 then yields all the target words that were found in the input list.

Note: The [X|Xs] in print_results just avoids having to use a cut in the first clause.

humble coffee
A: 

I think that liori has the best answer. Here is a slightly different approach that might make sense in some cases, i.e.:

  • generate a print-out
  • if the print-out is empty then print "Nothing found", otherwise output the print-out

The following works in SWI-Prolog and probably not in other Prologs because it uses with_output_to/2:

% Define what are the keywords
keyword(word1).
keyword(word2).
keyword(word3).

% Define how the found keywords are pretty-printed
print_keyword(W) :-
    format("Found: ~w.~n", [W]).

% Generate a print-out and output it unless its empty
print_keywords(Words) :-
    with_output_to(atom(PrintOut),
                   forall((member(W, Words), keyword(W)), print_keyword(W))),
    (
     PrintOut == ''
    ->
     writeln('Nothing found.')
    ;
     write(PrintOut)
    ).
Kaarel