我进行了Prolog
练习,要求我计算一个列表L2
作为L1
中的子列表显示的时间。
我需要做的是从用另一种语言编写的代码开始编写Prolog
代码(我们在大学使用的某种教学语言)。
所以我写的算法是这种语言,一切顺利,但Prolog
版本失败了,我没有说明原因。
这个想法非常简单:
L2
的长度并将其称为SizeL2
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)
)中,因为size
和subList
工作正常。
有什么想法吗?
答案 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的跟进,并尝试以不同的方式改进它:
:- use_module(library(clpfd)). % for declarative integer arithmetic
我们通过使用类似累加器clpfd来最小化this fd_length/2
开销。
此外,我们会以更一致的方式处理一些角落。
让我们开始吧!在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.
clpfd给我们带来了哪些开销?让我们测量并比较一些运行时 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代码与非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_/3
和prefix_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