【问题标题】:Deleting the middle element of a list删除列表的中间元素
【发布时间】:2020-11-05 07:13:55
【问题描述】:

我想编写一个 Prolog 程序,将奇数列表中的中间元素删除到另一个列表中。

例如,如果我们给出:delete_mid([1,2,3,4,5],L),那么它将产生:L = [1,2,4,5] 作为答案。

【问题讨论】:

  • 承诺:将为此提供赏金,以获得最佳终止 ISO Prolog 定义(即,无协同程序),除了 OP 的用例还用于 ?- delete_middle(Ls, []). 和 @ 之外,至少终止(普遍) 987654324@

标签: list prolog


【解决方案1】:

到目前为止,没有一个答案采用最明显的方法,这让我感到惊讶和悲伤。你肯定在学校听说过它(我怀疑这可能是 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.

似乎有效。现在,我可以尝试添加它保留当前丢弃的部分。


我很忙,所以这需要一段时间。与此同时,the answer by 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 看到了它(或者已经知道它),我再次感到惊讶和有点难过。


Und täglich grüßt das Murmeltier

【讨论】:

  • find_mid/2的第一个参数真的是find吗?
  • 我只是发布它。 :-)
  • @false 不,不是,显然应该是list_middle/2
  • 啊,我一直在寻找这样的解决方案。 (此外,学校是老布什总统的时候,而这个不在 Dijkstra 的“编程学科”中)
  • @DavidTonhofer 我怀疑它当时已经在教科书中了。见here
【解决方案2】:

现在我也想加入(回答第 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) 并认为:
这是天才。但是等等……这可以改进。


进一步解释该算法:谓词可用于生成一个列表,该列表类似于没有中间元素的(奇数编号)输入列表。或者如果这个属性成立,它可以测试两个列表。

“天才”部分是不需要计算长度或有计数器,因为它实际上使用输入列表的副本作为计数器。原理解释herehere

第 1 行和第 2 行创建了对同一个列表的两个引用。计数器列表称为快速,元素列表称为慢。为什么?因为在每个递归步骤中,您从快速列表 ([_,_|Fast]) 中删除两个元素,但从元素列表 ([H|Slow]) 中只删除一个元素。当快速列表中只剩下一个元素时([_]),您从慢速列表中点击中间元素。因此,将其移除并将其余部分放在返回轨道上。在进行递归时,将您从慢速列表中删除的所有元素 (H) 作为返回列表的头部,然后递归填充其余部分。

等等你得到了元素列表的精确副本,只是缺少中间元素。

【讨论】:

  • 迄今为止最好的解决方案。
  • 我不想这么快给出一个可行的解决方案。我以为没有必要。你是唯一接受它的人。这个想法的功劳归于no one in particular。你的最终解决方案可能比我想出的更好。
  • 对,除了不是副本,而是原始列表本身,其中有两个指针,其中一个的速度是另一个的两倍。并且没有通过这个递归向后进行,因为它是tail递归。规则头部的前置[H|Ret] 在递归调用之前完成,它填补了漏洞Ret。见1
  • 哦,不,整个编辑,为什么?这只是一个小问题......
  • 我冒昧地恢复了它,做了一些小的改动...... :)
【解决方案3】:

我认为您需要 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.

【讨论】:

  • 还有偶数个元素的列表呢?
  • @Raubsauger :问题仅限于odd-numbered list。在偶数长度的列表中,您将什么定义为中间元素?要么忽略删除,要么删除其中两个。
  • @DavidTonhofer 如果长度尚未被限制为奇数,则将 divmod 中的余数设置为 1 即可解决问题。
  • @false:重新排序子句。先放 nth0 谓词然后它终止。
  • @false 这就是 Prolog 的问题——你永远不知道你的谓词会抛出什么,但你也不愿意添加一些 must_be 来限制用于实际已知的工作案例.
【解决方案4】:

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 的规范自动变形为一个有效的解决方案。

【讨论】:

  • 将两个 length/2 移动到 same__length/2 作为第一个目标将时间提高了 5 倍,但 @false 发布的终止问题无法通过这种方式解决。放弃...
  • 怎么样:middle_less(InList,MiddlelessList,Middle) :- list_onelesslong(InList, MiddlelessList), <same as before>list_onelesslong([_], []). list_onelesslong([_X | Xs], [_Y | Ys]) :- list_onelesslong(Xs, Ys).。这允许谓词生成答案。
【解决方案5】:
delete_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.

【讨论】:

  • (您可以打开Left 列表并将其尾部与Right 统一,而不需要append/3。)
  • 对于你的最后一个例子,试试:?- delete_middle(X, Y), numbervars(X-Y). 它更容易阅读。
  • 我想知道什么时候会看到差异列表的答案。 :)
【解决方案6】:

新版本,现在更具确定性:

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

【讨论】:

  • @false 也许这已经足够确定了... :-)
  • 显然这是进步
【解决方案7】:

基于 TA_intern 提出的 find 中间算法:

%! 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
?- 

【讨论】:

    【解决方案8】:

    此方案在“取出”中间项后保留一个计数器以将尾部统一为适当长度的列表:

    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.
    

    【讨论】:

    • 我知道这是一个玩具问题......无论哪种方式,尝试在更长的列表(超过 10000 个元素)上为您的解决方案计时然后将其与 Raubsauger 的解决方案进行比较。
    • 没错!,Raubsager算法比这个好很多
    • @TA_intern 添加了一个小变化,它得到了可比较的时间
    【解决方案9】:

    利用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.
    

    【讨论】:

      【解决方案10】:

      不是直截了当,也不是更理想的答案。

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

      这种方法在处理链表和指针时效果很好。当一个指针位于末尾时,另一个指针将靠近中间。然后我们就可以删除元素了。

      【讨论】:

        【解决方案11】:

        这是我的序言解决方案:

        delMidNumber(K,L):-
             len(K,N),
             (N mod 2 =:= 1 ->  
              N1 is N//2,
              nth0(N1,K,E1),
              del(E1,K,L); write('List has even length'),!).
        
        len([],0).
        len([H|T],N):-
            len(T,N1),
            N is N1+1.
        
        del(E,[E|T],T).
        del(E,[H|T],[H|T1]):-
            del(E,T,T1).
        

        谓词 delMidNumber 有两个参数 1-奇数列表。 2- 将形成的新列表。谓词首先计算列表的长度,然后检查列表的长度是否为奇数,然后将长度除以 2。然后在 nth0 中使用结果来为我们提供该索引上的元素。然后我们简单地使用 del 谓词来删除那个中间数字元素。如果长度为偶数,则写入长度为偶数的消息,然后剪切(停止)。

        ?-delMidNumber([1,3,2,4,5],L).
        L = [1, 3, 4, 5]
        
        ?-delMidNumber([1,3,4,5],L).
        List has even length
        

        【讨论】:

        • 我喜欢你努力学习序言。你能在你的测试用例中添加另一个测试用例吗?试试delMidNumber([2,3,2,4,5],L).,你可能会发现一些需要改进的地方。
        • @Raubsauger 是的,我试过了。它返回 L=[3,2,4,5] 和最终预期答案 L=[2,3,4,5]。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-08-03
        • 2013-11-25
        • 1970-01-01
        • 2019-07-21
        • 2017-03-05
        相关资源
        最近更新 更多