我想编写一个Prolog程序,将奇数长度列表的中间元素删除到另一个列表中。
例如,如果我们给出: delete_mid([1,2,3,4,5],L)
,那么它将产生答案:L = [1,2,4,5]
。
我很惊讶,有点难过,因为到目前为止,没有一个答案采取最明显的方法;你肯定在学校里听说过这个方法(我猜测这就是OP期望做的方法)。
然而,这种方法一次性解释或实现起来有些困难,因此,首先,这里有一个谓词可以找到中间元素:
list_mid([H|T], Mid) :-
list_mid_1(T, T, H, Mid).
list_mid_1([], _, Mid, Mid).
list_mid_1([_,_|Fast], [S|Slow], _, Mid) :-
list_mid_1(Fast, Slow, S, Mid).
?- list_mid([], Mid).
false.
?- list_mid([x], Mid).
Mid = x.
?- list_mid([a,x,b], Mid).
Mid = x.
?- list_mid([a,a,x,b,b], Mid).
Mid = x.
?- list_mid([a,a,x,b], Mid).
false.
看起来可以工作了。现在,我可以尝试添加保留它当前丢弃的部分的部分。
我很忙,所以这花了一些时间。与此同时,Raubsauger的答案正是我想要的。我没有看到它,而是写了这个:
delete_mid([H|T], L) :-
delete_mid_1(T, T, H, L).
delete_mid_1([], Rest, _, Rest).
delete_mid_1([_,_|Fast], [H|Slow], Prev, [Prev|Back]) :-
delete_mid_1(Fast, Slow, H, Back).
这个解决方案不如Raubsauger的解决方案整洁,但似乎是相同的解决方案。它在@false的测试用例下终止。
我认为list_middle/2
谓词已经足够了;我又惊讶又有点难过,只有Raubsauger看到了它(或已经知道它)。
find_mid/2
的第一个参数真的是一个查找吗? - falselist_middle/2
。 - TA_internappend(Xs,Ys,[])
。但是delete_mid(L,[a,b])
不能仅通过索引解决。 - false现在我也想加入(回答这个问题的第8个答案)。
delete_mid(Ori, Del):-
delete_mid(Ori, Ori, Del).
delete_mid([_], [_|Slow], Slow).
delete_mid([_,_|Fast], [H|Slow], [H|Ret]):-
delete_mid(Fast, Slow, Ret).
?- delete_mid([1, 2, 3, 4, 5], Del).
Del = [1, 2, 4, 5] ;
false.
?- delete_mid([1, 2, 3, 4], Del).
false.
?- delete_mid(L, []).
L = [_1500] ;
false.
?- dif(A,B), delete_mid([A|_], [B|_]).
false.
关于这个想法:我看到TA_interns answer 关于获取中间元素(list_mid
)的方法,我想:
这太聪明了。但是等等……这可以改进。
[_,_|Fast]
)中剥离两个元素,但只从元素列表([H|Slow]
)中剥离一个元素。当快速列表中只剩下一个元素([_]
)时,您就会命中慢速列表的中间元素。因此,将其删除并将其余部分放在返回轨道上。在递归向前进行时,将从慢速列表中删除的所有元素(H
)作为返回列表的头部,并由递归填充其余部分。[H|Ret]
预先处理,在递归调用之前完成,填充空白Ret
。请参见1。 - Will Ness我认为你需要使用nth0/4
谓词。只需找到中间元素的索引,然后使用nth0/4
删除它。
delete_middle(Ls, Ls1) :-
length(Ls, L),
divmod(L, 2, Q, 1), % constrain remainder to be 1: fails on even list
nth0(Q, Ls, _, Ls1).
生成变量:唯一的问题出在divmod上。
divmod1(Dividend, Divisor, Quotient, Remainder) :-
( var(Dividend)
-> Dividend is Divisor*Quotient+Remainder
; divmod(Dividend, Divisor, Quotient, Remainder)
).
delete_middle(Ls, Ls1) :- % Reversed the clauses.
nth0(Q, Ls, _, Ls1),
divmod1(L, 2, Q, 1),
length(Ls, L).
?- dif(A, B), delete_middle([A|_], [B|_]).
false.
?- delete_middle(X, []).
X = [_382] ;
false.
listing(divmod1)
查看如何正确缩进此内容。 - false使用 nth0/4
方法的解决方案效率很高,但我们可以采用声明式的方式来解决吗?
middle_less(InList,MiddlelessList,Middle) :-
append([Prefix,[Middle],Suffix],InList),
length(Prefix,Len),
length(Suffix,Len),
append(Prefix,Suffix,MiddlelessList).
这基本上是Prolog形式的问题陈述。
它也可以工作:
:- begin_tests(middleless).
test("empty list",fail) :- middle_less([],_,_).
test("1-element list",[true([MLL,M] == [[],a]),nondet]) :-
middle_less([a],MLL,M).
test("2-element list",fail) :-
middle_less([a,b],_,_).
test("3-element list",[true([MLL,M] == [[a,c],b]),nondet]) :-
middle_less([a,b,c],MLL,M).
:- end_tests(middleless).
所以:
?- run_tests.
% PL-Unit: middleless .... done
% All 4 tests passed
true.
但是对于一个包含1001个元素的列表:
?- length(L,1001),time(middle_less(L,MLL,M)).
% 757,517 inferences, 0.110 CPU in 0.111 seconds (99% CPU, 6862844 Lips)
有一天,编译器会自动地将middle_less
的规范转变为高效的解决方案。
middle_less(InList,MiddlelessList,Middle) :- list_onelesslong(InList, MiddlelessList), <同上>
, 其中 list_onelesslong([_], [])。 list_onelesslong([_X | Xs], [_Y | Ys]) :- list_onelesslong(Xs, Ys).
。这使得谓词能够生成答案。 - Isabelle Newbiedelete_middle([], [], _MiddleDeletedPrefix) -->
[_Middle].
delete_middle([L | Left], [R | ReversedRight], [L | MiddleDeletedPrefix]) -->
[L],
delete_middle(Left, ReversedRight, MiddleDeletedPrefix),
[R].
delete_middle(List, MiddleDeleted) :-
phrase(delete_middle(Left, ReversedRight, MiddleDeleted), List),
reverse(ReversedRight, Right),
append(Left, Right, MiddleDeleted).
?- delete_middle([1, 2, 3, 4, 5], Xs).
Xs = [1, 2, 4, 5] ;
false.
?- delete_middle(Ls, []).
Ls = [_2542] ;
false.
?- dif(A,B), delete_middle([A|_],[B|_]).
false.
?- delete_middle(List, MiddleDeleted).
List = [_2368],
MiddleDeleted = [] ;
List = [_2368, _2392, _2374],
MiddleDeleted = [_2368, _2374] ;
List = [_2368, _2392, _2416, _2398, _2374],
MiddleDeleted = [_2368, _2392, _2398, _2374] ;
List = [_2368, _2392, _2416, _2440, _2422, _2398, _2374],
MiddleDeleted = [_2368, _2392, _2416, _2422, _2398, _2374] ;
List = [_2368, _2392, _2416, _2440, _2464, _2446, _2422, _2398, _2374],
MiddleDeleted = [_2368, _2392, _2416, _2440, _2446, _2422, _2398, _2374] . % etc.
?- delete_middle(X, Y), numbervars(X-Y).
这样稍微容易一些。 - TA_interndelete_mid(List, MiddleDeleted) :-
List = [_ | Tail],
gallop(Tail, MiddleDeleted, List, MiddleDeleted).
gallop([], [], [_Middle | Xs], Xs).
gallop([_,_ | Fast1], [_,_ | Fast2], [X | Xs], [X | Ys]) :-
gallop(Fast1, Fast2, Xs, Ys).
与以往的答案相比,最新的答案是它以双倍速度遍历两个列表,并同时复制前缀。为了保证确定性,它需要至少对前两个参数进行浅索引,但SWI-Prolog已经实现了这一点:
?- delete_mid([1, 2, 3, 4, 5], MiddleDeleted).
MiddleDeleted = [1, 2, 4, 5].
?- delete_mid(Xs, []).
Xs = [_2008].
?- delete_mid(Xs, [a, b]).
Xs = [a, _2034, b].
?- dif(A, B), delete_mid([A | _], [B | _]).
false.
在TA_intern提出的查找中间值算法的基础上进行改进:
%! list_without_middle(SOURCEs,TARGETs)
list_without_middle(SOURCEs,TARGETs)
:-
list_middle(SOURCEs,_MIDDLE_,PREFIXs,SUFFIXs) ,
lists:append(PREFIXs,SUFFIXs,TARGETs)
.
%! list_middle(LISTs,MIDDLE,PREFIXs,SUFFIXs)
list_middle([ITEM|LISTs],MIDDLE,PREFIXs,SUFFIXs)
:-
list_middle(LISTs,LISTs,ITEM,MIDDLE,PREFIXs,SUFFIXs)
.
%! list_middle(FASTs,SLOWs,ITEM,MIDDLE,PREFIXs,SUFFIXs)
list_middle([],SLOWs,ITEM,ITEM,[],SLOWs) .
list_middle([_,_|FASTs],[ITEM|SLOWs],PREVIOUS_ITEM,MIDDLE,[PREVIOUS_ITEM|PREFIXs],SUFFIXs)
:-
list_middle(FASTs,SLOWs,ITEM,MIDDLE,PREFIXs,SUFFIXs)
.
?- list_without_middle([a,b,c],Ys).
Ys = [a, c].
?- list_without_middle([a,c],Ys).
false.
?- list_without_middle([a,b,c,d,e],Ys).
Ys = [a, b, d, e].
?-
?- list_without_middle(Xs,Ys) .
Xs = [_924],
Ys = [] ;
Xs = [_924, _930, _936],
Ys = [_924, _936] ;
Xs = [_924, _930, _936, _948, _954],
Ys = [_924, _930, _948, _954] %.e.t.c.
?- list_middle([a,b,c],MIDDLE,PREFIXs,SUFFIXs).
MIDDLE = b,
PREFIXs = [a],
SUFFIXs = [c].
?- list_middle([a,c],MIDDLE,PREFIXs,SUFFIXs).
false.
?- list_middle([a,b,c,d,e],MIDDLE,PREFIXs,SUFFIXs).
MIDDLE = c,
PREFIXs = [a, b],
SUFFIXs = [d, e].
?-
?- list_without_middle(Ls,[]) .
Ls = [_4364] ;
ERROR: Out of global stack
?- list_without_middle([a],Ys).
Ys = [].
?- dif(A,B) , list_without_middle([A|_],[B|_]) .
ERROR: Out of global stack
?-
without_middle(Ls, Ls1):-
without_middle(Ls, 0, Ls1).
without_middle([_Mid|Tail], Len, Tail):-
length(Tail, Len).
without_middle([Item|Tail], Len, [Item|NTail]):-
succ(Len, Len1),
without_middle(Tail, Len1, NTail).
without_middle(Ls, Ls1):-
without_middle(Ls, [], Ls1).
without_middle([_Mid|Tail], Tail, Tail).
without_middle([Item|Tail], RTail, [Item|NTail]):-
without_middle(Tail, [_|RTail], NTail).
示例测试用例:
?- without_middle([a,b,c,d,e,f,g], L).
L = [a, b, c, e, f, g] ;
false.
?- without_middle([a,b,c,d,e,f], L).
false.
?- without_middle(L, []).
L = [_552] ;
false.
?- dif(A,B), without_middle([A|_], [B|_]).
false.
append/3
:del_mid([_], []). % if input only has one element => output is []
del_mid([H|T], [H|X]) :-
append(M, [Litem], T), % M = list without first and last (Litem) element
del_mid(M, R), % Apply on M; if M is only one item => R will be []
append(R, [Litem], X). % X = R + [last item] => which gets added as result's tail
?- del_mid([], X).
false.
?- del_mid([a], X).
X = [] ;
false.
?- del_mid([a,b], X).
false.
?- del_mid([a,b,c], X).
X = [a, c] ;
false.
?- del_mid([a,b,c,d,e,f,g], X).
X = [a, b, c, e, f, g] ;
false.
这不是一个直截了当或更优的答案。
delete_middle1(Ls, Ls1) :- delete_middle1_(Ls, Ls, [], Ls1).
delete_middle1_([X | Cs], [_, _ | Ds], Acc, L) :-
delete_middle1_(Cs, Ds, [X | Acc], L).
delete_middle1_([_ | Cs], [_], Acc, L) :- revappend(Acc, Cs, L).
revappend([], L, L).
revappend([X | L1], L2, L3) :- revappend(L1, [X | L2], L3).
这种方法在处理链表和指针时非常有效。当一个指针到达末尾时,另一个指针会接近中间。然后我们就可以删除元素了。