Prolog: Take the first “N” elements of a list

2020-02-13 09:48发布

I need to write a Prolog predicate take(L, N, L1) which succeeds if list L1 contains the first N elements of list L, in the same order. For example:

?- take([5,1,2,7], 3, L1).
L1 = [5,1,2]
?- take([5,1,2,7], 10, L1).
L1 = [5,1,2,7] 

Prolog thus far is making little sense to me, and I'm having a hard time breaking it down. Here is what I have so far:

take([H|T], 0, []).
take([H|T], N, L1) :-
   take(T, X, L2),
   X is N-1.

Can you please explain what I did wrong here?

标签: list prolog
5条回答
ゆ 、 Hurt°
2楼-- · 2020-02-13 10:04

The above code by @CapelliC works if the instantiation is right; if not, it can show erratic behavior:

?- take(Es, 0, Xs).
**LOOPS**                   % trouble: goal does not terminate

?- take([A,_], 1, [x]).          
true.                       % trouble: variable A remains unbound

To safeguard against this you can use iwhen/2 like so:

take(Src, N, L) :-
   iwhen(ground(N+Src), findall(E, (nth1(I,Src,E), I =< N), L)).

Sample queries run with SWI-Prolog 8.0.0:

?- take([a,b,c,d,e,f], 3, Ls).
Ls = [a,b,c].

?- take([a,b,c,d,e,f], N, Ls).
ERROR: Arguments are not sufficiently instantiated

?- take(Es, 0, Xs).
ERROR: Arguments are not sufficiently instantiated

?- take([A,_], 1, [x]).
ERROR: Arguments are not sufficiently instantiated

Safer now!

查看更多
\"骚年 ilove
3楼-- · 2020-02-13 10:05

The obvious solution would be:

take(List, N, Prefix) :-
    length(List, Len),
    (   Len =< N
    ->  Prefix = List
    ;   length(Prefix, N),
        append(Prefix, _, List)
    ).

Less thinking means less opportunity for mistakes. It also makes the predicate more general.

查看更多
Lonely孤独者°
4楼-- · 2020-02-13 10:13

your base case is fine

take([H|T], 0, []).

And also you can say what if N is 1

take([H|T],1,[H]).

But you recursive case some variable is not defined like L2. So we can write this as

take([X|T1],N,[X|T2]):-
N>=0,
N1 is N-1,
take(T1,N1,T2).

which case all varibles are pattern-matched.

查看更多
唯我独甜
5楼-- · 2020-02-13 10:15

findall/3 it's a bit the 'swiss knife' of Prolog. I would use this snippet:

take(Src,N,L) :- findall(E, (nth1(I,Src,E), I =< N), L).
查看更多
Rolldiameter
6楼-- · 2020-02-13 10:21

Here is a definition that implements the relational counterpart to take in functional languages like Haskell1. First, the argument order should be different which facilitates partial application. There is a cut, but only after the error checking built-in (=<)/2 which produces an instantiation_error should the argument contain a variable.

take(N, _, Xs) :- N =< 0, !, N =:= 0, Xs = [].
take(_, [], []).
take(N, [X|Xs], [X|Ys]) :- M is N-1, take(M, Xs, Ys).


| ?- take(2, Xs, Ys).
Xs = [],
Ys = [] ? ;
Xs = [_A],
Ys = [_A] ? ;
Xs = [_A,_B|_C],
Ys = [_A,_B] ? ;
no

Note how above query reads:

How can one take 2 elements from Xs to get Ys?

And there are 3 different answers. If Xs is empty, then so is Ys. If Xs is a list with one element, then so is Ys. If Xs has at least 2 elements, then those two are Ys.


1) The only difference being that take(-1, Xs,Ys) fails (for all Xs, Ys). Probably the best would be to issue a domain_error similar to arg(-1,s(1),2)

查看更多
登录 后发表回答