计算列表是另一个列表的子列表的时间

时间:2014-01-28 11:08:15

标签: algorithm list prolog sublist

我进行了Prolog练习,要求我计算一个列表L2作为L1中的子列表显示的时间。

我需要做的是从用另一种语言编写的代码开始编写Prolog代码(我们在大学使用的某种教学语言)。

所以我写的算法是这种语言,一切顺利,但Prolog版本失败了,我没有说明原因。

这个想法非常简单:

  • 获取L2的长度并将其称为SizeL2
  • 从L1的头部开始,获取大小为SizeL2的每个子列表。
  • 检查是否为subList(L1, SizeL2)==L2,如果为真,则添加1
  • 到达L1时,我们会返回count

这意味着:

  

L1 = [1,2,3,4,5,2,3,2,3,4],L2 = [2,3,4],SizeL2 = 3,count = 0

     

步骤1 - > [1,2,3]

     

步骤2 - > [2,3,4] - 是的,数+ 1

     

步骤3 - > [3,4,5]

     

步骤4 - > [4,5,2]

     

步骤5 - > [5,2,3]

     

步骤6 - > [2,3,2]

     

步骤7 - > [3,2,3]

     

步骤8 - > [2,3,4] - 是的,数+ 1

     

步骤9 - > L1完成,返回2

现在,我将其翻译为Prolog

请注意,我必须避免任何类型的系统谓词!

%% we get the size of a list
size([], 0):-!.
size([H|T], R):- size(T,R1), R is R1+1.

%% we get the sublist of the given size
subList(_, 0, []):-!. %% if size is 0, we return an empty list
subList([], _, []):-!. %% if the list is empty, we're done
subList([H|T], Size, [H|Tr]):- Size1 is Size-1, 
                               subList(T, Size1, Tr).

%% we count how many times L2 is sublist of L1  
countSublist([], _, 0):-!. %% if L1 is empty we return 0
countSublist(L1, L2, 0):- size(L1, S1),
                         size(L2, S2),
                         S1 < S2. %% if L1 is shorter than L2, we return 0

countSublist([H|T], L2, R):- size(L2, SizeL2), %% we need L2's size
                             subList([H|T], SizeL2, SubL1), %% we get L1's sublist
                             countSublist(T, L2, R),
                             SubL1 = L2,
                             R1 is R+1. %% we do R+1 only if L2=Sublist of L1

我们语言的翻译很简单,我确信我做得很好,但我仍然不明白为什么Prolog版本不起作用。

我认为问题应该在最后一个谓词(countSublist([H|T], L2, R))中,因为sizesubList工作正常。

有什么想法吗?

7 个答案:

答案 0 :(得分:2)

如果你可以避免计算尺寸/ 2等,你的解决方案会更简单。

BTW请注意,您在您的解决方案中避免使用系统谓词,因为它是/ 2它是一个'extralogical'系统谓词,以及(&lt;)/ 2。

如果你必须避免这样的系统谓词,你必须使用Peano算术(即代表 - 例如 - 2代表s(s(0))),并用更简单的术语重写:

countSublist([], Sought, 0).
countSublist([H|List], Sought, s(C)) :-
  isprefix(Sought, [H|List]),
  % !, can you use cuts ?
  countSublist(List, Sought, C).
countSublist([H|List], Sought, C) :-
  % \+ isprefix(Sought, [H|List]), can you use not ?
  countSublist(List, Sought, C).

% isprefix(Sought, List) is simple enough
顺便说一句,使用某些高级库(例如aggregate

)要简单得多

这是DCG版本

countSublist(List, SubL, Count) :-
    aggregate(count, R^phrase((..., SubL), List, R), Count).

... --> [] ; [_], ... .

这里是追加/ 3个

countSublist(List, SubL, Count) :-
    aggregate(count, U1^U2^U3^(append(U1,U2,List),append(SubL,U3,U2)), Count).

和,我的首选,使用append / 2

