你能解释一下mapAccumR
究竟是如何运作的,它解决了哪些问题,以及它与foldr
的区别。我很难想象它是如何运作的。
答案 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)])
这里有很多可能性。希望现在很明显为什么人们说这就像是map
和foldr
的组合。如果你碰巧像我一样思考几何,我认为它就像你需要转换某种类型的集合一样,并且你需要通过该集合来处理一些变化的东西,作为转换的一部分。
希望这有助于给你一种直觉并将这种模式存储在你的脑海中,以便以后当你意识到将来可能需要它时:)
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
.)