簡體   English   中英

序言清單意外倒轉

[英]Prolog list accidentally reversing

我在Prolog中編寫代碼時遇到麻煩,該代碼需要一個列表,刪除所有素數,然后返回列表的其余部分。 我已經寫了一個素數檢查謂詞prime/1 ,它可以很好地工作,但是當我將程序應用到列表中時,就像我在Prolog中嘗試執行的幾乎所有操作一樣,我會按需返回沒有素數的列表,但順序相反。

primemover(L, Lcomp):-
    primeremover(L, [], Lcomp).
primeremover([], A, A).
primeremover([H | T], A, X):-
    \+ prime(H),
    primeremover(T, [H | A], X).
primeremover([H | T], A, X):-
    prime(H),
    primeremover(T, A, X).

通過查看我的代碼,我可以看到為什么列表又回來了,但是我找不到解決方法。 如果我嘗試反轉遞歸案例的首尾,將非素數移到中間列表中,它將正常工作並以正確的順序出現,但是每個值都在其自己的嵌套列表中,這比出現時更糟向后。

有沒有簡單的方法可以解決此問題?

我認為這可以解決您的問題:

primeremover([], []).
primeremover([H | T], [H | Y]):-
    \+ prime(H),
    primeremover(T, Y).
primeremover([H | T], Y):-
    prime(H),
    primeremover(T, Y).

我不確定是否遺漏了一些東西,但是我相信您正在以一種功能性語言而不是邏輯性語言來對待它。 同樣,第三個參數似乎並沒有真正為解決方案添加任何東西; 可以刪除它而不會失去功能。

我沒有您的prime謂詞,但是我用它來測試:

main :- primeremover([1,2,3,4,5], A), write(A).

prime(X) :- X = 2; X = 3; X = 5.

我使用了GNU Prolog(1.4.0)。

您無需調用輔助函數即可執行此操作,只需使用輸入變量和return即可直接執行此操作

碼:

primeremover([], []).

primeremover([H | T], Y):-
    prime(H),
    !,
    primeremover(T, Y).

primeremover([H | T], [H | Y]):-
        primeremover(T, Y).

您在primeremover/3使用第二個參數作為累加器 ,即作為堆棧的輔助參數,收集中間結果。 經常在遞歸謂詞的定義中使用這種(有用的)技術來獲得尾遞歸的好處。 該技術的一個典型示例是用於反轉列表的謂詞的定義。 天真的定義不是尾遞歸的,因此需要與列表長度成比例的空間:

reverse([], []).
reverse([Head| Tail], Reversed) :-
    reverse(Tail, Reversed0),
    append(Reversed0, [Head], Reversed).

請注意,對verse reverse/2謂詞的第二個子句中的遞歸調用不是最后一個調用。 因此,必須通過將其后面的append/3謂詞調用保存到堆棧中來掛起,直到遞歸的reverse/2謂詞終止。 該堆棧在每個遞歸調用中增長一個元素。 但是,如果遞歸調用是最后一個調用,則不需要此堆棧。 尾遞歸定義可以使用累加器進行編碼:

reverse(List, Reversed) :-
    reverse(List, [], Reversed).

reverse([], Reversed, Reversed).
reverse([Head| Tail], List, Reversed) :-
    reverse(Tail, [Head| List], Reversed).

但是,在您的特定情況下,正如Erwin和Guillermo都解釋的那樣,由於在遍歷輸入列表時可以構造輸出列表,因此無需使用累加器。 但是,可以建議通過避免使用輸入列表的當前頭是否是素數兩次的測試(對於Erwin解決方案),以及避免使用割線(對於Guillermo解決方案),可以改進他們建議的代碼。 Prolog的標准if-then-else控制構造:

prime_remover([], []).
prime_remover([Head| Tail], NonPrimes):-
    (   prime(Head) ->
        prime_remover(Tail, NonPrimes)
    ;   NonPrimes = [Head| NonPrimesTail),
        prime_remover(Tail, NonPrimesTail)
    ).

請注意,此版本(也是)尾部遞歸。

這是對代碼進行的最小編輯,以解決此問題。

%%// primeremover( +L, -Lcomp) 
primeremover(L, Lcomp):-             %// was:
    primeremover(L, [], Lcomp).
primeremover([], A, A).
primeremover([H | T], A, [H | X]):-  %// primeremover([H | T], A, X):-
    \+ prime(H),
    primeremover(T, A, X).           %//     primeremover(T, [H | A], X).
primeremover([H | T], A, X):-
    prime(H),
    primeremover(T, A, X).

添加在前面- -代替前面附加的元素添加到蓄能器和從最深調用返回它的最終值,我們附加 -添加在末端-元素在返回表,並設置其最后端的指針作為[]在最深調用。 兩者本質上都是迭代過程,並且都由Prolog本身進行編譯,即使用恆定的控制堆棧空間。

兩種變體都是尾部遞歸的,但是新的變體是尾部遞歸的模態缺點

由於變量A不再用作累加器,而是用作最終的“指針”(列表的最后一個cons單元格),因此習慣上將其稱為Z

新代碼演示了“差異列表”技術:邏輯變量AX組成一對,將列表從XA的前綴描述為列表X與其尾部A之間的“差異”。

因此,我們在接口調用中顯式地獲得了終止值:

primeremover(L, Lcomp):-        
    primeremover(L, E, Lcomp), E = [].

通過直接調用primeremover/3謂詞,我們可以根據需要為E使用任何值,而不僅僅是硬編碼的[]

與通常的命令式“累加器”技術(使用cons ,前置)相比,用Prolog編寫代碼實際上更自然,除非我們實際上需要以相反的順序構建結果。 雖然,將元素追加到不限成員名額列表的末尾也可以視為累積。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM