【问题标题】:How to apply a function to all elements of a tree?如何将函数应用于树的所有元素?
【发布时间】:2020-02-04 15:51:23
【问题描述】:
data RoseTree a = RoseNode a [RoseTree a] deriving Show

things :: RoseTree String
things = 
    RoseNode "thing" [
        RoseNode "animal" [
            RoseNode "cat" [], RoseNode "dog" []
        ],

        RoseNode "metal" [
            RoseNode "alloy" [
                RoseNode "steel" [], RoseNode "bronze" []
            ],
            RoseNode "element" [
                RoseNode "gold" [], RoseNode "tin" [], RoseNode "iron" []
            ]
        ],       
    ] 
-- Turns string into all upper case
allCaps :: String -> String
allCaps x = map toUpper x
-- This function uses allCaps as a helper function 
--  to turn the elements in tree into upper case
roseMap :: (a -> b) -> RoseTree a -> RoseTree b
roseMap  f  rtree = case rtree of
    RoseNode a []    ->  allCaps a
    RoseNode a sub   ->  Rose (allCaps a) (map (roseMap f sub)

接受一个函数,并将其应用于玫瑰树的每个元素。通过将函数allCaps 映射到玫瑰树things 来测试结果。现在所有元素都应该用大写字母书写。

我不知道如何使用递归来编写这个函数。

【问题讨论】:

  • 提示:玫瑰树顶部的值应该如何处理,每个子树应该如何处理?
  • 它应该首先更改玫瑰树顶部的元素,然后将相同的函数应用于其余元素,但我不知道如何将其转换为函数。跨度>

标签: haskell recursion tree map-function


【解决方案1】:

已经在使用递归了。实际上,您使用 roseMap f 本身:

roseMap :: (a -> b) -> RoseTree a -> RoseTree b
roseMap f  rtree = case rtree of
    RoseNode a [] -> allCaps a
    RoseNode a sub -> Rose (allCaps a) (map (roseMap f sub))

但由于以下几个原因,上述方法不起作用:

  1. 你不要在这里使用f。确实你写了Rose (allCaps a),所以你不用f来映射元素;
  2. sub 不应roseMap f 的括号中传递;
  3. 您应该使用RoseNode 而不是Rose;和
  4. 您的第一个案例,您返回 allCaps a,而不是 RoseNode (allCaps a) []

没有必要区分没有子节点或有子节点的节点。我们可以将映射定义为:

roseMap :: (a -> b) -> RoseTree a -> RoseTree b
roseMap f (RoseNode a xs) = RoseNode (f a) (map (roseMap f) xs)

所以这里我们使用f a 代替,并且我们对孩子执行映射。

如果我们随后以allCaps 作为函数执行roseMap,我们得到:

Prelude Data.Char> roseMap allCaps things
RoseNode "THING" [RoseNode "ANIMAL" [RoseNode "CAT" [],RoseNode "DOG" []],RoseNode "METAL" [RoseNode "ALLOY" [RoseNode "STEEL" [],RoseNode "BRONZE" []],RoseNode "ELEMENT" [RoseNode "GOLD" [],RoseNode "TIN" [],RoseNode "IRON" []]],RoseNode "FRUIT" [RoseNode "APPLE" [RoseNode "GRANNY SMITH" [],RoseNode "PINK LADY" []],RoseNode "BANANA" [],RoseNode "ORANGE" []],RoseNode "ASTRONOMICAL OBJECT" [RoseNode "PLANET" [RoseNode "EARTH" [],RoseNode "MARS" []],RoseNode "STAR" [RoseNode "THE SUN" [],RoseNode "SIRIUS" []],RoseNode "GALAXY" [RoseNode "MILKY WAY" []]]]

我们不需要自己实现映射,我们可以启用DeriveFunctor extension [ghc-doc],让Haskell为我们做这项工作:

{-# LANGUAGE DeriveFunctor #-}

data RoseTree a = RoseNode a [RoseTree a] deriving (Functor, Show)

我们可以用fmap :: Functor f => (a -> b) -> f a -> f b 调用它:

Prelude Data.Char> fmap (map toUpper) things
RoseNode "THING" [RoseNode "ANIMAL" [RoseNode "CAT" [],RoseNode "DOG" []],RoseNode "METAL" [RoseNode "ALLOY" [RoseNode "STEEL" [],RoseNode "BRONZE" []],RoseNode "ELEMENT" [RoseNode "GOLD" [],RoseNode "TIN" [],RoseNode "IRON" []]],RoseNode "FRUIT" [RoseNode "APPLE" [RoseNode "GRANNY SMITH" [],RoseNode "PINK LADY" []],RoseNode "BANANA" [],RoseNode "ORANGE" []],RoseNode "ASTRONOMICAL OBJECT" [RoseNode "PLANET" [RoseNode "EARTH" [],RoseNode "MARS" []],RoseNode "STAR" [RoseNode "THE SUN" [],RoseNode "SIRIUS" []],RoseNode "GALAXY" [RoseNode "MILKY WAY" []]]]

【讨论】:

  • 感谢您的回答。我还有一个问题。 haskell 的递归是很难想象的,我的意思是我无法想象递归的实现过程。所以写一个正确的递归表达式很难。我可以在纸上扩展函数的执行,或者类似的东西,让我更好地理解递归。
  • Haskell 中的递归与任何其他语言中的递归没有什么不同。它在 Haskell(以及一般的函数式语言)中更为常见,因为它通常是完成在命令式语言中使用循环的唯一方法,并且因为它在 Haskell 中比在命令式语言中更有效. (我无法详细解释原因,但懒惰是其中很大一部分。)
  • 至于递归在这种特定情况下的工作方式:玫瑰树由a 类型的标签和其他(子)树的列表组成。将roseMap f 应用于这样的树只需将a 替换为f a,并将每个子树sub 替换为roseMap f sub。当子树列表为空时,递归最终结束(至少在实践中,理论上不需要)。如果您尝试使用示例树或更简单的树来解决此问题,我相信您很快就会理解它的工作原理。
猜你喜欢
  • 2023-01-24
  • 2017-03-09
  • 2012-12-25
  • 1970-01-01
  • 2017-09-03
  • 2016-02-20
相关资源
最近更新 更多