1

长序列和短序列之间的距离是短序列与长序列的任何与短序列长度相同的子序列之间的最小距离。

我使用的距离是我认为的曼哈顿距离。(但这应该不重要,因为我希望能够改变距离函数)。

第一个版本展示了一个简单的实现,没有提前放弃。我生成所有相同长度的子序列,映射它们以找到它们与短序列之间的距离,然后使用 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 的回答,我现在有了这个,我认为这是一个很大的改进。谁能看到对此的进一步改进?我有一个双嵌套 -> 结构和一个!在 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].
4

1 回答 1

2

一个重要的注意事项:您应该清楚您正在谈论列表之间的曼哈顿 距离。这只能从您的代码中清楚地看出,而您的措辞很容易让读者认为您在谈论编辑 距离。

这是一个简单地遍历列表、跟踪最小值并最终产生找到的最小值的解决方案。

list_subseq_min(Ls, Subs, Min) :-
    prefix_dist(Ls, Subs, D0),
    min_sublist(Ls,Subs,D0,Min)。

absdiff(X, Y, Z):- Z #= abs(XY)。

列表距离(Ls1,Ls2,D):-
    maplist(absdiff, Ls1, Ls2, Ds),
    总和(Ds,#=,D)。

prefix_dist(Ls, Ps, D) :-
    相同长度(前,Ps),
    附加(前,_,Ls),
    列表距离(前,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/3lists_dist/3从上面,以及以下附加辅助谓词:

相同长度前缀(Ls,Ps,前):-
        相同长度(前,Ps),
        附加(前,_,Ls)。

list_subseq_min/3现在略有不同:

list_subseq_min(Ls, Subs, Min) :-
        same_length_prefix(Ls, Subs, Front),
        lists_dist(前,潜艇,D0),
        短语(min_sublist(Ls,Subs),[D0-Front],[Min-_])。

现在重点:min_sublist//2是一个DCG 非终结符,它简洁地描述了算法的主要思想:

min_sublist(Ls0, Subs) -->
        (前(Ls0,Subs)->
            { Ls0 = [_|Ls] },
            min_sublist(Ls, Subs)
        ; []
        )。

从这个描述中,很明显我们正在逐个元素地考虑列表。它使用比以前更少的(显式)参数。额外的两个参数作为pair D-Front隐式传递,它跟踪迄今为止找到的最佳距离和子序列。请注意 DCG 符号如何暴露计算的核心,并隐藏在此不相关的内容。

其余部分是不言自明的,类似于您实施的修剪。我在这个程序中强调了半上下文符号的单一使用,它可以让我们表达迄今为止发现的最优序列的任何变化。

前(Ls, Subs), [D-Front] -->
        [当前的],
        { 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) :-
        绝对差异(L,P,D2),
        D3 #= D1 + D2,
        电流 = D0-_,
        ( D3 #> D0 -> DF = 当前
        ; 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。

我留下扩展它,以便它也将找到的子序列显示为一个简单的练习。

一个重要说明:封顶只影响距离的计算;特别注意运行时间是 Θ(N×M),因为same_length_prefix/3在 front//2!我把改进这个作为一个稍微困难的练习。

于 2016-06-01T09:28:33.990 回答