这与这个问题有关:
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,产生一个状态单位。
答案 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)
对于您的示例,您根本不需要可变向量,您可以使用以下未经测试的代码中的map
或generate
:
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
之前分配结果向量,而不是在通话期间,请参阅弗拉基米尔的回答。