【问题标题】:Variations of folds on Haskell TreesHaskell 树上的褶皱变化
【发布时间】:2018-09-21 22:54:24
【问题描述】:

给定一棵树定义为:

data Tree a = Leaf | Node (Tree a) a (Tree a) deriving (Eq, Show)

我要使用该功能:

foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b
foldTree _ b Leaf         = b
foldTree f b (Node lt x rt) = f (foldTree f b lt) x (foldTree f b rt)

为了能够创建普通foldrfoldl 的等效项:

foldTreeR :: (a -> b -> b) -> b -> Tree a -> b
foldTreeL :: (b -> a -> b) -> b -> Tree a -> b

我认为这些会相当简单,因为它们的定义几乎完全模仿了 foldrfoldl 的定义。我假设我所要做的就是以类似的方式插入值,所以我会编写一个匿名函数,一个带有我的树的基本状态和需要处理的树的累加器。 lambda 函数必须根据所执行的折叠类型而有所不同。

这是我想出的:

foldTreeR :: (a -> b -> b) -> b -> Tree a -> b
foldTreeR f acc t =  foldTree (\x acc -> f x acc) acc t

我得到错误:

Couldn't match type ‘a’ with ‘a -> b’ 
      ‘a’ is a rigid type variable bound by
        the type signature for:
          foldTreeR :: forall a b. (a -> b -> b) -> b -> Tree a -> b
        at Folds.hs:294:14
      Expected type: Tree (a -> b)
        Actual type: Tree a

我不确定在这种情况下我应该如何传递原始树。

似乎左折叠只是一个变体,lambda函数中的值重新排序以及评估不同。

有人可以帮助我了解如何在这里找到解决方案吗?

