4

I am trying to learn Prolog and I've been doing some exercises.

There is a list with student's names. Write the predicate filter(L,LN) that returns a second list named LN which includes the names like this:

?- filter([kostas, dimitris, anna, antonis, antonia], LN).
LN = [kostas, anna, antonia]

So..it shows one and then skips one and does this continuously. This is what I've done but it isn't correct.

filter([],[]).
filter([H|T],[H|LN]) :-
    filter(T,LN,0).

filter([H|T],[H|LN],C) :-
    0 is C mod 2, 
    append(H,LN),
    C = C + 1, 
    filter(T,LN,C). 
filter([H|T],LN,C) :-
    (C/2) \= 0,
    C = C + 1,
    filter(T,LN,C).
repeat
  • 18,496
  • 4
  • 54
  • 166
Newera
  • 65
  • 7
  • 2
    A couple of fundamental things right off: (1) `=` is not an assignment operator in Prolog, it's for *unification*. If you want to unify a single variable with the result of an arithmetic expression, you would use `is/2` (*e.g.*, `X is Y + 1`). (2) within a Prolog predicate clause, a variable, once unified with a value, can't be re-assigned, so you cannot do `C is C + 1` since it means that a variable is itself and itself plus 1 which is impossible, You have to have an auxiliary variable, *e.g.*, `C1 is C + 1, filter(T, LN, C1)`. – lurker Sep 12 '15 at 18:47
  • 2
    If you want to test if two numerical expressions result in the same value, use `=:=`. *e.g.*, `0 =:= C mod 2`. And if you want to check for inequality of the expression values, use `=\=`. – lurker Sep 12 '15 at 18:49
  • Oh thanks that helped a lot. – Newera Sep 12 '15 at 19:04
  • It still isn't correct though. – Newera Sep 12 '15 at 19:05
  • Yes, I realize. Those were some primary things that needed fixing. But the logic still isn't quite right. – lurker Sep 12 '15 at 19:40
  • As a hint, it would be a lot easier not to use a counter but to use a list format like, `[X, Y |T]` which is a list with first two elements `X` and `Y` and the rest of the list is `T` (`T` is a list, the *tail*). You know `filter([X], [X]).` is true, so just need the general case. – lurker Sep 12 '15 at 21:36

3 Answers3

4

One of the many cases when using a DCG makes your life much easier:

s([]) --> [].
s([X|Xs]) --> [X], t(Xs).

t([]) --> [].
t(Xs) --> [_], s(Xs).

filter_dcg(List, Filtered) :-
    phrase(s(Filtered), List).

Without a DCG, this would look something like:

q([], []).
q([X|Xs], [X|Ys]) :-
    r(Xs, Ys).

r([], []).
r([_|Xs], Ys) :-
    q(Xs, Ys).

filter_pred(List, Filtered) :-
    q(List, Filtered).

The take home message here is that what you are trying to achieve is easily implemented as a state machine with two states. Each of them transitions to the other, one keeps its input and the other one throws it away.

2

Here is a simple answer that doesn't use any arithmetic:

filter([],[]).
filter([A],[A]).
filter([A,_|T],[A|T2]) :-
    filter(T,T2).

For an empty list, the result is an empty list. For a list of one element, the result is a list of that element. For a list of two ore more elements, the result is the first element followed by the result of the filtering on elements after the first two (so we discard the second one).

Fatalize
  • 3,513
  • 15
  • 25
1

I don't dislike merging arithmetic and list processing:

filter(Xs, Ys) :- must_be(list, Xs), findall(Y, (nth1(I,Xs,Y), I mod 2 =:= 1), Ys).

@Fatalize answer in DCG flavour:

filter_dcg(Xs, Ys) :- must_be(list, Xs), phrase(keep_1(Ys), Xs).

keep_1([]) --> [].
keep_1([A]) --> [A].
keep_1([A|As]) --> [A,_], keep_1(As).
CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • It is fine to do it like this (+1), but `I mod 2 =:= 1` is maybe cleaner (non-variable left-hand side of `is/2` can be problematic in other situations, as you know). Also, `?- filter(Xs, Ys).` where `Xs` is not a proper list does not terminate. My initial DCG solution did not terminate either, so at least I got to correct my answer. –  Sep 14 '15 at 10:45