我对两个问题解决方案之间的区别有疑问。该问题要求将列表转换为截断列表,如下所示:
?- reduce([a,a,a,b,b,c,c,b,b,d,d],Z).
Z = [a,b,c,b,d].
第一个解决方案需要一个额外的步骤来反转列表:
reduce([X|Xs],Z) :-
reduce(X,Xs,Y,[X]),
reverse(Y,Z).
reduce(X,[L|Ls],Y,List) :-
( X=L
-> reduce(X,Ls,Y,List)
; reduce(L,Ls,Y,[L|List])
).
reduce(_,[],Y,Y).
第二种解决方案不需要reverse/2
:
reduced([X|Xs],Result) :-
reduced(Xs,List),
List=[A|_],
( A=X
-> Result=List
; Result=[X|List]
),
!.
reduced(Result,Result).
在一系列语句之前或之后执行递归时有哪些优化注意事项?条件的顺序是否重要?我倾向于认为提前完成所有递归是要走的路,特别是因为这里需要向后构建列表。
答案 0 :(得分:6)
优化任何内容时,请务必先测量! (我们大多数人都会忘记这一点....)
优化Prolog时,请注意以下事项:
优化的解决方案"对于或多或少的标准Prolog实现看起来可能会有所不同。我将其命名为list_uniq
(类似于命令行uniq
工具):
list_uniq([], []). % Base case list_uniq([H|T], U) :- list_uniq_1(T, H, U). % Helper predicate list_uniq_1([], X, [X]). list_uniq_1([H|T], X, U) :- ( H == X -> list_uniq_1(T, X, U) ; [X|U1] = U, list_uniq_1(T, H, U1) ).
它与@CapelliC的reduce0/2
不同,因为它使用滞后来避免第一个参数中[X|Xs]
和[X,X|Xs]
的固有非确定性
现在宣称它已经过优化":
您将获得与@CapelliC相同的12个推论,如果您使用稍长的列表,您将开始看到差异:
?- length(As, 100000), maplist(=(a), As), length(Bs, 100000), maplist(=(b), Bs), length(Cs, 100000), maplist(=(c), Cs), append([As, Bs, Cs, As, Cs, Bs], L), time(list_uniq(L, U)). % 600,006 inferences, 0.057 CPU in 0.057 seconds (100% CPU, 10499893 Lips) As = [a, a, a, a, a, a, a, a, a|...], Bs = [b, b, b, b, b, b, b, b, b|...], Cs = [c, c, c, c, c, c, c, c, c|...], L = [a, a, a, a, a, a, a, a, a|...], U = [a, b, c, a, c, b].
与来自@ CapelliC的答案的reduce0
,reduce1
,reduce2
相同的查询:
% reduce0(L, U) % 600,001 inferences, 0.125 CPU in 0.125 seconds (100% CPU, 4813955 Lips) % reduce1(L, U) % 1,200,012 inferences, 0.393 CPU in 0.394 seconds (100% CPU, 3050034 Lips) % reduce2(L, U) % 2,400,004 inferences, 0.859 CPU in 0.861 seconds (100% CPU, 2792792 Lips)
因此,创建和放弃带有削减(!
)的选择点也是有代价的。
但是,list_uniq/2
,对于第一个参数不是基础的查询,可能是错误的:
?- list_uniq([a,B], [a,b]). B = b. % OK ?- list_uniq([a,A], [a]). false. % WRONG!
reduce0/2
和reduce1/2
也可能是错的:
?- reduce0([a,B], [a,b]). false. ?- reduce1([a,B], [a,b]). false.
关于reduce2/2
,我不确定这个:
?- reduce2([a,A], [a,a]). A = a.
相反,使用this answer中的if_/3
定义:
list_uniq_d([], []). % Base case list_uniq_d([H|T], U) :- list_uniq_d_1(T, H, U). % Helper predicate list_uniq_d_1([], X, [X]). list_uniq_d_1([H|T], X, U) :- if_(H = X, list_uniq_d_1(T, H, U), ( [X|U1] = U, list_uniq_d_1(T, H, U1) ) ).
用它:
?- list_uniq_d([a,a,a,b], U). U = [a, b]. ?- list_uniq_d([a,a,a,b,b], U). U = [a, b]. ?- list_uniq_d([a,A], U). A = a, U = [a] ; U = [a, A], dif(A, a). ?- list_uniq_d([a,A], [a]). A = a ; false. % Dangling choice point ?- list_uniq_d([a,A], [a,a]). false. ?- list_uniq_d([a,B], [a,b]). B = b. ?- list_uniq_d([a,A], [a,a]). false.
需要更长时间,但谓词似乎正确。 使用与其他时间相同的查询:
% 3,000,007 inferences, 1.140 CPU in 1.141 seconds (100% CPU, 2631644 Lips)
答案 1 :(得分:2)
分析似乎是回答效率问题的更简单方法:
% my own
reduce0([], []).
reduce0([X,X|Xs], Ys) :- !, reduce0([X|Xs], Ys).
reduce0([X|Xs], [X|Ys]) :- reduce0(Xs, Ys).
% your first
reduce1([X|Xs],Z) :- reduce1(X,Xs,Y,[X]), reverse(Y,Z).
reduce1(X,[L|Ls],Y,List) :-
X=L -> reduce1(X,Ls,Y,List);
reduce1(L,Ls,Y,[L|List]).
reduce1(_,[],Y,Y).
% your second
reduce2([X|Xs],Result) :-
reduce2(Xs,List),
List=[A|_],
(A=X -> Result=List;
Result=[X|List]),!.
reduce2(Result,Result).
SWI-Prolog提供时间/ 1:
4 ?- time(reduce0([a,a,a,b,b,c,c,b,b,d,d],Z)).
% 12 inferences, 0.000 CPU in 0.000 seconds (84% CPU, 340416 Lips)
Z = [a, b, c, b, d].
5 ?- time(reduce1([a,a,a,b,b,c,c,b,b,d,d],Z)).
% 19 inferences, 0.000 CPU in 0.000 seconds (90% CPU, 283113 Lips)
Z = [a, b, c, b, d] ;
% 5 inferences, 0.000 CPU in 0.000 seconds (89% CPU, 102948 Lips)
false.
6 ?- time(reduce2([a,a,a,b,b,c,c,b,b,d,d],Z)).
% 12 inferences, 0.000 CPU in 0.000 seconds (83% CPU, 337316 Lips)
Z = [a, b, c, b, d].
你的第二个谓词就像我的一样,而第一个谓词似乎留下了一个选择点...
鉴于Prolog实施的解决方案策略,这是最重要的条件顺序。在天真的实现中,例如我的IL,tail recursion optimization仅在递归调用是最后一个时被识别,而在前面被切割。只是为了确定它是确定性的......
答案 2 :(得分:2)
此答案是对@Boris's answer的直接跟进。
为了估计编译if_/3
后我们可以预期的运行时间,
我创建list_uniq_e/2
就像@Boris的list_uniq_d/2
一样,手动编译if_/3
。
list_uniq_e([], []). % Base case
list_uniq_e([H|T], U) :-
list_uniq_e_1(T, H, U). % Helper predicate
list_uniq_e_1([], X, [X]).
list_uniq_e_1([H|T], X, U) :-
=(H,X,Truth),
list_uniq_e_2(Truth,H,T,X,U).
list_uniq_e_2(true ,H,T,_, U ) :- list_uniq_e_1(T,H,U).
list_uniq_e_2(false,H,T,X,[X|U]) :- list_uniq_e_1(T,H,U).
让我们比较运行时(SWI Prolog 7.3.1,Intel Core i7-4700MQ 2.4GHz)!
首先,list_uniq_d/2
:
% 3,000,007 inferences, 0.623 CPU in 0.623 seconds (100% CPU, 4813150 Lips)
接下来,list_uniq_e/2
:
% 2,400,003 inferences, 0.132 CPU in 0.132 seconds (100% CPU, 18154530 Lips)
为了完整起见reduce0/2
,reduce1/2
和reduce2/2
:
% 600,002 inferences, 0.079 CPU in 0.079 seconds (100% CPU, 7564981 Lips)
% 600,070 inferences, 0.141 CPU in 0.141 seconds (100% CPU, 4266842 Lips)
% 600,001 inferences, 0.475 CPU in 0.475 seconds (100% CPU, 1262018 Lips)
还不错!并且......就优化if_/3
而言,这不是行的结束:)
答案 3 :(得分:1)
希望这是@Boris's answer比last try更好的后续行动!
首先,这里是@鲍里斯的代码(100%原创):
// like canonical() but allows the last component of p to be a broken symlink
filesystem::path
resolve_most_symlinks(filesystem::path const& p, filesystem::path const& base = filesystem::current_path())
{
if (is_symlink(p) && !exists(p))
return canonical(absolute(p, base).remove_filename()) / p.filename();
return canonical(p);
}
再增加一些基准测试代码:
list_uniq_d([], []). % Base case
list_uniq_d([H|T], U) :-
list_uniq_d_1(T, H, U). % Helper predicate
list_uniq_d_1([], X, [X]).
list_uniq_d_1([H|T], X, U) :-
if_(H = X,
list_uniq_d_1(T, H, U),
( [X|U1] = U,
list_uniq_d_1(T, H, U1)
)
).
现在,让我们介绍模块bench(P_2) :-
length(As, 100000), maplist(=(a), As),
length(Bs, 100000), maplist(=(b), Bs),
length(Cs, 100000), maplist(=(c), Cs),
append([As, Bs, Cs, As, Cs, Bs], L),
time(call(P_2,L,_)).
:
re_if
现在...... *鼓声* ......看见:)
$ swipl Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.3-18-gc341872) Copyright (c) 1990-2015 University of Amsterdam, VU Amsterdam SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. Please visit http://www.swi-prolog.org for details. For help, use ?- help(Topic). or ?- apropos(Word). ?- compile(re_if), compile(list_uniq). true. ?- bench(list_uniq_d). % 2,400,010 inferences, 0.865 CPU in 0.865 seconds (100% CPU, 2775147 Lips) true. ?- assert(re_if:expand_if_goals), compile(list_uniq). true. ?- bench(list_uniq_d). % 1,200,005 inferences, 0.215 CPU in 0.215 seconds (100% CPU, 5591612 Lips) true.