想象一下mapAccumR的工作原理

时间:2017-06-15 03:53:10

标签: haskell

你能解释一下mapAccumR究竟是如何运作的,它解决了哪些问题,以及它与foldr的区别。我很难想象它是如何运作的。

2 个答案:

答案 0 :(得分:4)

这是一个很好的问题。我希望文档对此有点好。我最近自己有一个用途,所以希望我能从那些在理解它们如何工作方面遇到一些麻烦的人的角度来解释。

因此,mapAccumR的类型签名是:

Traversable t => (a -> b -> (a, c)) -> a -> t b -> (a, t c)

让我们假设问题中的Traversable是一个列表,因为这样可能更容易理解,因此专门化类型:

(a -> b -> (a, c)) -> a -> [b] -> (a, [c])

所以,为了解释这一点,mapAccumR是三个参数的函数(忽略currying,就像我们为简单解释所做的那样),我将在这里注释这些参数:

mapAccumR :: (a -> b -> (a, c))             -> a                     -> [b]           -> (a, [c])
mapAccumR :: mappingAndAccumulationFunction -> initialAccumulatorValue -> listToMapOver -> resultantAccumulatorAndMappedListPair

很酷,所以清除位的东西,但它仍然有点令人困惑,对吧。那它究竟做了什么呢?

嗯,它有一个累积的地图:所以我们在第一步说,它的作用是从initialAccumulatorValue获取b和第一个listToMapOver,然后传递给它们mappingAndAccumulationFunction函数,它将对它们执行某些事情并返回两件事:1。类型a的新值和2.映射值,以便以后集合到映射中list(参见resultantAccumulatorAndMappedListPair的类型)。这两个值是成对的,因此mappingAndAccumulationFunction函数的返回类型为(a, c)

在第二步和后续步骤中,它会从最后一步开始使用(a, c)对,将c拉出并通过将其附加到内部列表来记住它,直到结束为止,并将a作为第一个参数拉出mappingAndAccumulationFunction的下一个应用以及b的下一个listToMapOver值。

一旦b中的listToMapOver值用尽,它就会返回一个最后一个值为a的对,以及一个内容类型为c的列表。

那你为什么要这个功能呢?示例时间!

annotateLeastFavourites items = snd (mapAccumR (\num item -> (num + 1, show num ++ ": " ++ item)) 1 items)

itemList = ["Geese","Monkeys","Chocolate","Chips"]

> annotateLeastFavourites itemList
["4: Geese","3: Monkeys","2: Chocolate","1: Chips"]

或者,或许这看起来有点简单:

> mapAccumR (\num item -> (num + 1, show num ++ ": " ++ item)) 1 ["Geese", "Monkeys", "Chocolate", "Chips"]
(5,["4: Geese","3: Monkeys","2: Chocolate","1: Chips"])

因此,我们可以看到,只要我们需要一些信息传递给地图,或者我们想要建立一个集合值,它就可以为我们提供“累积值”以及累积值。在右边),还需要传递信息,每个步骤都会改变(左边的值)。

也许你想得到一个项目列表的最大长度,因为你还用每个项目的长度注释它们

> mapAccumR (\biggestSoFar item -> (max biggestSoFar (length item), (item, length item))) 0 ["Geese", "Monkeys", "Chocolate", "Chips"]
(9,[("Geese",5),("Monkeys",7),("Chocolate",9),("Chips",5)])

这里有很多可能性。希望现在很明显为什么人们说这就像是mapfoldr的组合。如果你碰巧像我一样思考几何,我认为它就像你需要转换某种类型的集合一样,并且你需要通过该集合来处理一些变化的东西,作为转换的一部分。

希望这有助于给你一种直觉并将这种模式存储在你的脑海中,以便以后当你意识到将来可能需要它时:)

let (_, result) =
  mapAccumR 
    (\cumulativeLength item -> 
      let newLength = cumulativeLength + length item 
      in (newLength, take cumulativeLength (repeat ' ') ++ item)
    )
    0
    ["Geese", "Monkeys", "Chocolate", "Chips", "Dust", "Box"]
in mapM_ putStrLn $ reverse result

Box
   Dust
       Chips
            Chocolate
                     Monkeys
                            Geese

有时,根据您想要的计算形状,您需要使用mapAccumL代替mapAccumR,但是您可以了解相关信息。

另外,请注意,它是为Traversable个实例定义的,而不仅仅是列表,因此它适用于各种可遍历的容器和数据结构,如树,地图,矢量等。

答案 1 :(得分:1)

Here are some examples, generated using Debug.SimpleReflect.

Below, f is the same f you would use in a foldr, except the arguments have been flipped. Otherwise, there's no difference.

Instead g is similar to what you would use in a map, except g x y does not only depend on the current list element y, but also on the results of the former fold x.

> import Data.List
> import Debug.SimpleReflect
> mapAccumR (\x y -> (f x y, g x y)) a [] :: (Expr, [Expr])
(a,[])
> mapAccumR (\x y -> (f x y, g x y)) a [b] :: (Expr, [Expr])
(f a b,[g a b])
> mapAccumR (\x y -> (f x y, g x y)) a [b,c] :: (Expr, [Expr])
(f (f a c) b,[g (f a c) b,g a c])
> mapAccumR (\x y -> (f x y, g x y)) a [b,c,d] :: (Expr, [Expr])
(f (f (f a d) c) b,[g (f (f a d) c) b,g (f a d) c,g a d])

Here is a foldr with f having its arguments flipped, by comparison.

> foldr (\x y -> f y x) a [b,c,d]
f (f (f a d) c) b

(I have no idea about why mapAccumR chose the arguments of f in the flipped order compared to foldr.)