使用生成将值散布到单独的向量中
Intersperse values into separate Vectors using generate
我正在尝试通过使用从索引创建值的自定义数据类型(或元组)的函数来生成 Vector
的元组。这是一种可以达到预期结果的方法:
import Prelude hiding (map, unzip)
import Data.Vector hiding (map)
import Data.Array.Repa
import Data.Functor.Identity
data Foo = Foo {fooX :: Int, fooY :: Int}
unfoo :: Foo -> (Int, Int)
unfoo (Foo x y) = (x, y)
make :: Int -> (Int -> Foo) -> (Vector Int, Vector Int)
make n f = unzip $ generate n getElt where
getElt i = unfoo $ f i
除了我想在每个 Vector 的单次迭代中完成它,几乎如下所示,但避免对函数进行多次评估 f
:
make' :: Int -> (Int -> Foo) -> (Vector Int, Vector Int)
make' n f = (generate n getElt1, generate n getElt2) where
getElt1 i = fooX $ f i
getElt2 i = fooY $ f i
请注意,我了解到 Vector
库支持融合,第一个示例已经非常高效。我需要一个 generate
概念的解决方案,其他库有非常相似的构造函数(例如 Repa 有 fromFunction
),我在这里使用 Vector
只是为了演示一个问题。
也许某种 f
函数调用的记忆会起作用,但我想不出任何东西。
编辑:
另一个使用 Repa 的问题演示:
makeR :: Int -> (Int -> Foo) -> (Array U DIM1 Int, Array U DIM1 Int)
makeR n f = runIdentity $ do
let arr = fromFunction (Z :. n) (\ (Z :. i) -> unfoo $ f i)
arr1 <- computeP $ map fst arr
arr2 <- computeP $ map snd arr
return (arr1, arr2)
与向量相同,融合在性能上节省了时间,但仍然需要元组的中间数组arr
,这是我试图避免的。
编辑 2:(3 年后)
在上面的 Repa 示例中,它不会创建中间数组,因为 fromFunction
创建延迟数组。相反,它会更糟,它会为每个索引计算两次 f
,一次用于第一个数组,第二次用于第二个数组。为了避免这种重复工作,必须计算延迟数组。
您要写的基本内容是
splat f = unzip . fmap f
在两个结果向量之间共享计算 f
的结果,但您想避免使用中间向量。不幸的是,我很确定你不能在任何有意义的意义上同时拥有这两种方式。为简单起见,考虑一个长度为 1 的向量。为了让结果向量共享 f (v ! 0)
的结果,每个向量都需要引用一个表示该结果的 thunk。好吧,那个 thunk 必须 是 某个地方,而且它真的可能在一个向量中。
回顾几年前我自己的问题,我现在可以很容易地展示我试图做的事情以及如何完成它。
简而言之,它不能纯粹地完成,因此我们需要求助于 ST
monad 和两个向量的手动变异,但最终我们确实得到了这个漂亮而纯粹的函数,它只创建两个向量,不依赖于融合。
import Control.Monad.ST
import Data.Vector.Primitive
import Data.Vector.Primitive.Mutable
data Foo = Foo {fooX :: Int, fooY :: Int}
make :: Int -> (Int -> Foo) -> (Vector Int, Vector Int)
make n f = runST $ do
let n' = max 0 n
mv1 <- new n'
mv2 <- new n'
let fillVectors i
| i < n' = let Foo x y = f i
in write mv1 i x >> write mv2 i y >> fillVectors (i + 1)
| otherwise = return ()
fillVectors 0
v1 <- unsafeFreeze mv1
v2 <- unsafeFreeze mv2
return (v1, v2)
我们以类似的方式使用它 generate
:
λ> make 10 (\ i -> Foo (i + i) (i * i))
([0,2,4,6,8,10,12,14,16,18],[0,1,4,9,16,25,36,49,64,81])
我正在尝试通过使用从索引创建值的自定义数据类型(或元组)的函数来生成 Vector
的元组。这是一种可以达到预期结果的方法:
import Prelude hiding (map, unzip)
import Data.Vector hiding (map)
import Data.Array.Repa
import Data.Functor.Identity
data Foo = Foo {fooX :: Int, fooY :: Int}
unfoo :: Foo -> (Int, Int)
unfoo (Foo x y) = (x, y)
make :: Int -> (Int -> Foo) -> (Vector Int, Vector Int)
make n f = unzip $ generate n getElt where
getElt i = unfoo $ f i
除了我想在每个 Vector 的单次迭代中完成它,几乎如下所示,但避免对函数进行多次评估 f
:
make' :: Int -> (Int -> Foo) -> (Vector Int, Vector Int)
make' n f = (generate n getElt1, generate n getElt2) where
getElt1 i = fooX $ f i
getElt2 i = fooY $ f i
请注意,我了解到 Vector
库支持融合,第一个示例已经非常高效。我需要一个 generate
概念的解决方案,其他库有非常相似的构造函数(例如 Repa 有 fromFunction
),我在这里使用 Vector
只是为了演示一个问题。
也许某种 f
函数调用的记忆会起作用,但我想不出任何东西。
编辑:
另一个使用 Repa 的问题演示:
makeR :: Int -> (Int -> Foo) -> (Array U DIM1 Int, Array U DIM1 Int)
makeR n f = runIdentity $ do
let arr = fromFunction (Z :. n) (\ (Z :. i) -> unfoo $ f i)
arr1 <- computeP $ map fst arr
arr2 <- computeP $ map snd arr
return (arr1, arr2)
与向量相同,融合在性能上节省了时间,但仍然需要元组的中间数组arr
,这是我试图避免的。
编辑 2:(3 年后)
在上面的 Repa 示例中,它不会创建中间数组,因为 fromFunction
创建延迟数组。相反,它会更糟,它会为每个索引计算两次 f
,一次用于第一个数组,第二次用于第二个数组。为了避免这种重复工作,必须计算延迟数组。
您要写的基本内容是
splat f = unzip . fmap f
在两个结果向量之间共享计算 f
的结果,但您想避免使用中间向量。不幸的是,我很确定你不能在任何有意义的意义上同时拥有这两种方式。为简单起见,考虑一个长度为 1 的向量。为了让结果向量共享 f (v ! 0)
的结果,每个向量都需要引用一个表示该结果的 thunk。好吧,那个 thunk 必须 是 某个地方,而且它真的可能在一个向量中。
回顾几年前我自己的问题,我现在可以很容易地展示我试图做的事情以及如何完成它。
简而言之,它不能纯粹地完成,因此我们需要求助于 ST
monad 和两个向量的手动变异,但最终我们确实得到了这个漂亮而纯粹的函数,它只创建两个向量,不依赖于融合。
import Control.Monad.ST
import Data.Vector.Primitive
import Data.Vector.Primitive.Mutable
data Foo = Foo {fooX :: Int, fooY :: Int}
make :: Int -> (Int -> Foo) -> (Vector Int, Vector Int)
make n f = runST $ do
let n' = max 0 n
mv1 <- new n'
mv2 <- new n'
let fillVectors i
| i < n' = let Foo x y = f i
in write mv1 i x >> write mv2 i y >> fillVectors (i + 1)
| otherwise = return ()
fillVectors 0
v1 <- unsafeFreeze mv1
v2 <- unsafeFreeze mv2
return (v1, v2)
我们以类似的方式使用它 generate
:
λ> make 10 (\ i -> Foo (i + i) (i * i))
([0,2,4,6,8,10,12,14,16,18],[0,1,4,9,16,25,36,49,64,81])