【发布时间】:2022-01-04 02:26:49
【问题描述】:
使用正常的右关联树折叠,我可以通过重新排列提供的函数 f 中的连接来定义前序/中序/后序:
const treeFoldr = f => acc => function go(t) {
if (t[TAG] === "Leaf") return acc;
else return f(t.v) (go(t.l)) (go(t.r));
};
const TAG = Symbol.toStringTag;
const N = (l, v, r) => ({[TAG]: "Node", l, v, r});
const L = {[TAG]: "Leaf"};
const foo = N(
N(
N(L, 4, L),
1,
N(L, 5, L)
),
0,
N(
L,
2,
N(L, 3, L)
)
);
const r1 = treeFoldr(
x => acc1 => acc2 => {return [x].concat(acc1).concat(acc2)}) ([]) (foo); // pre-order
const r2 = treeFoldr(
x => acc1 => acc2 => {return acc1.concat([x]).concat(acc2)}) ([]) (foo); // in-order
const r3 = treeFoldr(
x => acc1 => acc2 => {return acc1.concat(acc2).concat([x])}) ([]) (foo); // post-order
console.log(r2); // in-order [4,1,5,0,2,3]
现在我猜想左关联折叠也应该是这样,f 的结果被传递给下一个递归go 调用。但我能想到的只是这个硬编码的预购折叠:
treeFoldl = f => acc => function go(t) {
if (t[TAG] === "Leaf") return acc;
else {
acc = f(acc) (t.v);
acc = go(t.l);
return go(t.r);
}
};
为了获得所需的设计,我必须以某种方式合并两个累加器(因为f 的签名)和左/右节点的递归调用,但我不知道如何。
这可能很容易,但我就是只见树木不见森林..
[编辑]
根据 cmets 的要求,这里是 treeFoldl 的纯版本:
const treeFoldl = f => init => t => function go(acc, u) {
if (u[TAG] === "Leaf") return acc;
else {
const acc_ = go(f(acc) (u.v), u.l);
return go(acc_, u.r);
}
} (init, t);
【问题讨论】:
-
你能把你的
treeFoldl改写成纯粹的吗?对acc的分配让人非常困惑 -
“期望的设计”我想你的意思是一个折叠函数,它允许与你的
treeFoldr中演示的相同的界面,而是发展了一个扁平的尾递归过程?我认为这基本上是不可能的,因为f调用的顺序必须有一个由折叠实现确定的顺序。在您的尝试中,您注意到了一个硬编码的预购,但重新安排了您可以实现中序和后序的操作。 -
这支持了为什么我建议不要将其视为
treeFoldR和treeFoldL而是根据它们的名称来考虑各种折叠,inorder、preorder、@ 987654339@。您提议的treeFoldL有两次对go的调用,因此它仍然会演变为递归(非线性)过程。inorder、preorder和postorder都可以通过使用 cps 的尾递归来实现。 -
this related Q&A你应该很熟悉,the other answer包含了每个遍历的实现
-
我只想感谢所有参与者的非常有趣和启发性的 Q + A。
标签: javascript recursion functional-programming binary-tree