问题是找到列表中的最后一个字符,例如
?- last_but_one(X, [a,b,c,d]).
X = c.
我的代码是:
last_but_one(X, [X,_]).
last_but_one(X, [_|T]) :- last_but_one(X, T).
他们提供的代码是:
last_but_one(X, [X,_]).
last_but_one(X, [_,Y|Ys]) :- last_but_one(X, [Y|Ys]).
当我在学习Haskell时,我记得当某些列表中的第二,第三或第n个字符的问题时,结构与提供的答案相同,所以我知道按照它们的方式编写它写了它有一些意义。但我似乎仍然按照我写的方式得到了正确答案。
我写错了吗?提出答案的人的代码是否写得更好 - 如果是这样,怎么样?
答案 0 :(得分:3)
您的原始版本更易于阅读。特别是,递归规则读取 - 从右向左读取
last_but_one(X, [_|T]) :- last_but_one(X, T).
^^^^^^^^^^
provided X is the lbo-element in T
^^ then, it follows, that (that's an arrow!)
^^^^^^^^^^^^^^^^^^^^^^
X is also the lbo-element of T with one more element
换句话说:如果你已经在给定列表T
中有一个lbo元素,那么你可以构造新的列表,前面还有任何其他元素也具有相同的lbo元素。
有人可能会争论哪个版本的效率更好。如果你真的喜欢这个,那就拿:
last_but_one_f1(E, Es) :-
Es = [_,_|Xs],
xs_es_lbo(Xs, Es, E).
xs_es_lbo([], [E|_], E).
xs_es_lbo([_|Xs], [_|Es], E) :-
xs_es_lbo(Xs, Es, E).
甚至:
last_but_one_f2(E, [F,G|Es]) :-
es_f_g(Es, F, G, E).
es_f_g([], E, _, E).
es_f_g([G|Es], _, F, E) :-
es_f_g(Es, F, G, E).
永远不要忘记一般测试:
| ?- last_but_one(X, Es).
Es = [X,_A] ? ;
Es = [_A,X,_B] ? ;
Es = [_A,_B,X,_C] ? ;
Es = [_A,_B,_C,X,_D] ? ;
Es = [_A,_B,_C,_D,X,_E] ? ;
Es = [_A,_B,_C,_D,_E,X,_F] ? ...
以下是我的oltop labtop的一些基准测试:
SICStus SWI
4.3.2 7.3.20-1
--------------+----------+--------
you 0.850s | 3.616s | 4.25×
they 0.900s | 16.481s | 18.31×
f1 0.160s | 1.625s | 10.16×
f2 0.090s | 1.449s | 16.10×
mat 0.880s | 4.390s | 4.99×
dcg 3.670s | 7.896s | 2.15×
dcgx 1.000s | 7.885s | 7.89×
ap 1.200s | 4.669s | 3.89×
差异很大的原因是f1
和f2
在没有创建选择点的情况下运行完全确定。
使用
bench_last :-
\+ ( length(Ls, 10000000),
member(M, [you,they,f1,f2,mat,dcg,dcgx,ap]), write(M), write(' '),
atom_concat(last_but_one_,M,P), \+ time(call(P,L,Ls))
).
答案 1 :(得分:3)
这是使用DCG的另一种方法。我认为这个解决方案更多"图形",但在SICStus中似乎相当慢:
last_but_one_dcg(L, Ls) :-
phrase( ( ..., [L,_] ), Ls).
... --> [].
... --> [_], ... .
因此,我们描述了一个列表必须如何看起来像是有一个最后一个元素。它看起来像这样:前面的任何东西(...
),然后是最后的两个元素。
通过展开phrase/2
来加快速度。请注意,扩展本身不再是一个符合要求的程序。
last_but_one_dcgx(L, Ls) :-
...(Ls, Ls2),
Ls2 = [L,_].
答案 2 :(得分:2)
我会说两个答案都一样好,我可能会按照你的方式写出来。他们在第二个解决方案中所做的是他们在递归调用之前检查第二个元素不是空列表([])。如果您在以下查询中跟踪两个不同的解决方案:last_but_one(X,[b]).
您会看到两者都给出相同的答案(false),但第二个解决方案需要更少的步骤,因为它在递归调用之前返回false。
答案 3 :(得分:2)
我同意@false你自己的版本更容易阅读。
就个人而言,我发现更容易使用DCG(见dcg):
last_but_one(X) --> [X,_].
last_but_one(X) -->
[_],
last_but_one(X).
作为界面谓词,您可以使用:
last_but_one(L, Ls) :-
phrase(last_but_one(L), Ls).
我现在想补充一些实际时间。
我们有3个版本可供比较:
last_but_one//1
last_but_one_you/2
last_but_one_they/2
。测试用例包括找到包含一千万个元素的列表的倒数第二个元素。
我们有:
?- length(Ls, 10_000_000), time(last_but_one(L, Ls)), false. 9,999,999 inferences, 1.400 CPU in 1.400 seconds (100% CPU, 7141982 Lips) ?- length(Ls, 10_000_000), time(last_but_one_you(L, Ls)), false. 9,999,998 inferences, 1.383 CPU in 1.383 seconds (100% CPU, 7229930 Lips) ?- length(Ls, 10_000_000), time(last_but_one_they(L, Ls)), false. 9,999,998 inferences, 5.566 CPU in 5.566 seconds (100% CPU, 1796684 Lips)
这表明他们提供的版本不仅难以阅读,而且对于此基准测试来说,它也是迄今为止最慢的。
首先要追求优雅和可读性。很多时候,如果你遵循这个原则,你也会得到一个快速版本。
答案 4 :(得分:2)
以下是 我不推荐实际使用以下任何一种方法,但IMO很有意思,因为它们对其他代码和各自Prolog处理器提供的Prolog库提供了不同的视图:
在前三个版本中,我们委托"递归部分"内置/库谓词:
last_but_one_append(X,Es) :-
append(_, [X,_], Es).
:- use_module(library(lists)).
last_but_one_reverse(X, Es) :-
reverse(Es, [_,X|_]).
last_but_one_rev(X, Es) :-
rev(Es, [_,X|_]). % (SICStus only)
或者,我们可以使用香草自制的myappend/3
和myreverse/2
:
myappend([], Bs, Bs).
myappend([A|As], Bs, [A|Cs]) :-
myappend(As, Bs, Cs).
last_but_one_myappend(X, Es) :-
myappend(_, [X,_], Es).
myreverse(Es, Fs) :-
same_length(Es, Fs), % for universal termination in mode (-,+)
myreverse_(Es, Fs, []).
myreverse_([], Fs, Fs).
myreverse_([E|Es], Fs, Fs0) :-
myreverse_(Es, Fs, [E|Fs0]).
last_but_one_myreverse(X, Es) :-
myreverse(Es, [_,X|_]).
让我们进行实验 1 !
bench_last :-
\+ ( length(Ls, 10000000),
member(M, [you,they,f1,f2,mat,dcg,dcgx,ap,
append,reverse,rev,
myappend,myreverse]),
write(M), write(' '),
atom_concat(last_but_one_,M,P),
\+ time(call(P,_L,Ls))
).
以下是使用SICStus Prolog和SWI-Prolog 3,4 的运行时间 2 :
SICStus | SICStus | SWI | 4.3.2 | 4.3.3 | 7.3.20 | -------------------+---------+--------| you 0.26s | 0.10s | 0.83s |3.1×8.3× they 0.27s | 0.12s | 1.03s |3.8×8.5× f1 0.04s | 0.02s | 0.43s |10.8×21.5× f2 0.02s | 0.02s | 0.37s |18.5×18.5× mat 0.26s | 0.11s | 1.02s |3.9×9.0× dcg 1.06s | 0.77s | 1.47s |1.3×1.9× dcgx 0.31s | 0.17s | 0.97s |3.1×5.7× ap 0.23s | 0.11s | 0.42s |1.8×3.8× append 1.50s | 1.13s | 1.57s |1.0×1.3× reverse 0.36s | 0.32s | 1.02s |2.8×3.1× rev 0.04s | 0.04s | --"-- |25.6×25.6× myappend 0.48s | 0.33s | 1.56s |3.2×4.7× myreverse 0.27s | 0.26s | 1.11s |4.1×4.2×
非常令人印象深刻!在SICStus / SWI加速列中,差异> 10%得到大胆的。
脚注1 :此答案中显示的所有测量均来自英特尔
Haswell处理器
Core i7-4700MQ。
脚注2 :rev/2
由SICStus提供 - 但不是由SWI提供。我们比较最快"反向"库谓词。
脚注3 :需要SWI命令行选项-G1G
来防止Out of global stack
错误。
脚注4 :此外,尝试了SWI命令行选项-O
(优化),但没有产生任何改进。
功能
答案 5 :(得分:1)
另一个解决方案:
代码:
last_but_one(R,[X|Rest]):-
( Rest=[_], R=X
; last_but_one(R,Rest)
).
测试:
| ?- last_but_one(Elem,List).
List = [Elem,_A] ? ;
List = [_A,Elem,_B] ? ;
List = [_A,_B,Elem,_C] ? ;
List = [_A,_B,_C,Elem,_D] ? ;
List = [_A,_B,_C,_D,Elem,_E] ? ;
List = [_A,_B,_C,_D,_E,Elem,_F] ? ;
List = [_A,_B,_C,_D,_E,_F,Elem,_G] ? ;
List = [_A,_B,_C,_D,_E,_F,_G,Elem,_H] ?
yes
希望这个想法可以帮助你