countSublist(List, SubL, Count) :-
    aggregate(count, L^R^append([L,SubL,R], List), Count).

答案 1 :(得分:2)

我想每个人都有自己的简化版。我的尾部递归只是略有不同:

% headsub(S, L) is true if S is a sublist at the head of L
headsub([Hs|Ts], [Hs|Tl]) :-
    headsub(Ts, Tl).
headsub([], _).

subcount(L, S, C) :-
    subcount(L, S, 0, C).
subcount([Hl|Tl], S, A, C) :-
    (   headsub(S, [Hl|Tl])
    ->  A1 is A + 1     % if S is a sublist in front, count it
    ;   A1 = A          % otherwise, don't count it
    ),
    subcount(Tl, S, A1, C).   % Count the rest, adding it into the accumulator
subcount([], _, C, C).  % We're done here. Accumulator becomes the answer.

在您的原始解决方案中,一个哲学问题是您解决问题的方法。您使用的算法越复杂,就像在传统的编程语言中而不是声明性地在程序上思考问题,这是Prolog的设计目标。更简单的代码来自于将问题视为定义谓词,该谓词指示子列表何时位于列表的前面。然后定义一个谓词,递归检查子列表是否位于该列表后续尾部的前面,并计算它的真实次数。

就原始解决方案中的特定错误而言,有两个问题正在关闭,两个都有以下谓词:

countSublist([H|T], L2, R):- size(L2, SizeL2), %% we need L2's size
                             subList([H|T], SizeL2, SubL1), %% we get L1's sublist
                             countSublist(T, L2, R),
                             SubL1 = L2,
                             R1 is R+1. %% we do R+1 only if L2=Sublist of L1

在这里,您希望最终的计数结果为R。但是,您使用R作为中间结果,然后使用R1作为最终结果。这些与它们应该是相反的。

其次,SubL1 = L2失败,然后上述条款失败并回溯重新评估countSublist(T, L2, R)。这可能不是您想要的,因为该查询的成功应计入您的结果中。所以逻辑需要重新考虑。快速修复显示了基本需求,但需要进行一些清理:

countSublist([H|T], L2, R):- size(L2, SizeL2), %% we need L2's size
                             subList([H|T], SizeL2, SubL1), %% we get L1's sublist
                             countSublist(T, L2, R1),
                             (   SubL1 = L2
                             ->  R is R1+1  %% we do R+1 only if L2=Sublist of L1
                             ;   R = R1
                             ).

如果SubL1 = L2失败,这就成功了,这根本不计算不匹配。所以现在你得到了正确答案,但多次:

| ?- countSublist([1,2,3,4,5,2,3,2,3,4], [2,3,4], R).

R = 2 ? a

R = 2

R = 2

我会把它作为练习让读者整理一下。 :)

答案 2 :(得分:2)

这个答案是对this earlier answer的跟进,并尝试以不同的方式改进它:

让我们开始吧!在prefix_of_t/3的基础上,我们定义:

list_contains_count(List, Sub, N) :-
   list_contains_count_acc(List, Sub, N, 0).

list_contains_count_acc(List, Sub, N, N0) :-
   prefix_of_t(Sub, List, T),
   t_01(T, T01),
   N1 #=  N0 + T01,
   N1 #=< N,
   aux_list_contains_(List, Sub, N, N1).

aux_list_contains_([], _, N, N).
aux_list_contains_([_|Es], Sub, N, N0) :-
   list_contains_count_acc(Es, Sub, N, N0).

t_01( true, 1).
t_01(false, 0).

一些示例查询:

?- list_contains_count([1,2,3,4,5,2,3,2,3,4], [2,3,4], N).
N = 2.

?- list_contains_count([2,2,2,2,2,2], [2,2,2], N).
N = 4.

?- list_contains_count([1,2,3,1,2,3], [1,2], N).
N = 2.

OK!和以前一样......一些角落案件怎么样?

?- length(Sub, N), list_contains_count([], Sub, C).
   N = 0, C = 1, Sub = []
