是'虚拟折叠'递归的一个很好的替代方案?

时间:2016-05-30 08:22:37

标签: haskell

看看下面的代码,尤其是第一个函数randVectors。 它创建了一个随机向量的向量。为了做到这一点,随机生成器g必须是循环的'上;返回并传递用于创建第一个向量的生成器以生成下一个向量,依此类推。代码编译并按预期工作,但这是编写Haskell的惯用方法吗?

如您所见,折叠中的步进功能忽略了折叠的内容。它可以用递归来实现,但至少在我看来这似乎并没有那么好。

import           Control.Monad               as M
import           Control.Monad.ST
import qualified Data.Vector                 as V
import qualified Data.Vector.Generic         as VG
import           Data.Vector.Generic.Mutable as VGM
import qualified Data.Vector.Unboxed         as VU
import           System.Random


randVectors :: (RandomGen g) => Int -> Int -> g -> (V.Vector (VU.Vector Int), g)
randVectors nvecs veclen g =
  foldl step (V.empty, g) [0..nvecs]
    where
  step :: (RandomGen g) => (V.Vector (VU.Vector Int), g) -> Int -> (V.Vector (VU.Vector Int), g)
  step (li, g1) _ =
    (V.snoc li subli, g2)
      where
    (subli, g2) = randVector veclen g1

randVector :: (RandomGen g) => Int -> g -> (VU.Vector Int, g)
randVector n =
  shuffle vector $ VU.length vector
    where
  vector = VU.enumFromN 0 n

shuffle :: (RandomGen g, VG.Vector v a) => v a -> Int -> g -> (v a, g)
shuffle vec size g =
  runST $ do
    vec_mut <- VG.thaw vec
    let swap_random g1 i = do
          let (j,g2) = randomR (0,i) g1
          VGM.swap vec_mut i j
          return g2
    g' <- M.foldM swap_random g [1..size-1]
    vec' <- VG.unsafeFreeze vec_mut
    let vec_sample = VG.take size vec'
    return (vec_sample, g')

1 个答案:

答案 0 :(得分:4)

如果你无法击败他们,请加入他们。 如果你不能折叠它们,展开它们。

确实,可以在这里使用unfoldrN

randVectors :: (RandomGen g) => Int -> Int -> g -> (V.Vector (VU.Vector Int), g)
randVectors nvecs veclen = unfoldrN nvecs (Just . randVector veclen)

更详细:

unfoldrN :: Int -> (b -> Maybe (a, b)) -> b -> Vector a 

用作

unfoldrN :: Int -> (g -> Maybe (VU.Vector Int, g)) -> g -> Vector (VU.Vector Int)

Int是生成的矢量的最大长度,即nvecs。 函数(g -> Maybe (VU.Vector Int, g))randVector完全相同,只是可以选择通过返回Nothing来提前停止生成 - 我们不需要它,因此我们使用{{ 1}}。参数Just是初始生成器。

或者,可以在类型中使用状态monad来表示g重复出现的模式,然后利用

g -> (g, _)