使用生成将值散布到单独的向量中

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])