;  N = 1, C = 0, Sub = [_A]
;  N = 2, C = 0, Sub = [_A,_B]
;  N = 3, C = 0, Sub = [_A,_B,_C]
;  N = 4, C = 0, Sub = [_A,_B,_C,_D]
...

?- length(List, N), list_contains_count(List, [], C).
;  N = 0, C = 1, List = []
;  N = 1, C = 2, List = [_A]
;  N = 2, C = 3, List = [_A,_B]
;  N = 3, C = 4, List = [_A,_B,_C]
;  N = 4, C = 5, List = [_A,_B,_C,_D]
...

好吧...... 事实上,即使更好也比之前 1

?- (  Version = old, list_contains_countX([], [], C)
   ;  Version = new, list_contains_count( [], [], C)
   ).
   Version = old, C = 0
;  Version = new, C = 1.

给我们带来了哪些开销?让我们测量并比较一些运行时 2,3

?- set_prolog_flag(toplevel_print_anon, false).
true.

?- length(_Xs, 100_000),
   maplist(=(x), _Xs),
   between(0, 6, _Exp),
   L #= 2 ^ _Exp,
   length(_Sub, L),
   maplist(=(x), _Sub),
   (  Version = old, call_time(list_contains_countX(_Xs,_Sub,_N), T_ms)
   ;  Version = new, call_time(list_contains_count( _Xs,_Sub,_N), T_ms)
   ).
   L =  1, Version = old, N = 100000, T_ms =  101
;  L =  1, Version = new, N = 100000, T_ms = 1422   /*  1500% slower */
;
   L =  2, Version = old, N =  99999, T_ms =  153
;  L =  2, Version = new, N =  99999, T_ms = 1479
;
   L =  4, Version = old, N =  99997, T_ms =  240
;  L =  4, Version = new, N =  99997, T_ms = 1572   /*   650% slower */
;
   L =  8, Version = old, N =  99993, T_ms =  417
;  L =  8, Version = new, N =  99993, T_ms = 1760
;
   L = 16, Version = old, N =  99985, T_ms =  778
;  L = 16, Version = new, N =  99985, T_ms = 2122   /*   280% slower */
;
   L = 32, Version = old, N =  99969, T_ms = 1497
;  L = 32, Version = new, N =  99969, T_ms = 2859
;
   L = 64, Version = old, N =  99937, T_ms = 2948
;  L = 64, Version = new, N =  99937, T_ms = 4330.  /*   150% slower */

以上代码与非clpfd代码相比,最坏情况下的减速率为1500%。

那么......是否存在任何案例,其中clpfd-variant优于其非clpfd版本? 是!

?- length(_Xs, 100_000),
   maplist(=(x), _Xs),
   member(Ub, [0,10,100,1000,10000,100000]),
   _N #< Ub,
   (  Version = old, call_time(ignore(list_contains_countX(_Xs,[x],_N)), T_ms)
   ;  Version = new, call_time(ignore(list_contains_count( _Xs,[x],_N)), T_ms)
   ).
   Ub =      0, Version = old, T_ms =  100  
;  Ub =      0, Version = new, T_ms =    0          /* 10000% faster */
;
   Ub =     10, Version = old, T_ms =  107
;  Ub =     10, Version = new, T_ms =    1          /*  5000% faster */
;
   Ub =    100, Version = old, T_ms =  104
;  Ub =    100, Version = new, T_ms =    3          /*  3000% faster */
;
   Ub =   1000, Version = old, T_ms =  104
;  Ub =   1000, Version = new, T_ms =   17          /*   611% faster */
;
   Ub =  10000, Version = old, T_ms =  106
;  Ub =  10000, Version = new, T_ms =  130          /*   125% slower */
;
   Ub = 100000, Version = old, T_ms =  102
;  Ub = 100000, Version = new, T_ms = 1238.         /*  1300% slower */

底线?如果预先约束N,新代​​码可以很多更快! YMMV!