【问题讨论】:

  • foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b 期望第一个参数是一个接受 3 个参数的函数。你给了它一个带有 2 个参数的函数。基本上错误说它不能使一个值(f x acc 的结果)成为一个函数(b -> a -> (b -> b) 类型的函数的预期结果,相当于b -> a -> b -> b
  • 我明白这一点,但我不完全确定在这种情况下我会作为第三个值传递什么。首先是它自己的树的值,其次是累加器。第三个可能在这里?

标签: haskell functional-programming binary-tree fold


【解决方案1】:

我们可以通过折叠到 endofunctions 中,从数据类型遵循树形折叠中恢复线性的、累加器传递折叠,如下所示:

data Tree a = Leaf | Node (Tree a) a (Tree a) deriving (Eq, Show)

-- foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b

foldTreeR :: (a -> r -> r) -> r -> Tree a -> r
foldTreeR cons z t = foldTree g id t z           -- b ~ r -> r
  where
  g lt a rt = lt . cons a . rt 

还有左折:

foldTreeL :: (acc -> a -> acc) -> acc -> Tree a -> acc
foldTreeL conj z t = foldTree g id t z           -- b ~ acc -> acc
  where
  g lt a rt = rt . flip conj a . lt

更详细的解释:

cons aflip conj a 都有类型 r -> r(或 acc -> acc,相同)。这是参数类型和结果相同的函数类型。

这样的函数被称为 endofunctions,endo 表示它们的域和共域(箭头右侧和左侧的类型)的相同性。因此,它们组合很容易:可以参与(.)操作,即函数组合,组合的结果与操作数的类型相同:

(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)

-- and for endofunctions,
-- ((f :: r -> r) . (g :: r -> r)) :: r -> r

对于有序遍历[a,b,c,...,n] 的树,右折叠将该树变成一个组合

(cons a . cons b . cons c . ..... . cons n) z

-- which is the same as
-- cons a (cons b (cons c ... (cons n z) ... ))

左折叠变成了

(conj' n . ..... . conj' c . conj' b . conj' a) z

在哪里

conj' a acc = flip conj a acc = conj acc a     -- by definition of `flip`

-- so the above composition chain is the same as
-- conj (... (conj (conj (conj z a) b) c) ...) n

一些ids 散布在该链周围,每个Leaf 都变成id,对整个链没有影响,因为

(id . f) x = id (f x) = f x = f (id x) = (f . id) x

所以

id . f = f = f . id

id 用作 w.r.t 的“零”元素。对于函数组合操作,就像0+ 操作所做的那样(顺便说一下,这被称为由.id 组成的'monoid',或0+)。


以下是我们为树创建有序遍历列表的方法:

inorder :: Tree a -> [a]
inorder t = foldTree g [] t
  where
  g lt a rt = lt ++ [a] ++ rt

所以列表[a,b,...,n]实际上创建为

[] ++ [a] ++ [] ++ [b] ++ [] ++ ... ++ [n] ++ []

并且该树的右折叠将被创建为

(id . cons a . id . cons b . id . .... . cons n . id) z

【讨论】:

    【解决方案2】:

    以下是您自己想出解决方案的方法。

    我们有

    data Tree a =                               Leaf 
                | Node (Tree a) a (Tree a)                   deriving (Eq, Show)
    
    foldTree :: (        b ->   a ->   b -> b) -> b -> Tree a -> b
    
    foldTree    (g :: b ->   a ->   b -> b) (z :: b) :: Tree a -> b
    

    因此,给定gz,此函数通过将t 的子树转换为bs 并将它们与树的a 组合,将Tree a 值转换为b 值,通过g

    我们可以map在这些树上使用foldTree吗?是的:

    mapTree :: (a -> c) -> Tree a -> Tree c
    mapTree f t = foldTree g z t
      where
      -- we need to create a Node with the mapped element inside it
      -- already having the transformed sub-trees. Well, 
      -- creating Tree values is the job of that type's data constructors:
      g lt a rt = Node lt (f a) rt      -- f is applied to the element `a`
      -- and all leaves are transformed into the same value, which is:
      z = Leaf
    

    所以现在我们有了mapTree (f :: a -> c) :: Tree a -> Tree c。这对我们有什么帮助?

    我们想要什么?我们想要

    foldTreeR :: (a -> b -> b) -> b -> Tree a -> b
    -- i.e.
    foldTreeR (cons :: a -> r -> r) (nil :: r) :: Tree a -> r
    

    所以我们有cons,这样cons (x :: a) :: r -> r

    如果我们将这个cons映射到一棵树上会怎样? a -> r -> r 类型实际上是 a -> (r -> r),实际上我们刚刚看到 cons(x :: a) 转换为 r -> r阅读:将值 x 或类型 a 转换为值类型为r -> r):

    mapTree (cons :: a -> r -> r) :: Tree a -> Tree (r -> r)
    

    我们为什么要那个?我们可以用树节点中的所有r -> r 函数做什么?好吧,我们可以通过中序遍历将树变成其节点中的值列表:

    inorder :: Tree d -> [d]
    inorder t = foldTree (\l a r -> l ++ a : r) [] t
    

    所以我们可以拥有

    inorder . mapTree (cons :: a -> r -> r) :: [r -> r]
              -----------------
              Tree a -> Tree (r -> r)
    --------
    Tree (r->r) -> [r->r]
    

    我们可以在该列表中组合所有这些函数,线性,以安排从右到左的结果传递操作, em> ... 正确的折叠!

    foldTreeR_ :: (a -> r -> r) -> r -> Tree a -> r
    foldTreeR_ cons z t = foldr ($) z (inorder $ mapTree cons t)
               -- or, equivalently,
               --         foldr (.) id (inorder $ mapTree cons t) z
    

    就是这样。

    如果我们内联所有内容并简化结果定义,我们会在另一个答案中得到一个。

    左折叠也是如此。试试看。

    【讨论】:

      猜你喜欢
      • 2017-12-05
      • 1970-01-01
      • 1970-01-01
      • 2021-02-07
      • 2013-05-16
      • 1970-01-01
      • 2011-01-15
      • 2016-12-17
      • 2015-08-29
      相关资源
      最近更新 更多