映射同时显示中间状态
Mapping while showing intermediate states
我需要一个函数来执行此操作:
>>> func (+1) [1,2,3]
[[2,2,3],[2,3,3],[2,3,4]]
我的实际情况更复杂,但这个例子说明了问题的要点。主要区别是在现实中使用索引是不可行的。 List
应该是 Traversable
或 Foldable
.
编辑:这应该是函数的签名:
func :: Traversable t => (a -> a) -> t a -> [t a]
更接近我真正想要的是与 traverse
相同的签名,但无法弄清楚我必须使用的函数才能获得所需的结果。
func :: (Traversable t, Applicative f) :: (a -> f a) -> t a -> f (t a)
在列表中,我会使用以下内容。如果不需要,请随意丢弃第一个元素。
> let mymap f [] = [[]] ; mymap f ys@(x:xs) = ys : map (f x:) (mymap f xs)
> mymap (+1) [1,2,3]
[[1,2,3],[2,2,3],[2,3,3],[2,3,4]]
这也适用于 Foldable
,当然,在使用 toList
将可折叠对象转换为列表之后。但是,人们可能仍然想要一个更好的实现来避免该步骤,特别是如果我们想要保留原始的可折叠类型,而不仅仅是获取列表。
根据你的问题,我只是将其命名为 func
,因为我想不出更好的名字。
import Control.Monad.State
func f t = [evalState (traverse update t) n | n <- [0..length t - 1]]
where update x = do
n <- get
let y = if n == 0 then f x else x
put (n-1)
return y
想法是 update
从 n
开始倒计时,当它达到 0 时我们应用 f
。我们将 n
保持在状态 monad 中,以便 traverse
可以在您走过可遍历的时候探测 n
。
ghci> func (+1) [1,1,1]
[[2,1,1],[1,2,1],[1,1,2]]
您可能可以使用 mapAccumL
来节省一些击键次数,这是一个捕获状态 monad 中遍历模式的 HOF。
这听起来有点像没有重点的拉链;也许是这样的:
data Zippy a b = Zippy { accum :: [b] -> [b], rest :: [a] }
mapZippy :: (a -> b) -> [a] -> [Zippy a b]
mapZippy f = go id where
go a [] = []
go a (x:xs) = Zippy b xs : go b xs where
b = a . (f x :)
instance (Show a, Show b) => Show (Zippy a b) where
show (Zippy xs ys) = show (xs [], ys)
mapZippy succ [1,2,3]
-- [([2],[2,3]),([2,3],[3]),([2,3,4],[])]
(为了效率这里使用差异列表)
转换成折叠看起来有点像 paramorphism:
para :: (a -> [a] -> b -> b) -> b -> [a] -> b
para f b [] = b
para f b (x:xs) = f x xs (para f b xs)
mapZippy :: (a -> b) -> [a] -> [Zippy a b]
mapZippy f xs = para g (const []) xs id where
g e zs r d = Zippy nd zs : r nd where
nd = d . (f e:)
对于任意遍历,有一个很酷的时间旅行状态转换器,叫做 Tardis,它可以让你向前和向后传递状态:
mapZippy :: Traversable t => (a -> b) -> t a -> t (Zippy a b)
mapZippy f = flip evalTardis ([],id) . traverse g where
g x = do
modifyBackwards (x:)
modifyForwards (. (f x:))
Zippy <$> getPast <*> getFuture
@Benjamin Hodgson 似乎误读了您的问题,并认为您希望 f
应用于每个部分结果中的单个元素。因此,您最终认为他的方法不适用于您的问题,但我认为适用。考虑以下变体:
import Control.Monad.State
indexed :: (Traversable t) => t a -> (t (Int, a), Int)
indexed t = runState (traverse addIndex t) 0
where addIndex x = state (\k -> ((k, x), k+1))
scanMap :: (Traversable t) => (a -> a) -> t a -> [t a]
scanMap f t =
let (ti, n) = indexed (fmap (\x -> (x, f x)) t)
partial i = fmap (\(k, (x, y)) -> if k < i then y else x) ti
in map partial [1..n]
在这里,indexed
在状态 monad 中运行,以向可遍历对象的元素添加递增索引(并获取长度 "for free",无论这意味着什么):
> indexed ['a','b','c']
([(0,'a'),(1,'b'),(2,'c')],3)
而且,正如 Ben 所指出的,也可以使用 mapAccumL
:
来编写
indexed = swap . mapAccumL (\k x -> (k+1, (k, x))) 0
然后,scanMap
获取可遍历对象,将其映射到类似的before/after对结构,使用indexed
对其进行索引,并应用一系列partial
函数,其中 partial i
为前 i
个元素选择 "afters",为其余元素选择 "befores"。
> scanMap (*2) [1,2,3]
[[2,2,3],[2,4,3],[2,4,6]]
至于将此从列表推广到其他东西,我无法弄清楚你想用你的第二个签名做什么:
func :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)
因为如果你将它专门化为一个列表,你会得到:
func' :: (Traversable t) => (a -> [a]) -> t a -> [t a]
并不清楚你想要它在这里做什么。
我需要一个函数来执行此操作:
>>> func (+1) [1,2,3]
[[2,2,3],[2,3,3],[2,3,4]]
我的实际情况更复杂,但这个例子说明了问题的要点。主要区别是在现实中使用索引是不可行的。 List
应该是 Traversable
或 Foldable
.
编辑:这应该是函数的签名:
func :: Traversable t => (a -> a) -> t a -> [t a]
更接近我真正想要的是与 traverse
相同的签名,但无法弄清楚我必须使用的函数才能获得所需的结果。
func :: (Traversable t, Applicative f) :: (a -> f a) -> t a -> f (t a)
在列表中,我会使用以下内容。如果不需要,请随意丢弃第一个元素。
> let mymap f [] = [[]] ; mymap f ys@(x:xs) = ys : map (f x:) (mymap f xs)
> mymap (+1) [1,2,3]
[[1,2,3],[2,2,3],[2,3,3],[2,3,4]]
这也适用于 Foldable
,当然,在使用 toList
将可折叠对象转换为列表之后。但是,人们可能仍然想要一个更好的实现来避免该步骤,特别是如果我们想要保留原始的可折叠类型,而不仅仅是获取列表。
根据你的问题,我只是将其命名为 func
,因为我想不出更好的名字。
import Control.Monad.State
func f t = [evalState (traverse update t) n | n <- [0..length t - 1]]
where update x = do
n <- get
let y = if n == 0 then f x else x
put (n-1)
return y
想法是 update
从 n
开始倒计时,当它达到 0 时我们应用 f
。我们将 n
保持在状态 monad 中,以便 traverse
可以在您走过可遍历的时候探测 n
。
ghci> func (+1) [1,1,1]
[[2,1,1],[1,2,1],[1,1,2]]
您可能可以使用 mapAccumL
来节省一些击键次数,这是一个捕获状态 monad 中遍历模式的 HOF。
这听起来有点像没有重点的拉链;也许是这样的:
data Zippy a b = Zippy { accum :: [b] -> [b], rest :: [a] }
mapZippy :: (a -> b) -> [a] -> [Zippy a b]
mapZippy f = go id where
go a [] = []
go a (x:xs) = Zippy b xs : go b xs where
b = a . (f x :)
instance (Show a, Show b) => Show (Zippy a b) where
show (Zippy xs ys) = show (xs [], ys)
mapZippy succ [1,2,3]
-- [([2],[2,3]),([2,3],[3]),([2,3,4],[])]
(为了效率这里使用差异列表)
转换成折叠看起来有点像 paramorphism:
para :: (a -> [a] -> b -> b) -> b -> [a] -> b
para f b [] = b
para f b (x:xs) = f x xs (para f b xs)
mapZippy :: (a -> b) -> [a] -> [Zippy a b]
mapZippy f xs = para g (const []) xs id where
g e zs r d = Zippy nd zs : r nd where
nd = d . (f e:)
对于任意遍历,有一个很酷的时间旅行状态转换器,叫做 Tardis,它可以让你向前和向后传递状态:
mapZippy :: Traversable t => (a -> b) -> t a -> t (Zippy a b)
mapZippy f = flip evalTardis ([],id) . traverse g where
g x = do
modifyBackwards (x:)
modifyForwards (. (f x:))
Zippy <$> getPast <*> getFuture
@Benjamin Hodgson 似乎误读了您的问题,并认为您希望 f
应用于每个部分结果中的单个元素。因此,您最终认为他的方法不适用于您的问题,但我认为适用。考虑以下变体:
import Control.Monad.State
indexed :: (Traversable t) => t a -> (t (Int, a), Int)
indexed t = runState (traverse addIndex t) 0
where addIndex x = state (\k -> ((k, x), k+1))
scanMap :: (Traversable t) => (a -> a) -> t a -> [t a]
scanMap f t =
let (ti, n) = indexed (fmap (\x -> (x, f x)) t)
partial i = fmap (\(k, (x, y)) -> if k < i then y else x) ti
in map partial [1..n]
在这里,indexed
在状态 monad 中运行,以向可遍历对象的元素添加递增索引(并获取长度 "for free",无论这意味着什么):
> indexed ['a','b','c']
([(0,'a'),(1,'b'),(2,'c')],3)
而且,正如 Ben 所指出的,也可以使用 mapAccumL
:
indexed = swap . mapAccumL (\k x -> (k+1, (k, x))) 0
然后,scanMap
获取可遍历对象,将其映射到类似的before/after对结构,使用indexed
对其进行索引,并应用一系列partial
函数,其中 partial i
为前 i
个元素选择 "afters",为其余元素选择 "befores"。
> scanMap (*2) [1,2,3]
[[2,2,3],[2,4,3],[2,4,6]]
至于将此从列表推广到其他东西,我无法弄清楚你想用你的第二个签名做什么:
func :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)
因为如果你将它专门化为一个列表,你会得到:
func' :: (Traversable t) => (a -> [a]) -> t a -> [t a]
并不清楚你想要它在这里做什么。