这是一个已删除的对该问题的回答引发的问题。这个问题可以概括如下:
是否可以折叠列表,折叠时生成列表的尾部?
这就是我的意思。假设我想计算阶乘(这是一个愚蠢的例子,但它只是为了演示),并决定这样做:
fac_a(N, F) :-
must_be(nonneg, N),
( N =< 1
-> F = 1
; numlist(2, N, [H|T]),
foldl(multiplication, T, H, F)
).
multiplication(X, Y, Z) :-
Z is Y * X.
在这里,我需要生成我提供给的列表foldl
。但是,我可以在常量内存中做同样的事情(不生成列表也不使用foldl
):
fac_b(N, F) :-
must_be(nonneg, N),
( N =< 1
-> F = 1
; fac_b_1(2, N, 2, F)
).
fac_b_1(X, N, Acc, F) :-
( X < N
-> succ(X, X1),
Acc1 is X1 * Acc,
fac_b_1(X1, N, Acc1, F)
; Acc = F
).
这里的要点是,与使用 的解决方案不同foldl
,它使用常量内存:无需生成包含所有值的列表!
计算阶乘并不是最好的例子,但接下来的愚蠢更容易理解。
假设我真的很害怕循环(和递归),并坚持使用折叠来计算阶乘。不过,我仍然需要一份清单。所以这是我可能会尝试的:
fac_c(N, F) :-
must_be(nonneg, N),
( N =< 1
-> F = 1
; foldl(fac_foldl(N), [2|Back], 2-Back, F-[])
).
fac_foldl(N, X, Acc-Back, F-Rest) :-
( X < N
-> succ(X, X1),
F is Acc * X1,
Back = [X1|Rest]
; Acc = F,
Back = []
).
令我惊讶的是,这按预期工作。我可以在部分列表的头部使用初始值“播种”折叠,并在使用当前头部时继续添加下一个元素。的定义fac_foldl/4
几乎与fac_b_1/4
上面的定义相同:唯一的区别是状态的维护方式不同。我的假设是这应该使用常量内存:这个假设是错误的吗?
我知道这很愚蠢,但是它对于折叠折叠开始时无法知道的列表可能很有用。在最初的问题中,我们必须找到一个连接区域,给定一个 xy 坐标列表。将 xy 坐标列表折叠一次是不够的(但是您可以分两次进行;请注意,至少有一种更好的方法可以做到这一点,在同一篇 Wikipedia 文章中引用,但这也使用了多次;总的来说,多遍算法假设对相邻像素的恒定时间访问!)。
我自己对原始“区域”问题的解决方案如下所示:
set_region_rest([A|As], Region, Rest) :-
sort([A|As], [B|Bs]),
open_set_closed_rest([B], Bs, Region0, Rest),
sort(Region0, Region).
open_set_closed_rest([], Rest, [], Rest).
open_set_closed_rest([X-Y|As], Set, [X-Y|Closed0], Rest) :-
X0 is X-1, X1 is X + 1,
Y0 is Y-1, Y1 is Y + 1,
ord_intersection([X0-Y,X-Y0,X-Y1,X1-Y], Set, New, Set0),
append(New, As, Open),
open_set_closed_rest(Open, Set0, Closed0, Rest).
使用与上面相同的“技术”,我们可以将其扭曲成折叠:
set_region_rest_foldl([A|As], Region, Rest) :-
sort([A|As], [B|Bs]),
foldl(region_foldl, [B|Back],
closed_rest(Region0, Bs)-Back,
closed_rest([], Rest)-[]),
!,
sort(Region0, Region).
region_foldl(X-Y,
closed_rest([X-Y|Closed0], Set)-Back,
closed_rest(Closed0, Set0)-Back0) :-
X0 is X-1, X1 is X + 1,
Y0 is Y-1, Y1 is Y + 1,
ord_intersection([X0-Y,X-Y0,X-Y1,X1-Y], Set, New, Set0),
append(New, Back0, Back).
这也“有效”。折叠留下了一个选择点,因为我没有像fac_foldl/4
上面那样清楚地表达结束条件,所以我需要在它之后进行切割(丑陋)。
问题
- 有没有一种干净的方法来关闭列表并删除削减?在阶乘示例中,我们知道何时停止,因为我们有额外的信息;但是,在第二个示例中,我们如何注意到列表的后面应该是空列表?
- 有没有我遗漏的隐藏问题?
- 这看起来有点类似于带有 DCG 的隐式状态,但我不得不承认我从来没有完全了解它是如何工作的;这些有联系吗?