我如何使用可变载体编写imapInto?

时间:2012-07-18 17:13:34

标签: haskell

这与这个问题有关:

Don't know where to start with mutable Vectors

我如何编写一个从一个向量中取值,转换它们并将结果放入第二个向量的函数?更具体地说,它应该迭代源数组的所有索引,并将该索引和数组交给函数,然后将函数的结果存储在另一个数组中。

我认为签名看起来像这样:

imapInto :: (PrimMonad m, Unbox a) => (MVector (PrimState m) a -> Int -> a) -> MVector (PrimState m) a -> MVector (PrimState m) a  -> m ()

会被称为这样的事情:

import Data.Vector.Unboxed.Mutable MV

...
let dst = MV.replicate 10 0.0 in
let src = MV.replicate 10 1.0 in
imapInto (\src' i -> (src' * 2.0)) dst src
...

将src中的所有元素乘以2并将结果放入dst,产生一个状态单位。

2 个答案:

答案 0 :(得分:5)

我认为没有内置功能可以满足您的需求。但是使用these原语,应该可以为您的任务编写命令式代码。如果它足够通用,你甚至可以在create的纯代码中使用它。 我想它会像(只是草图,未经测试)这样:

imapInto :: (PrimMonad m, Unbox a) => (MVector (PrimState m) a -> Int -> a) -> MVector (PrimState m) a -> MVector (PrimState m) a -> m ()
imapInto f d s = go 0
    where
        go i = when (i < length d) $ write d i (f s i) >> go (i+1)

你的映射函数类型看起来很奇怪。你的意思是a -> Int -> a吗?那么上面的代码将需要一些小的改动。

<强>更新

以下是使用示例以及上述功能的更新版本。最不可能的调整是将m类型构造函数添加到mapper函数:

module Main where

import qualified Data.Vector.Unboxed.Mutable as MV
import qualified Data.Vector.Unboxed as U
import Control.Monad (forM_)
import Control.Monad.Primitive

imapInto :: (PrimMonad m, MV.Unbox a) 
         => (MV.MVector (PrimState m) a -> Int -> m a)
         -> MV.MVector (PrimState m) a 
         -> MV.MVector (PrimState m) a 
         -> m ()
imapInto f d s = forM_ [0..MV.length s - 1] $ \i -> do
    v <- f s i
    MV.write d i v

main = do
    -- Create two vectors
    v1 <- MV.replicate 10 1
    v2 <- MV.new 10
    -- Map v1 into v2 using function mapper defined below
    imapInto mapper v2 v1
    -- Print the source and the result
    uv1 <- U.unsafeFreeze v1
    uv2 <- U.unsafeFreeze v2
    print $ U.toList uv1   -- [1,1,1,1,1,1,1,1,1,1]
    print $ U.toList uv2   -- [0,1,2,3,4,5,6,7,8,9]

    where
        -- Mapper reads a value from the vector and multiplies it by its index
        mapper v i = fmap (*i) $ MV.read v i

由于你是初学者,我试着尽可能简单,问一下是不是很清楚。

正如您所看到的,我利用路易斯的评论并使用forM_函数使imapInto更加简单(forM_mapM_并且参数被交换了)。它现在看起来像命令式语言的普通for循环。另外正如我所说,我将映射函数类型从(MVector (PrimState m) a -> Int -> a)更改为(MVector (PrimState m) a -> Int -> m a)。这是必需的,因为你不能在它的monad之外使用一个可变向量(它作为第一个参数传递)。但是在monad中我们可以用它做任何事情,在这里我们只是阅读i - 元素并将其乘以i

请注意,在此版本的函数中,您可以使用映射函数内部的源向量执行任何操作。如果您不需要此项,并且只将i - 元素与i本身结合使用,则可以进一步简化此操作:

imapInto' :: (PrimMonad m, MV.Unbox a, MV.Unbox b) 
         => (a -> Int -> b)
         -> MV.MVector (PrimState m) b
         -> MV.MVector (PrimState m) a
         -> m ()
imapInto' f d s = forM_ [0..MV.length s - 1] $ \i -> do
    v <- MV.read s i
    MV.write d i (f v i)

这里映射函数是纯函数,仅将i - 元素作为第一个参数,而不是向量本身。此外,我已经推广了这个版本,因此它可以将第一个矢量映射到另一个类型的矢量,因此源和目标矢量不需要是相同的类型。这也可以在以前的版本中完成。

答案 1 :(得分:1)

对于您的示例,您根本不需要可变向量,您可以使用以下未经测试的代码中的mapgenerate

import Data.Vector.Unboxed as V
import Data.Vector.Unboxed.Mutable as M

double :: V.Vector Int -> V.Vector Int
double = V.map (*2)

如果你想使用索引:

addIndex = V.imap (\element idx -> element + idx)

构造不可变向量后,您可以使用unsafeThaw(假设您知道没有对此向量的其他引用)来获取O(1)中的MVector。所以你的最终“imapInto”看起来像是:

imapInto :: some constraints => (V.Vector a -> Int -> b) -> V.Vector a -> M.Vector b
imapInto op v = V.unsafeThaw (V.imap op v)

如果您真的希望在调用imapInto之前分配结果向量,而不是在通话期间,请参阅弗拉基米尔的回答。