这个问题被问到但没有答案:here。我阅读了这些评论并尝试以两种方式实施,但还有更多我不理解的问题。
我首先尝试了不保持原始顺序的简单方法:
list_repeated(L, Ds) :-
msort(L, S),
sorted_repeated(S, Ds).
sorted_repeated([], []).
sorted_repeated([X|Xs], Ds) :-
first(Xs, X, Ds).
first([], _, []).
first([X|Xs], X, [X|Ds]) :-
more(Xs, X, Ds).
first([X|Xs], Y, Ds) :-
dif(X, Y),
first(Xs, X, Ds).
more([], _, []).
more([X|Xs], X, Ds) :-
more(Xs, X, Ds).
more([X|Xs], Y, Ds) :-
dif(X, Y),
first(Xs, X, Ds).
对列表进行排序而不删除重复项后,使用first
和more
我将元素添加到第二个参数(如果它至少出现两次)并跳过该元素的所有连续副本。
这不能正常工作,因为如果我有:
?- list_duplicates([b,a,a,a,b,b], Ds).
我得到答案[a,b]
而不是[b,a]
,而且在答案之后我得到; false
。
我也尝试过另一种方式,但这不起作用,因为累加器是不可变的?
list_duplicates(L, Ds) :-
ld_acc(L, [], Ds).
ld_acc([], _, []).
ld_acc([X|Xs], Acc, Ds) :-
( memberchk(X, Acc)
-> Ds = [X|Ds0],
ld_acc(Xs, Acc, Ds0)
; Acc1 = [X|Acc],
ld_acc(Xs, Acc1, Ds)
).
这不起作用,因为当我检查一个元素是累加器的成员时,我只删除每个元素的一个出现位置:如果我在第一个参数中有三次相同的元素,那么我剩下两个。如果我可以更改累加器中的元素,那么我可以在它上面放一个计数器?在第一个版本中,我使用了不同的状态first
和more
,但是在这里我必须将状态附加到累加器的元素,这可能吗?
答案 0 :(得分:2)
在Prolog中编程时,一个主要的吸引力是我们从纯粹的关系中享受的普遍性。
这使我们可以在多个方向中使用我们的代码,并在我们的程序和答案中声明声明。
如果您保持程序纯,您可以享受这些好处。
与描述列表时一样,也请考虑使用 DCG表示法。有关详细信息,请参阅dcg。
例如,要以纯粹的方式描述重复项的列表,请考虑:
list_duplicates([]) --> []. list_duplicates([L|Ls]) --> list_duplicates_(Ls, L), list_duplicates(Ls). list_duplicates_([], _) --> []. list_duplicates_([L0|Ls], L) --> if_(L0=L, [L], []), list_duplicates_(Ls, L).
这使用if_//3
来保留一般性 和 确定性(如果适用)。
以下是一些示例查询和答案。我们从简单的地面案例开始:
?- phrase(list_duplicates([a,b,c]), Ds). Ds = []. ?- phrase(list_duplicates([a,b,a]), Ds). Ds = [a].
即使最不纯的版本也能正确处理这些情况。所以,更有趣的是:
?- phrase(list_duplicates([a,b,X]), Ds). X = a, Ds = [a] ; X = b, Ds = [b] ; Ds = [], dif(X, b), dif(X, a).
相当不错,不是吗?最后一部分说:Ds = []
是一个解决方案,如果 X
与b
和a
不同。请注意,纯关系dif/2
会自动出现在这些剩余目标中,并保留关系的一般性。
以下是两个变量的示例:
?- phrase(list_duplicates([X,Y]), Ds). X = Y, Ds = [Y] ; Ds = [], dif(Y, X).
最后,考虑使用迭代深化对任意长度的列表进行枚举答案:
?- length(Ls, _), phrase(list_duplicates(Ls), Ds). Ls = Ds, Ds = [] ; Ls = [_136], Ds = [] ; Ls = [_136, _136], Ds = [_136] ; Ls = [_156, _162], Ds = [], dif(_162, _156) ; Ls = Ds, Ds = [_42, _42, _42] ; Ls = [_174, _174, _186], Ds = [_174], dif(_186, _174) .
这是一个处理同一元素的任意多次出现的版本,只要(并且仅当)元素发生时,完全出现 至少两次:
list_duplicates(Ls, Ds) :- phrase(list_duplicates(Ls, []), Ds). list_duplicates([], _) --> []. list_duplicates([L|Ls], Ds0) --> list_duplicates_(Ls, L, Ds0, Ds), list_duplicates(Ls, Ds). list_duplicates_([], _, Ds, Ds) --> []. list_duplicates_([L0|Ls], L, Ds0, Ds) --> if_(L0=L, new_duplicate(L0, Ds0, Ds1), {Ds0 = Ds1}), list_duplicates_(Ls, L, Ds1, Ds). new_duplicate(E, Ds0, Ds) --> new_duplicate_(Ds0, E, Ds0, Ds). new_duplicate_([], E, Ds0, [E|Ds0]) --> [E]. new_duplicate_([L|Ls], E, Ds0, Ds) --> if_(L=E, { Ds0 = Ds }, new_duplicate_(Ls, E, Ds0, Ds)).
评论中@fatalize显示的查询现在产生:
?- list_duplicates([a,a,a], Ls). Ls = [a].
其他示例产生相同的结果。例如:
?- list_duplicates([a,b,c], Ds). Ds = []. ?- list_duplicates([a,b,a], Ds). Ds = [a]. ?- list_duplicates([a,b,X], Ds). X = a, Ds = [a] ; X = b, Ds = [b] ; Ds = [], dif(X, b), dif(X, a). ?- list_duplicates([X,Y], Ds). X = Y, Ds = [Y] ; Ds = [], dif(Y, X).
我将案例?- list_duplicates(Ls, Ls).
作为练习。
理想情况下,我们希望能够在所有方向中使用关系。
例如,我们的程序应该能够回答如下问题:
如果的重复项是
[a,b]
,那么列表的内容是什么?
使用上面显示的版本,我们得到:
?- list_duplicates(Ls, [a,b]). nontermination
幸运的是,一个非常简单的变化允许回答这样的问题!
其中一个变化就是简单地写:
list_duplicates(Ls, Ds) :- length(Ls, _), phrase(list_duplicates(Ls, []), Ds).
这显然是声明性的可接受的,因为Ls
必须是列表。 操作,这有助于我们以公平的方式枚举列表。
我们现在得到:
?- list_duplicates(Ls, [a,b]). Ls = [a, a, b, b] ; Ls = [a, b, a, b] ; Ls = [a, b, b, a] ; Ls = [a, a, a, b, b] ; Ls = [a, a, b, a, b] ; Ls = [a, a, b, b, a] ; Ls = [a, a, b, b, b] ; Ls = [a, a, b, b, _4632], dif(_4632, b), dif(_4632, a) ; etc.
这是一个更简单的情况,只使用一个元素:
?- list_duplicates(Ls, [a]). Ls = [a, a] ; Ls = [a, a, a] ; Ls = [a, a, _3818], dif(_3818, a) ; Ls = [a, _3870, a], dif(_3870, a) ; Ls = [_4058, a, a], dif(a, _4058), dif(a, _4058) ; Ls = [a, a, a, a] ; etc.
也许更有趣:
列表没有重复项是什么样的?
我们的计划回答:
?- list_duplicates(Ls, []). Ls = [] ; Ls = [_3240] ; Ls = [_3758, _3764], dif(_3764, _3758) ; Ls = [_4164, _4170, _4176], dif(_4176, _4164), dif(_4176, _4170), dif(_4170, _4164) .
因此,列表的所有元素都是不同的的特殊情况自然存在于我们已经实现的更一般关系的特例中。
我们可以将这种关系用于生成答案(如上所示),也可以用于测试 列表是否包含不同的元素:
?- list_duplicates([a,b,c], []). true. ?- list_duplicates([b,b], []). false.
不幸的是,以下甚至更一般的查询仍会产生:
?- list_duplicates([b,b|_], []). nontermination
从好的方面来说,如果列表的长度已修复,我们会遇到这种情况:
?- length(Ls, L), maplist(=(b), Ls), ( true ; list_duplicates(Ls, []) ). Ls = [], L = 0 ; Ls = [], L = 0 ; Ls = [b], L = 1 ; Ls = [b], L = 1 ; Ls = [b, b], L = 2 ; Ls = [b, b, b], L = 3 ; Ls = [b, b, b, b], L = 4 .
这表明该程序在这种情况下确实终止。请注意,答案当然是太笼统。
众所周知,在高性能计算领域,只要您的程序足够快,其正确性几乎不值得考虑。
因此,关键问题当然是:我们如何才能加快速度?
我离开这是一个非常容易的练习。在特定情况下使这种速度更快的一种方法是首先检查给定列表是否充分实例化。在这种情况下,您可以应用 ad hoc 解决方案,该解决方案在更一般的情况下非常失败,但具有快速的极大好处!
答案 1 :(得分:0)
据我所知,你使用累加器处于正确的轨道上,但是这个实现绝对可以按你的需要工作(假设你想按照它们首次出现在列表中的顺序重复)。
list_duplicates(Input,Output)
仅用于包装和初始化累加器。
list_duplicates(Accumulator,[],Accumulator)
将累加器与输出结合起来。
list_duplicates(Accumulator,[H|T],Output)
说"如果输入列表的头部(H
)位于列表的其余部分(T
),并且不在{{1已经把它放在Accumulator
的末尾(使用Accumulator
),然后递归到列表的尾部"。
append
(如果头部不重复,或者已经在list_duplicates(Accumulator,[_|T],Output)
中,我们只能在列表的尾部进行递归)。
Accumulator
您还可以使用list_duplicates(Input,Output) :-
once(list_duplicates([],Input,Output)).
list_duplicates(Accumulator,[],Accumulator).
list_duplicates(Accumulator,[H|T],Output) :-
member(H,T),
\+member(H,Accumulator),
append(Accumulator,[H],NewAccumulator),
list_duplicates(NewAccumulator,T,Output).
list_duplicates(Accumulator,[_|T],Output) :-
list_duplicates(Accumulator,T,Output).
在list_duplicates(Accumulator,[H|T],Output)
中递归并在包装中反转,如下所示:
list_duplicates([H|Accumulator],T,Output)
包装器中的list_duplicates(Input,Output) :-
once(list_duplicates([],Input,ReverseOutput)),
reverse(ReverseOutput,Output).
list_duplicates(Accumulator,[],Accumulator).
list_duplicates(Accumulator,[H|T],Output) :-
member(H,T),
\+member(H,Accumulator),
list_duplicates([H|Accumulator],T,Output).
list_duplicates(Accumulator,[_|T],Output) :-
list_duplicates(Accumulator,T,Output).
调用会阻止once
输出(或者在这种情况下,由于第二条规则缺少防护而导致部分重复列表)。