使用折叠映射任意n-ary树

时间:2017-11-30 11:32:08

标签: javascript haskell tree functional-programming fold

我想要一些处理树木的通用工具。我正在使用JavaScript,所以我可以施加的很少,而且我正在使用我无法改变的现有数据结构。我设法定义了以下内容:

datepicker

(我正在使用Haskell类型签名,因为它们更容易阅读)

这个reduceTree :: (T a -> [T a]) -> (b -> T a -> b) -> b -> T a -> b reduceTree(getChildren, f, accumulator, tree) 函数是必需的,因为我的树是任意的,我对它的构造方式一无所知。

getChildren效果很好。但是我也希望有reduceTree函数,最好重用我的mapTree函数,但是我被卡住了。有些不对劲,但我无法弄清楚是什么。

修改

我的reduceTree实施:

reduceTree

经过测试和运作。

(我用来构建/证明上面的javascript的伪Haskell实现:

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),
      accumulator
    );
    return f(childrenResult, tree)
  }
}

2 个答案:

答案 0 :(得分:3)

我看到您的树数据结构定义如下:

$sql = "INSERT INTO 
user_table (id, username, email, password)
VALUES ('NULL', '".$username."', '".$email."', '".$password."')

INSERT INTO
character_table (name, id)
VALUES ('".$character."', 'LAST_INSERT_ID()')";"

如果是这种情况,那么树数据结构的折叠将是:

data T a = Node a [T a]

您现在可以使用reduceTree :: (a -> [b] -> b) -> T a -> b reduceTree f = let g (Node a xs) = f a (map g xs) in g 定义mapTree,如下所示:

reduceTree

将它全部转换为JavaScript:



mapTree :: (a -> b) -> T a -> T b
mapTree f = reduceTree (Node . f)




希望有所帮助。

答案 1 :(得分:3)

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)  // referring to `tree` only for its stored node value, yes?
    );
    return childrenResult;
  }
}

据推测,Javascript在列表中的reduce折叠(根据Wikipedia就是这样)。

它执行预订树遍历,相当于本文底部的tfoldl函数。用它来实现map并不是很有效,

tmap f t = reduceTree (\acc val -> Node (f val) ???) ??? t

因为类型不适合Node :: a -> [T a] -> T a,因此无法使其适合上面的减速器类型b -> a -> b(它需要类型a -> [b] -> b)。< / p>

这是因为这种线性折叠基本上是扁平化结构,将其视为线性序列。

接下来是一些无关紧要的阐述。

Haskell has it the exact same way作为Aadit's answer中的reduceTree函数。

John Hughes在他的着名论文&#34;为什么功能编程很重要&#34;

也有同样的方式
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,以及#34;减少树&#34;。它认为

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) (:) [] 

没有&#34;零&#34;即,初始累加器值来自数据定义中的第二个子句,data T a = Node a [T a]而不是List a = Nil | Cons a (List a),用于列表。

后者的fold的reducer函数需要NilCons a rr,因此它必须具有&#34; 0&#34;即提供给它的侮辱价值;对于前者,需要Node a [r]r,因此无法处理Nil个案件(参见)。

在评论中从a hint关注user Bergi后,Haskell软件包containers为此类型定义了a Foldable instance

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
                 -- // = tfoldl f tn (... (tfoldl f t2 (tfoldl f t1 (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
                 -- // = tfoldl f tn (... (tfoldl f t2 (tfoldl f t1 (f z x))) ...)

这相当于本文开头的代码,直到参数的顺序。