Haskell 我的Fisher Yates shuffle有什么问题吗?

Haskell 我的Fisher Yates shuffle有什么问题吗?,haskell,shuffle,Haskell,Shuffle,我意识到,当一些事情看起来太好而不可能是真的时,我想我会提出这个问题,希望能把所有的小精灵都赶出来。我回顾了我能找到的几个相关的线索,但我的问题仍然挥之不去 我对Haskell比较陌生,在我的实验中,我编写了一个基本的Fisher-Yates混洗,如下所示 shuffle :: RandomGen g => [a] -> g -> ([a],g) shuffle [] g0 = ([],g0) shuffle [x] g0 = ([x],g0) shuffle xs g0 =

我意识到,当一些事情看起来太好而不可能是真的时,我想我会提出这个问题,希望能把所有的小精灵都赶出来。我回顾了我能找到的几个相关的线索,但我的问题仍然挥之不去

我对Haskell比较陌生,在我的实验中,我编写了一个基本的Fisher-Yates混洗,如下所示

shuffle :: RandomGen g => [a] -> g -> ([a],g)
shuffle [] g0 = ([],g0)
shuffle [x] g0 = ([x],g0)
shuffle xs g0 = (x:newtail,g2)
  where (i,g1) = randomR (0, length $ tail xs) g0
        (xs1,x:xs2) = splitAt i xs
        (newtail,g2) = shuffle (xs1++xs2) g1
当然,这个实现使用BeaUoup内存来存储大的列表,但是它在我的笔记本电脑上AVG 5S快,在303M的内嵌式,在STS C++中在2.3秒内进行洗牌。事实上,它比其他地方的Haskell实现要快得多

考虑到我见过的其他Haskell洗牌都更复杂和更慢,我想知道加速/简单是否仅仅是我作为一个毫无歉意的内存占用者的奖励,还是我错过了一些微小但关键的细节,使我的算法有偏差。我没有进行过广泛的测试,但初步的观察似乎显示排列的分布是均匀的


我希望能有更多哈斯克尔和/或洗牌经验的眼睛。非常感谢所有花时间回复的人。

让我们做一些适当的基准测试。下面是一些代码,您的shuffle重命名为
shuffle1
,我个人最喜欢的变体是
shuffle2

import System.Random

import Control.Monad

import Control.Monad.ST.Strict
import Data.STRef.Strict

import Data.Vector.Mutable

import Prelude as P

import Criterion.Main


shuffle1 :: RandomGen g => [a] -> g -> ([a], g)
shuffle1 [] g0 = ([],g0)
shuffle1 [x] g0 = ([x],g0)
shuffle1 xs g0 = (x:newtail,g2)
  where (i,g1) = randomR (0, P.length $ P.tail xs) g0
        (xs1,x:xs2) = P.splitAt i xs
        (newtail,g2) = shuffle1 (xs1++xs2) g1


shuffle2 :: RandomGen g => [a] -> g -> ([a], g)
shuffle2 xs g0 = runST $ do
    let l = P.length xs
    v <- new l
    sequence_ $ zipWith (unsafeWrite v) [0..] xs

    let loop g i | i <= 1 = return g
                 | otherwise = do
            let i' = i - 1
                (j, g') = randomR (0, i') g
            unsafeSwap v i' j
            loop g' i'

    gFinal <- loop g0 l
    shuffled <- mapM (unsafeRead v) [0 .. l - 1]
    return (shuffled, gFinal)


main = do
    let s1 x = fst $ shuffle1 x g0
        s2 x = fst $ shuffle2 x g0
        arr = [0..1000] :: [Int]
        g0 = mkStdGen 0
    -- make sure these values are evaluated before the benchmark starts
    print (g0, arr)

    defaultMain [bench "shuffle1" $ nf s1 arr, bench "shuffle2" $ nf s2 arr]
导入系统。随机
进口管制
严格的进口管制
导入Data.STRef.Strict
导入Data.Vector.Mutable
导入前奏曲作为P
进口标准.Main
shuffle1::RandomGen g=>[a]->g->([a],g)
shuffle1[]g0=([],g0)
shuffle1[x]g0=([x],g0)
shuffle1 xs g0=(x:newtail,g2)
其中(i,g1)=randomR(0,P.length$P.tail xs)g0
(xs1,x:xs2)=P.splitAt i xs
(newtail,g2)=shuffle1(xs1++xs2)g1
shuffle2::RandomGen g=>[a]->g->([a],g)
shuffle2 xs g0=runST$do
设l=P.length xs

v考虑到这会计算每次迭代时列表的长度,我发现您指定的性能数字很难令人相信。这是一个O(n^2)算法。你是否在没有强制计算整个结果的情况下对算法进行计时?显示计时代码。3000万个
Int
s的代码需要几天才能完成。“我几乎希望能回到过去,阻止自己发布这个问题。”为什么?你确实从中学到了一些东西,不是吗?这是件好事。(你也从中获得了一些名声,这也不坏。)所以你陷入了一个陷阱,没有充分解释懒惰,多么尴尬——好像我们大多数人都没有这样做。你在那里有很好的伙伴。自己搞清楚一些事情比被告知更令人满意?是的,没错。但如果弄清楚这一点需要很长时间,人们应该问。如果有人告诉你,你自己是否会很快发现,只有你自己能知道。谢谢!(还要感谢上面评论的其他人)。正如我所说,我是哈斯克尔的新手,似乎我只是犯了一个巨大的努布错误,忽略了懒惰的含义。尾巴夹在我两腿之间,蛋在我脸上,至少现在对我来说这一切都是有意义的。@Tientuinë嗯,这是一件很难学的事情。我希望你也能从我的回答中得到一些建设性的东西。查看标准库。我并没有在我的回复中特别提到它,但它确实值得你们花时间去学习。Criteria看起来很有用,很高兴它现在在我的雷达上。
carl@ubuntu:~/hask$ ghc -O2 shuffle.hs
[1 of 1] Compiling Main             ( shuffle.hs, shuffle.o )
Linking shuffle ...
carl@ubuntu:~/hask$ ./shuffle 
(1 1,[0, .. <redacted for brevity>])
warming up
estimating clock resolution...
mean is 5.762060 us (160001 iterations)
found 4887 outliers among 159999 samples (3.1%)
  4751 (3.0%) high severe
estimating cost of a clock call...
mean is 42.13314 ns (43 iterations)

benchmarking shuffle1
mean: 10.95922 ms, lb 10.92317 ms, ub 10.99903 ms, ci 0.950
std dev: 193.8795 us, lb 168.6842 us, ub 244.6648 us, ci 0.950
found 1 outliers among 100 samples (1.0%)
variance introduced by outliers: 10.396%
variance is moderately inflated by outliers

benchmarking shuffle2
mean: 256.9394 us, lb 255.5414 us, ub 258.7409 us, ci 0.950
std dev: 8.042766 us, lb 6.460785 us, ub 12.28447 us, ci 0.950
found 1 outliers among 100 samples (1.0%)
  1 (1.0%) high severe
variance introduced by outliers: 26.750%
variance is moderately inflated by outliers