脚注1: prefix([], [])成功; list_contains_count([], [], 0)不太适合。{ 脚注2:与之相比 earlier definition,在此答案中称为list_contains_countX/3 脚注3:所有运行时测量均使用SWI-Prolog 7.3.13(AMD64)进行。 功能

答案 3 :(得分:1)

使用if_/3prefix_of_t/3直接前进:

list_contains_count(Ls, Es, N) :-
   list_contains_acc_count(Ls, Es, 0, N).

list_contains_acc_count([], _, N, N).
list_contains_acc_count([X|Xs], Sub, N0, N) :-
   if_(prefix_of_t(Sub, [X|Xs]),
       N1 is N0+1,
       N1 = N0),
   list_contains_acc_count(Xs, Sub, N1, N).

示例查询:

?- list_contains_count([1,2,3,4,5,2,3,2,3,4], [2,3,4], N).
N = 2.

?- list_contains_count([2,2,2,2,2,2], [2,2,2], N).
N = 4.

?- list_contains_count([1,2,3,1,2,3], [1,2], N).
N = 2.

一般情况怎么样?

?- dif(N, 0), Sub = [_|_], list_contains_count([1,2,3,1,2,3], Sub, N).
  N = 2, Sub = [1]
; N = 2, Sub = [1,2]
; N = 2, Sub = [1,2,3]
; N = 1, Sub = [1,2,3,1]
; N = 1, Sub = [1,2,3,1,2]
; N = 1, Sub = [1,2,3,1,2,3]
; N = 2, Sub = [2]
; N = 2, Sub = [2,3]
; N = 1, Sub = [2,3,1]
; N = 1, Sub = [2,3,1,2]
; N = 1, Sub = [2,3,1,2,3]
; N = 2, Sub = [3]
; N = 1, Sub = [3,1]
; N = 1, Sub = [3,1,2]
; N = 1, Sub = [3,1,2,3]
; false.

答案 4 :(得分:0)

为了简化解决方案,您不需要计算任何列表的大小,也不需要检查一个列表是否短于另一个列表。一个子列表是主列表的成员,或者不是。所以唯一需要的计数器就是子列表匹配的数量。

% H1 is the head of both lists. When done recursively, checks whole list.
isl(_, []).
isl([H1|T1], [H1|T2]) :-
    isl(T1, T2).

% Base of recursion, finished first parameter
subby([], _, 0).

% ToFind is a sublist of first parameter
subby([H|T], ToFind, N) :-
    isl([H|T], ToFind),

    % recurse around, increment count
    subby(T, ToFind, Inc),
    N is Inc+1.

% ToFind is not sublist, decapitate and recurse
subby([_|T], ToFind, N) :-
    subby(T, ToFind, N).

此解决方案不使用尾递归,这对于较长的列表来说效率很低,但对于小型列表,它很好。即。在第二个子谓词谓词中,首先完成对尾部的递归,然后在返回的过程中随着递归冒泡而添加计数。

答案 5 :(得分:0)

似乎不应该比

更复杂
sublist_count(L,S,N) :-
  sublist_count(L,S,0,N)
  .

sublist_count([],_,N,N) :-
  !.
sublist_count([X|Xs],S,T,N) :-
  prefix_of([X|Xs],S) ,
  T1 is T+1 ,
  sublist_count(Xs,S,T1,N)
  !.

prefix_of(_,[]) :-
  !.
prefix_of([X|Xs],[X|Ys]) :-
  prefix_of(Xs,Ys)
  .

答案 6 :(得分:0)

以下是我的尝试。这更像是基于谓词/关系方法的解决方案,而不是程序方法。

pe([],[]).
pe(X,X).
pe([_|T],X) :- pe(T,X).

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

sblist(L,S) :- pe(L,X), sbls(X,S).


| ?- findall(_,sblist([1,2,3,1,2,3],[1,2]),L), length(L,Count).

Count = 2

| ?- findall(_,sblist([2,2,2,2,2,2],[2,2,2]),L), length(L,Count).

Count = 4