节省空间的函数符号书写



编写函数式表示法在辅助空间消耗方面通常相当昂贵。这对于列表的规范编写尤其重要。

首先考虑输出的大小:通常的ignore_ops(false)写作需要至少2n+1个字符,如[1,2,3]长度为n,规范写作至少需要7n+2'.'(1,'.'(2,'.'(3,[])))。 (补充:(没有办法更改输出,因为它是这样定义的。但是,在写作过程中还有很大的改进空间:

现在考虑写入输出所需的辅助空间:用方括号编写列表不需要任何与列表长度成比例的辅助空间。 朴素的规范书写需要与列表长度成比例的空间来表示缩进堆栈。

如何在没有这种开销的情况下规范地编写列表?


这是一个简单的测试,用于检查系统中发生的情况。

首先,减少最大虚拟内存大小以减少您的等待时间,大约 180M 左右对我有用。

$ ulimit -v -180000

nat_sx(N0, s(X)) :-
N0 > 0,
N1 is N0-1, nat_sx(N1,X).
nat_sx(0, 0).
?- open('/dev/null',write,Null),
length(_,I), N is 10^I, nat_sx(N,SX),
( Res=unwritten ; write_canonical(Null,SX) ).

SICStus和SWI现在都在write_canonical(Null, SX)内中止。预计它们宁愿在nat_sx/2的某个时候中止.无法直接比较列表,因为SWI的write_canonical/1总是使用方括号。

只需将'.'(视为左括号,,'.'(作为逗号,并将结尾输出的右括号的整数计数递增为右括号。

因此O(1(辅助空间。

有人可能会认为在Prolog中实现write_canonical/1本身可以解决本机堆栈问题。但是有一点转折,如果我使用这段代码,那么writec/1无法使用最后调用优化,因为最后有一个write(')')

writec(X) :- 
X =.. [F,Y|L], !,
writeq(F),
write('('),
writec(Y),
writec_list(L),
write(')').              /* prevents LCO */
writec(X) :-
writeq(X).
writec_list([]).
writec_list([X|L]) :-
write(','),
writec(X),
writec_list(L).

对于SWI-Prolog中的列表,需要添加从'[|]''.'的翻译。但除此之外,代码按预期工作:

?- writec(f(h(- 1),g(2+3))).
f(h(-(1)),g(+(2,3)))
true.
?- writec([a,b,c]).
'[|]'(a,'[|]'(b,'[|]'(c,[])))
true.

但是按照Will Ness的想法,我们可以按如下方式转换代码。我们按照建议使用右括号的整数计数,并在 writec 的原子大小写中处理右括号:

writec2(X) :-
writec2(0, X).
writec2(N, X) :- 
X =.. [F,Y], !,
writeq(F),
write('('),
M is N+1,
writec2(M, Y).
writec2(N, X) :- 
X =.. [F,Y,Z|L], !,
writeq(F),
write('('),
writec2(Y),
writec2_list([Z|L], N).
writec2(N, X) :- 
writeq(X),
writec2_closing(N).
writec2_closing(0) :- !.
writec2_closing(N) :-
M is N-1,
write(')'),
writec2_closing(M).

writec2_list([X], N) :- !,
write(','),
M is N+1,
writec2(M, X).
writec2_list([X,Y|L], N) :-
write(','),
writec2(X),
writec2_list([Y|L], N).

上面的代码现在完全适用于上次调用优化。可以尝试使用最后一个复合参数的循环将其移植回本机堆栈例程。最后的健全性检查,它确实做了正确的事情:

?- writec2(f(h(- 1),g(2+3))).
f(h(-(1)),g(+(2,3)))
true.
?- writec2([a,b,c]).
'[|]'(a,'[|]'(b,'[|]'(c,[])))
true.

最新更新