[英]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
新代碼演示了“差異列表”技術:邏輯變量A
和X
組成一對,將列表從X
到A
的前綴描述為列表X
與其尾部A
之間的“差異”。
因此,我們在接口調用中顯式地獲得了終止值:
primeremover(L, Lcomp):-
primeremover(L, E, Lcomp), E = [].
通過直接調用primeremover/3
謂詞,我們可以根據需要為E
使用任何值,而不僅僅是硬編碼的[]
。
與通常的命令式“累加器”技術(使用cons ,前置)相比,用Prolog編寫代碼實際上更自然,除非我們實際上需要以相反的順序構建結果。 雖然,將元素追加到不限成員名額列表的末尾也可以視為累積。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.