Philkav
Philkav

Reputation: 122

SWI Prolog - conditional NOT?

I'm trying to make a prolog function. The function reads in a sentence, 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.

Upvotes: 1

Views: 2840

Answers (4)

Kaarel
Kaarel

Reputation: 10672

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

Upvotes: 0

nedned
nedned

Reputation: 3707

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.

Upvotes: 1

liori
liori

Reputation: 42227

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.

Upvotes: 2

Jerome
Jerome

Reputation: 2370

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

Upvotes: 4

Related Questions