TL;DR:您的伪代码有问题。修复它的一种方法是:
reduceTree :: (b -> a -> b) -> b -> T a -> b
reduceTree f acc (Node val []) = f acc val
reduceTree f acc (Node val ts) =
Data.List.foldl (\acc tree -> reduceTree f acc tree) (f acc val) ts
这意味着你的 Javascript 应该已经被...(需要上下文补充)
export function reduceTree(getChildren, f, accumulator, tree) {
const children = getChildren(tree);
if (!children || children.length === 0) {
return f(accumulator, tree)
} else {
const childrenResult = children.reduce(
(accumulator, subTree) => reduceTree(getChildren, f, accumulator, subTree),
f(accumulator,tree)
);
return childrenResult;
}
}
大概意思是:Javascript中列表的reduce操作应该是一个左折叠(根据维基百科的说法是这样的)。它执行先序树遍历,并等同于本帖底部的tfoldl函数。但用它实现map操作并不完全可行。
tmap f t = reduceTree (\acc val -> Node (f val) ???) ??? t
因为类型不适合
Node :: a -> [T a] -> T a
,无法使其与上述规约器类型
b -> a -> b
相匹配(它需要类型
a -> [b] -> b
)。
这是因为这种线性折叠本质上是将结构展平,将其视为一个“线性”序列。
接下来是一些冗余的阐述。
Haskell中的
reduceTree
函数与Aadit的答案中的函数
完全相同。约翰·休斯在他的著名论文“为什么函数式编程很重要”中也几乎是这样的。
foldTree :: (a -> b -> r) -> (r -> b -> b) -> b -> Tree a -> r
foldTree f g z (Node x t) = f x . foldr g z . map (foldTree f g z) $ t
他使用了一个相等的但略微冗长的表述,他称之为
redtree
,意思是“缩减树”。它的含义是:
foldTree f g z = reduceTree (\a rs -> f a (foldr g z rs))
所以这两者基本上是等价的。然后,
map h = reduceTree (Node . h)
= reduceTree (\a rs -> Node (h a) rs)
= foldTree (Node . h) (:) []
“零”即初始累加器值的缺失源于数据定义中第二个从句的缺失,即
data T a = Node a [T a]
,而不是列表的
List a = Nil | Cons a (List a)
。后者的折叠函数的归约器接受
Nil
或
Cons a r
作为输入,并将其转化为
r
,因此必须提供“零”即默认值;而前者的归约器将
Node a [r]
转化为
r
,因此没有需要处理的
Nil
情况(参见
recursion-schemes)。
根据评论中
用户Bergi提供的
线索,Haskell包
containers
为此类型定义了
可折叠实例。
data T a = Node a [T a]
其等效于(为方便起见,参数已翻转)
foldr
的是:
tfoldr :: (a -> b -> b) -> T a -> b -> b
tfoldr f (Node x ts) z = f x $ Data.List.foldr ($) z [tfoldr f t | t <- ts]
确实,线程穿过状态/累加器!它也可以写成:
tfoldr :: (a -> b -> b) -> T a -> b -> b
tfoldr f (Node x ts) z = f x . Data.List.foldr (.) id [tfoldr f t | t <- ts] $ z
无论哪种对你来说更容易实现。这是实现后序遍历树;对于通常的前序遍历,请使用。
tfoldl :: (a -> b -> b) -> T a -> b -> b
tfoldl f (Node x ts) z = Data.List.foldr (>>>) id [tfoldl f t | t <- ts] $ f x z
--
其中 (f >>> g) x = g (f x)
,或者
tfoldl :: (b -> a -> b) -> T a -> b -> b
tfoldl f (Node x ts) z = Data.List.foldr (>>>) id [tfoldl f t | t <- ts] $ f z x
--
这句话的意思是:它与本帖开头的代码等价,只是参数的顺序不同。
mapTree
的签名了吗? - BergireduceTree
的实现吗?我猜它目前并没有调用f
- 除非你把getChildren
改成类似于getContents :: T a -> (a, [T a])
这样的形式。 - Bergif
应该接受一个a
参数,而不是一个T a
。 - BergireduceTree
实现和伪代码。请注意,我的“树”只有一些值的节点和(可能为空的)子节点列表。 - Luftzig