【问题标题】:Swap last two elements of a list in Prolog在 Prolog 中交换列表的最后两个元素
【发布时间】:2021-12-08 20:36:39
【问题描述】:

我一直在尝试编写一个程序来比较两个相同的列表,除了列表 2 的最后两个元素顺序相反,即 [4,5,6] 和 [4,6,5],并返回最后两个被交换的元素。

例如:

SwapLastTwo([4, 5, 6] , [ 4, 6, 5], X).

应该返回

X = [6, 5]

到目前为止,我的代码如下所示:

lastTwoReversed([Z,A|T],[_,Y,X]) :-reverse([Z,A|T],[Y,X|_]).

到目前为止,我的谓词只接受两个参数并检查是否相同,但列表 2 的最后两个元素顺序相反,如果满足条件,则返回 true

我不知道如何修改我的谓词以将 X 作为其第三个参数并使其返回交换的元素。

【问题讨论】:

    标签: list prolog


    【解决方案1】:

    试试这个:

    lastTwoReversed(L1, L2, [X1,X2]) :-
        reverse(L1, [X1,X2|Rest]),
        reverse(L2, [X2,X1|Rest]).
    

    请注意,通过在两个子目标中使用变量 Rest,您可以确定列表必须相同,除了最后两项(已交换)。

    例子:

    ?- lastTwoReversed([1,2,3,4,5,6], [1,2,3,4,6,5], R).
    R = [6, 5].
    
    ?- lastTwoReversed([1,2,3,4,5,6], [1,2,3,6,5], R).
    false.
    
    ?- lastTwoReversed([1,2,3,4,5,6], [1,2,3,4,5,6], R).
    false.
    

    【讨论】:

    • 也许这一切都是为了成为统治精英......哈哈dev.to/facundocorradini/why-i-don-t-use-stack-overflow-1f0l
    • 只是为了把它放在上下文中,我上面的评论是对一个用户的回答,他问我为什么有人对一个好的解决方案投反对票(在我的解决方案被投反对票之后)。提出的问题已被删除。
    【解决方案2】:

    使用第一个参数索引,并通过删除不必要的 reverse() 和 same_length() 来提高性能:

    swap_last_2([Elem1, Elem2|Tail], SwappedLst, Last2) :-
        % same_length would prevent an unwanted choicepoint on: swap_last_2(L, [a,b], SL).
        %same_length([Elem1, Elem2|Tail], SwappedLst),
        swap_last_2_(Tail, Elem1, Elem2, SwappedLst, Last2).
    
    
    % Swap the last 2 elements
    swap_last_2_([], Elem1, Elem2, [Elem2, Elem1], [Elem2, Elem1]).
    
    swap_last_2_([Head|Tail], Elem1, Elem2, [Elem1|SwappedLst], Last2) :-
        % Move the elements along
        swap_last_2_(Tail, Elem2, Head, SwappedLst, Last2).
    

    在 swi-prolog 中的结果:

    ?- swap_last_2([4, 5, 6], [4, 6, 5], X).
    X = [6,5].
    

    性能对比(此方法最快):

    ?- cmp(1000, 1000).
    % 7,011,001 inferences, 0.503 CPU in 0.497 seconds (101% CPU, 13941510 Lips)
    % 2,001,001 inferences, 0.282 CPU in 0.278 seconds (101% CPU, 7098617 Lips)
    % 2,007,001 inferences, 0.205 CPU in 0.203 seconds (101% CPU, 9782721 Lips)
    % 1,002,001 inferences, 0.063 CPU in 0.063 seconds (101% CPU, 15819840 Lips)
    

    【讨论】:

    • 我赞成你回答,但我认为对same_length/2 的调用并不是真正必要的:如果swap_last_2_/2 以第一个列表为空而第二个列表正好有两个项目结束,它表示作为swap_last_2/2 输入的列表确实具有相同的长度。通过消除此调用,您将拥有更快的代码 [但它仍然具有 O(n) 的时间复杂度]。
    • @slago 是的,我已经注释掉了 same_length/2,以获得更高的性能。在昨天的版本中,需要防止无限循环,但现在只是防止不需要的选择点。
    【解决方案3】:

    我认为@CapelliC [已删除] 的比较具有误导性。 OP 要求一个谓词比较两个列表 并检查它们是否相同,除了可能出现交换的最后两个元素(这与 只是 交换单个列表的最后两个元素)。因此,更公平的比较应该调用要与两个实例化列表作为输入进行比较的谓词。

    我的建议是修改perf_belt/3如下:

    /*  File:    last2swap_perf.pl
        Author:  Carlo
        Created: Dec 12 2021
        Purpose: compare answers to https://stackoverflow.com/q/70281408/874024
    */
    
    :- module(last2swap_perf, [cmp/2]).
    
    :- meta_predicate perf_belt(+,+,3).
    
    % CapelliC's solutions
    
    % append/2 based
    
    last2swap_app2(A,B,[U,V]):-
        append([C,[U,V]],A),
        append([C,[V,U]],B).
    
    % append/3 based
    
    last2swap_app3(A,B,[U,V]):-
        append(C,[U,V],A),
        append(C,[V,U],B).
    
    % slago' solution
    
    lastTwoReversed(L1, L2, [X1,X2]) :-
        reverse(L1, [X1,X2|Rest]),
        reverse(L2, [X2,X1|Rest]).
    
    % brebs' answer
    
    swap_last_2([Elem1, Elem2|Tail], SwappedLst, Last2) :-
        swap_last_2_(Tail, Elem1, Elem2, [], RevSwappedLst, Last2),
        reverse(RevSwappedLst, SwappedLst).
    
    % This swaps the last 2 elements
    
    swap_last_2_([], Elem1, Elem2, SoFar, [Elem1, Elem2|SoFar], [Elem2, Elem1]).
    
    swap_last_2_([Head|Tail], Elem1, Elem2, SoFar, SwappedLst, Last2) :-
        % Move the elements along
        swap_last_2_(Tail, Elem2, Head, [Elem1|SoFar], SwappedLst, Last2).
    
    %!  %%% minimal perf utility
    
    perf_belt(NRep, LList, Pred) :-       % <== NEW VERSION PROPOSED
        make_lists(LList, List1, List2), 
        time( forall(between(1, NRep, _), % <== TWO INSTANTIATED LISTS AS INPUT 
                     call(Pred, List1, List2, _)) ).
    
    make_lists(N, List1, List2) :-
        N1 is N - 1,
        N2 is N - 2,
        numlist(1, N2, Prefix),
        append(Prefix, [N1, N], List1),
        append(Prefix, [N, N1], List2).
    
    cmp(NRep,LList) :-
        perf_belt(NRep,LList,last2swap_app2),
        perf_belt(NRep,LList,last2swap_app3),
        perf_belt(NRep,LList,lastTwoReversed),
        perf_belt(NRep,LList,swap_last_2).
    

    结果更公平在 SWI-Prolog 8.4.1 版本上运行,我们可以看到双反转还不错。

    ?- cmp(100, 100).
    % 71,101 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips)
    % 20,101 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips)
    % 20,701 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips)
    % 20,401 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips)
    true.
    
    ?- cmp(100, 1000).
    % 701,101 inferences, 0.047 CPU in 0.047 seconds (100% CPU, 14956821 Lips)
    % 200,101 inferences, 0.031 CPU in 0.031 seconds (100% CPU, 6403232 Lips)
    % 200,701 inferences, 0.016 CPU in 0.016 seconds (100% CPU, 12844864 Lips)
    % 200,401 inferences, 0.016 CPU in 0.016 seconds (100% CPU, 12825664 Lips)
    true.
    
    ?- cmp(1000, 1000).
    % 7,011,001 inferences, 0.438 CPU in 0.437 seconds (100% CPU, 16025145 Lips)
    % 2,001,001 inferences, 0.219 CPU in 0.219 seconds (100% CPU, 9147433 Lips)
    % 2,007,001 inferences, 0.141 CPU in 0.141 seconds (100% CPU, 14272007 Lips)
    % 2,004,001 inferences, 0.141 CPU in 0.141 seconds (100% CPU, 14250674 Lips)
    true.
    

    【讨论】:

    • 你说得对,我的错使用未经实例化的论点会被回溯。抱歉打扰了。
    • @CapelliC 没问题!的确,我必须感谢你!您的回答有助于澄清问题,因为我的回答受到了那些认为效率太低的人的批评。
    • 我已经通过删除它的一个 reverse() 来改进我的代码,所以它现在明显更快。
    • @brebs 不错!每次调用reverse/2(完整列表遍历)的时间复杂度为 O(n)。因此,通过消除一个这样的调用(并像您所做的那样并行遍历两个列表),实际上可以预期这种效率增益。但是请注意,算法的时间复杂度保持不变,O(n)。这个新版本的优势仅在于一个常数因子(似乎小于 2)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-09
    • 1970-01-01
    • 2015-10-16
    • 2020-11-23
    • 2011-09-02
    相关资源
    最近更新 更多