[英]Prolog: Take the first "N" elements of a list
我需要编写一个 Prolog 谓词take(L, N, L1)
如果列表L1
以相同的顺序包含列表L
的前N
个元素,它就会成功。 例如:
?- take([5,1,2,7], 3, L1).
L1 = [5,1,2]
?- take([5,1,2,7], 10, L1).
L1 = [5,1,2,7]
到目前为止,Prolog 对我来说意义不大,而且我很难分解它。 这是我到目前为止所拥有的:
take([H|T], 0, []).
take([H|T], N, L1) :-
take(T, X, L2),
X is N-1.
你能解释一下我在这里做错了什么吗?
这是一个实现关系对应物的定义,以take
Haskell 1等函数式语言。 首先,参数顺序应该不同,这有利于部分应用。 有一个切入点,但只有在错误检查内置(=<)/2
,如果参数包含变量,它会产生一个instantiation_error
。
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].
请注意上面的查询是如何读取的:
如何从
Xs
取出 2 个元素来获得Ys
?
并且有3个不同的答案。 如果Xs
是空的,那么Ys
也是空的。 如果Xs
是一个包含一个元素的列表,那么Ys
。 如果Xs
至少有 2 个元素,那么这两个元素就是Ys
。
1) 唯一的区别是take(-1, Xs,Ys)
失败(对于所有Xs, Ys
)。 可能最好的方法是发出类似于arg(-1,s(1),2)
的domain_error
findall/3 有点像 Prolog 的“瑞士刀”。 我会使用这个片段:
take(Src,N,L) :- findall(E, (nth1(I,Src,E), I =< N), L).
如果实例化正确,@CapelliC的代码就可以工作; 如果没有,它可能会显示不稳定的行为:
?- take(Es, 0, Xs). **LOOPS** % trouble: goal does not terminate ?- take([A,_], 1, [x]). true. % trouble: variable A remains
为了防止这种情况,您可以像这样使用iwhen/2
:
take(Src, N, L) :-
iwhen(ground(N+Src), findall(E, (nth1(I,Src,E), I =< N), L)).
使用 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
现在更安全!
显而易见的解决方案是:
take(List, N, Prefix) :-
length(List, Len),
( Len =< N
-> Prefix = List
; length(Prefix, N),
append(Prefix, _, List)
).
更少的思考意味着更少的错误机会。 它还使谓词更通用。
你的基本情况很好
take([H|T], 0, []).
你也可以说如果 N 是 1
take([H|T],1,[H]).
但是在递归情况下,某些变量没有像 L2 那样定义。 所以我们可以把它写成
take([X|T1],N,[X|T2]):-
N>=0,
N1 is N-1,
take(T1,N1,T2).
在这种情况下,所有变量都是模式匹配的。
取(L,N,L1):-长度(L1,N),追加(L1,_,L)。
这是高性能的、通用的和确定性的:
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).
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].
下面是一个变体,它也支持:
?- 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).
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.