2012-08-24 101 views
1

我不介意以“功能”的方式来完成。但我确实需要它是线性时间(而不是O(n log n)),并且我更喜欢类型签名保持完整(即不添加其他类型约束)。这是我到目前为止,但我不断收到一个堆栈溢出:随机排列大型列表(超过1亿个元素)

import Control.Monad 
import Control.Monad.ST 
import Data.Array.ST 
import Data.STRef 
import System.Random 

randomPermute :: RandomGen g => [a] -> g -> ([a],g) 
randomPermute l rgen = runST $ newListArray (1,n) l >>= body rgen where 
    n = length l 
    body :: RandomGen g => g -> STArray s Int e -> ST s ([e],g) 
    body rgen arr = do 
    rgenRef <- newSTRef rgen 
    let pick i j = do vi <- readArray arr i 
         vj <- readArray arr j 
         writeArray arr j vi 
         return vj 
     rand lo hi = do rgen <- readSTRef rgenRef 
         let (v,rgen') = randomR (lo,hi) rgen 
         writeSTRef rgenRef rgen' 
         return v 
    rv <- forM [1..n] $ \i -> do 
     j <- rand i n 
     pick i j 
    rgen <- readSTRef rgenRef 
    return (rv,rgen) 

ascCount x = sum $ map oneIfBig $ zip x $ tail x where 
    oneIfBig (x,y) = if x<y then 0 else 1 

main = do 
    -- Using String types just for testing 
    res <- getStdRandom $ randomPermute $ map show [1..1000000] 
    putStrLn $ show $ ascCount res 

现在我用命令式语言打交道告诉我,应该避免使用堆栈一起的方式。但在Haskell中,我似乎无法弄清楚如何。我发现了一些方法,如果我使用unboxed数组。但正如我所说,我不想添加额外的限制。有任何想法吗?

编辑:我也很感激,如果有人可以向我解释上面的代码是如何消耗堆栈空间,以及为什么我不能简单地避免使用尾递归调用。我尝试在某些地方使用急切的评估,但它并没有帮助

回答

5

随机列表置换可以通过矢量包使用backpermute在/ O(n)/(假设您有一个随机输入数组)操作。

backpermute :: Unbox a => Vector a -> Vector Int -> Vector a 

/O(n)/ 
Yield the vector obtained by replacing each element i of the index vector by xs!i. This is equivalent to map (xs!) is but is often much more efficient. 

即,

backpermute <a,b,c,d> <0,3,2,3,1,0> = <a,d,c,d,b,a> 

您可以通过a number of packages.

+1

谢谢。但是这难道不会将问题转换为生成整数置换的问题吗?如果我理解正确,你的软件包(mersenne-random,vector-random等)不会导出任何生成具有非重复元素的向量的方法。 由于我对haskell比较新,我还想知道GHC运行时如何在我粘贴的代码中使用堆栈空间,以便我不会再犯同样的错误 – Samee

+0

它将问题分解为O( n)组件来执行置换,并且O(n log n)步骤来生成唯一的随机数(通过集合的一个集合) –

+0

啊,所以我们回到O(n log n)。好,谢谢。但我们可以避免这种情况吗?只是好奇 – Samee

0

创建高效的随机向量我觉得刚刚找到一个线性时间的解决方案我自己,所以我想我应该在这里添加它。显然,从forM或replicateM等monadic函数生成列表是一个糟糕的主意。他们用尽堆栈空间。相反,我只是为了纯粹的命令式处理而使用循环,然后将数组转换为循环外的列表。代码粘贴在下面。

如果有人有兴趣,有一个伟大的usenix后here,它以纯粹的功能方式做同样的事情,但使用O(n log n)时间。

randomPermute :: RandomGen g => [a] -> g -> ([a],g) 
randomPermute x rgen = (body,rgen2) where 
    (rgen1,rgen2) = split rgen 
    body = elems $ runST $ do 
    g <- newSTRef rgen1 
    arr <- newArray x 
    let newInd st = do 
      (i,rgen') <- liftM (randomR (st,n-1)) (readSTRef g) 
      writeSTRef g rgen' 
      return i 
    forM_ [0..n-1] $ \i -> do 
     j <- newInd i 
     p <- readArray arr i 
     q <- readArray arr j 
     writeArray arr j p 
     writeArray arr i q 
    unsafeFreeze arr 
    n = length x 
    newArray :: [a] -> ST s (STArray s Int a) 
    newArray x = newListArray (0,length x-1) x 
相关问题