简体   繁体   中英

Prolog: Take the first "N" elements of a list

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?

Here is a definition that implements the relational counterpart to take in functional languages like Haskell 1 . 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].

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)

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

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

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

?- take([,_], 1, [x]).          
true.                       % trouble: variable  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], , Ls).


?- take(Es, 0, Xs).


?- take([A,_], 1, [x]).

Safer now!

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.

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.

取(L,N,L1):-长度(L1,N),追加(L1,_,L)。

This is performant, general and deterministic:

first_elements_of_list(IntElems, LongLst, ShortLst) :-
    LongLst = [H|T],
    (   nonvar(IntElems) -> Once = true
    ;   is_list(ShortLst) -> Once = true
    ;   Once = false
    ),
    first_elements_of_list_(T, H, 1, IntElems, ShortLst),
    (Once = true -> ! ; true).

first_elements_of_list_([], H, I, I, [H]).

first_elements_of_list_([_|_], H, I, I, [H]).

first_elements_of_list_([H|LongLst], PrevH, Upto, IntElems, [PrevH|ShortLst]) :-
    Upto1 is Upto + 1,
    first_elements_of_list_(LongLst, H, Upto1, IntElems, ShortLst).

Result in swi-prolog:

?- first_elements_of_list(N, [a, b, c], S).
N = 1,
S = [a] ;
N = 2,
S = [a,b] ;
N = 3,
S = [a,b,c].

?- first_elements_of_list(2, [a, b, c], S).
S = [a,b].

Below is a variant which also supports:

?- first_elements_of_list_more(10, [5, 1, 2, 7], L1).
L1 = [5,1,2,7].
first_elements_of_list_more(IntElems, [H|LongLst], [H|ShortLst]) :-
    once_if_nonvar(IntElems, first_elements_of_list_more_(LongLst, 1, IntElems, ShortLst)).

first_elements_of_list_more_([], Inc, Elems, []) :-
    (var(Elems) -> Inc = Elems
    ; Elems >= Inc).

first_elements_of_list_more_([_|_], E, E, []).

first_elements_of_list_more_([H|LongLst], Upto, IntElems, [H|ShortLst]) :-
    succ(Upto, Upto1),
    first_elements_of_list_more_(LongLst, Upto1, IntElems, ShortLst).

once_if_nonvar(Var, Expr) :-
    nonvar(Var, Bool),
    call(Expr),
    (Bool == true -> ! ; true).

nonvar(Var, Bool) :-
    (nonvar(Var) -> Bool = true ; Bool = false).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM