长序列和短序列之间的距离是短序列与长序列的任何子序列之间的最小距离,其与短序列的长度相同。
我使用的距离是我认为曼哈顿距离。 (但这应该是不重要的,因为我希望能够改变距离函数)。
第一个版本显示了一个没有提前放弃的天真实现。我生成相同长度的所有子序列,映射它们以找到它们与短序列之间的距离,然后使用aggregate / 3来查找最小值。
abs(X,Y,Z):-
Z is abs(X-Y).
seq_seq_absdis(Seq1,Seq2,Dis):-
same_length(Seq1,Seq2),
maplist(abs,Seq1,Seq2,Dislist),
sumlist(Dislist,Dis).
seq_subseq(List1,List2):-
append(List2,_,List1),
dif(List2,[]).
seq_subseq([_|T],Subseq):-
seq_subseq(T,Subseq).
smallseq_largeseq_dis(Sseq,Lseq,Dis):-
findall(Subseq, (same_length(Subseq,Sseq),seq_subseq(Lseq,Subseq)),Subseqs),
maplist(seq_seq_absdis(Sseq),Subseqs,Distances),
aggregate(min(D),member(D,Distances),Dis).
示例查询:
?-smallseq_largeseq_dis([1,2,4],[1,2,3,1,2,5],Dis).
Dis = 1
下一个版本应该更有效,因为一旦距离超过已找到的最小值,它将放弃计算长序列的子序列与短序列之间的距离。
ea_smallseq_largeseq_dis(Sseq,Lseq,Subseq,Dis):-
retractall(best(_,_)),
assert(best(initial,10000)),
findall(Subseq-Dis, ea_smallseq_largeseq_dis_h(Sseq,Lseq,10000,Subseq,Dis),Pairs),
append(_,[Subseq-Dis|[]],Pairs).
ea_smallseq_largeseq_dis_h(Sseq,Lseq,BestSofar1,Subseq,Dis):-
same_length(Sseq,Subseq),
seq_subseq(Lseq,Subseq),
best(_,BestSofar2),
( ( BestSofar2 < BestSofar1) ->
accumulate_dis(Sseq,Subseq,BestSofar2,Dis),
retractall(best(_,_)),
assert(best(Subseq,Dis))
;(
accumulate_dis(Sseq,Subseq,BestSofar1,Dis),
retractall(best(_,_)),
assert(best(Subseq,Dis))
)
).
accumulate_dis(Seq1,Seq2,Best,Dis):-
accumulate_dis(Seq1,Seq2,Best,Dis,0).
accumulate_dis([],[],_Best,Dis,Dis).
accumulate_dis(Seq1,Seq2,Best,Dis,Ac):-
Seq1=[H1|T1],
Seq2=[H2|T2],
abs(H1,H2,Dis1),
Ac1 is Dis1 + Ac,
Ac1 <Best,
accumulate_dis(T1,T2,Best,Dis,Ac1).
查询:
?-ea_smallseq_largeseq_dis([1,2,3],[1,2,4,5,6,7,8,1,2,3],Subseq,Dis).
Dis = 0,
Subseq = [1, 2, 3]
但是在这里我使用了assert和retract所以我想要一个版本执行相同的算法,但没有这些。我想我应该能够用带半色式记号的dcg做到这一点,但发现很难掌握......我如何跟踪我通过回溯产生的子序列,同时记录最小距离的“状态”发现到目前为止?
我遇到的问题.. seq_subseq / 2通过反向跟踪生成子序列。 测试的第一个子项需要设置为最小距离。 然后我想循环,所以回溯生成另一个序列。但要追溯,我必须失败。但是到目前为止我无法将最小距离带回来检查下一个序列。
如果我不想使用回溯,我想我需要定义一个状态转换谓词来按顺序生成子序列。
目前
? seq_subseq([1,2,3,4],X).
X = [1]
X = [1, 2]
X = [1, 2, 3]
X = [1, 2, 3, 4]
X = [2]
X = [2, 3]
X = [2, 3, 4]
X = [3]
X = [3, 4]
X = [4]
所以我认为我需要定义一个关系:
subseq0_seq_subseq1(Subseq0,Seq,Subseq1)
这就像:
?-subseq0_seq_subseq1([1,2,3,4],[1,2,3,4],Subseq1).
Subseq1 = [2].
和
?-subseq0_seq_subseq1([1,2,3],[1,2,3,4],Subseq1).
Subseq1 = [1,2,3,4].
但我需要以有效的方式做到这一点。
更新 - 感谢Mat的答案,我现在有了这个,这是一个很好的改进,我认为。任何人都可以看到任何进一步的改进吗?我有一个双嵌套 - &gt;结构和一个!在accumulate_dis / 4定义中,这两个看起来都很难看。我还让它返回长序列的子序列,该序列是距离短序列最短的距离。
它需要使用非整数,所以我认为clpfd是不合适的。
abs(X,Y,Z):-
Z is abs(X-Y).
list_subseq_min(Ls, Subs, Min,BestSeq1) :-
prefix_dist(Ls, Subs, 1000, Front, D0),
BestSeq0=Front,
min_sublist(Ls, Subs,BestSeq0,BestSeq1, D0, Min).
prefix_dist(Ls, Ps, Best,Front,D) :-
same_length(Front, Ps),
append(Front, _, Ls),
accumulate_dis(Front, Ps, Best, D).
min_sublist(Ls0, Subs, BestSeq0,BestSeq2, D0, D) :-
( prefix_dist(Ls0, Subs, D0, Front,D1) ->
min_list([D0,D1],D2),
Ls0 = [_|Ls],
( D0 < D1 ->
BestSeq1 =BestSeq0
;
BestSeq1 =Front
),
min_sublist(Ls, Subs, BestSeq1,BestSeq2, D2, D)
; D = D0,BestSeq0 =BestSeq2
).
accumulate_dis(Seq1,Seq2,Best,Dis):-
accumulate_dis(Seq1,Seq2,Best,Dis,0),!.
accumulate_dis([],[],_Best,Dis,Dis).
accumulate_dis(Seq1,Seq2,Best,Dis,Ac):-
Seq1=[H1|T1],
Seq2=[H2|T2],
abs(H1,H2,Dis1),
Ac1 is Dis1 + Ac,
Ac1 <Best,
accumulate_dis(T1,T2,Best,Dis,Ac1).
accumulate_dis(Seq1,Seq2,Best,Dis):-Dis is Best+1.
查询:
?- list_subseq_min([2.1,3.4,4,1.1,2,4,10,12,15],[1,2,3],D,B).
D = 1.1,
B = [1.1, 2, 4].
答案 0 :(得分:2)
一个重要的注意事项:您应该清楚地知道您正在谈论列表之间的 Manhatten 距离。这只能从您的代码中清楚看出,而您的措辞很容易让读者假设您正在谈论编辑距离。
这是一个简单地遍历列表,跟踪最小值并最终产生最小值的解决方案。
list_subseq_min(Ls, Subs, Min) :- prefix_dist(Ls, Subs, D0), min_sublist(Ls, Subs, D0, Min). absdiff(X, Y, Z):- Z #= abs(X-Y). lists_dist(Ls1, Ls2, D) :- maplist(absdiff, Ls1, Ls2, Ds), sum(Ds, #=, D). prefix_dist(Ls, Ps, D) :- same_length(Front, Ps), append(Front, _, Ls), lists_dist(Front, Ps, D). min_sublist(Ls0, Subs, D0, D) :- ( prefix_dist(Ls0, Subs, D1) -> D2 #= min(D0,D1), Ls0 = [_|Ls], min_sublist(Ls, Subs, D2, D) ; D #= D0 ).
示例查询及其结果:
?- list_subseq_min([1,2,3,1,2,5], [1,2,4], D). D = 1.
这是非常直截了当的,并且由于簿记仅限于一个谓词,因此使用半分文本符号并不能真正得到回报。一般情况下使用半音文法符号和DCG特别有用 - 当所描述的内容跨越不同的规则时,它们之间的通信会更难。
正在运行的时间位于 O(N×M)。
现在我要留下一点作为练习:如果先前已经超过了修剪,则将此解决方案修改为修剪。以纯方式执行此操作,或者至少尽可能纯粹:assertz/1
和朋友肯定不可能,因为它们会阻止您测试这些谓词处于隔离状态。传递参数并逐步增加距离!这可能会帮助您改善平均运行时间,但当然不是最糟糕的情况。
对于这种在不同条款之间传递的状态,半文字符号最终可能会变得有用。
编辑:很好,您已经实施了修剪解决方案。我现在也将展示我的。我将重用上面的辅助谓词absdiff/3
和lists_dist/3
以及以下附加辅助谓词:
same_length_prefix(Ls, Ps, Front) :- same_length(Front, Ps), append(Front, _, Ls).
list_subseq_min/3
现在略有不同:
list_subseq_min(Ls, Subs, Min) :- same_length_prefix(Ls, Subs, Front), lists_dist(Front, Subs, D0), phrase(min_sublist(Ls, Subs), [D0-Front], [Min-_]).
现在要点:min_sublist//2
是一个 DCG非终结,它简明扼要地描述了算法的主要思想:
min_sublist(Ls0, Subs) --> ( front(Ls0, Subs) -> { Ls0 = [_|Ls] }, min_sublist(Ls, Subs) ; [] ).
从这个描述中,很明显我们正在考虑列表元素。它使用比以前更少的(显式)参数。另外两个参数隐式传递为对 D-Front
,它跟踪到目前为止找到的最佳距离和子序列。注意DCG表示法如何暴露计算的核心,并隐藏在这个地方不相关的内容。
其余的都是不言自明的,类似于你实施的修剪。我强调在这个程序中单独使用semicontext表示法,它可以让我们表达迄今为止发现的最佳序列的任何变化。
front(Ls, Subs), [D-Front] --> [Current], { same_length_prefix(Ls, Subs, Front1), capped_dist(Front1, Subs, Current, 0-Front1, D-Front) }. capped_dist([], [], _, DF, DF). capped_dist([L|Ls], [P|Ps], Current, D1-Front1, DF) :- absdiff(L, P, D2), D3 #= D1 + D2, Current = D0-_, ( D3 #> D0 -> DF = Current ; capped_dist(Ls, Ps, Current, D3-Front1, DF) ).
我不能让自己接受当代浮点数的肮脏和原始性,所以我保留了整数运算并简单地乘以你显示的所有数字,使它们变成整数:
?- list_subseq_min([21,34,40,11,20,40,100,120,150], [10,20,30], D). D = 11.
我对此进行了扩展,以便将已发现的子序列显示为一个简单的练习。
一个重要的注意事项:封顶仅影响距离的计算;请注意,由于same_length_prefix/3
中front//2
的使用方式,运行时间为Θ(N×M)!我将这项改进作为一项稍微艰难的练习。