这是一个由已删除的回答引发的问题,与这个问题有关。问题可以总结如下:
令人惊讶的是,这个方法按预期工作。我可以在部分列表的开头“种植”一个初始值,并在消耗当前头部时继续添加下一个元素。定义
我对原始“区域”问题的自己的解决方案看起来像这样:
使用与上文相同的“技巧”,我们可以将其扭曲成一个折叠:
是否可能在折叠列表时,在折叠过程中生成列表的尾部?
我想表达的是这样的。假设我想计算阶乘(这只是一个愚蠢的例子,仅用于演示),并决定这样做:
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
的解决方案不同,这个解决方案使用恒定的内存:不需要生成带有所有值的列表!
计算阶乘不是最好的例子,但易于理解接下来的愚蠢。
假设我非常害怕循环(和递归),并坚持使用fold来计算阶乘。然而,我仍然需要一个列表。因此,我可能会尝试以下操作:
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
的定义相同:唯一的区别在于状态的维护方式不同。我的假设是,这应该使用恒定的内存:这个假设是错误的吗?
我知道这很傻,但它可能对于折叠无法在折叠开始时知道的列表非常有用。在原始问题中,我们需要给出一组x-y坐标的连接区域。仅对x-y坐标列表进行一次折叠是不够的(您可以使用两次通过方法;请注意,至少有一种更好的方法在同一维基百科文章中引用,但这也使用多个通过;总体而言,多通道算法假设可以恒定时间访问相邻像素!)。我对原始“区域”问题的自己的解决方案看起来像这样:
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中的隐式状态有些相似,但我必须承认我从未完全弄清楚它是如何工作的;它们是否相关?
foldl/4
绝对不是 SWI 特定的。它甚至出现在 Richard O'Keefe 的库提案中。任何初学者都可以在任何 Prolog 系统中实现它。[tag: swi-prolog] 标签应该保留用于清楚地说明与 SWI 相关的问题,以便用户更轻松地找到这些相关问题。将任何使用 SWI 提供的单个谓词的地方标记为“SWI”会使查找此类实例变得不可能。 - matfoldl/4
就在那里。然而,must_be/2
不在其中。它是否在标准库中? - user1812457fold/4
这样有多个合理参数顺序的情况。 - Paulo Moura