「機能的な」方法でそれを行うことは気にしません。しかし、線形時間 (O(n log n) ではない) である必要があり、型シグネチャがそのまま維持される (つまり、追加の型制約を追加しない) ことを本当に好みます。これは私がこれまでに持っているものですが、スタックオーバーフローが発生し続けています:
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 では、その方法を理解できないようです。ボックス化されていない配列を使用する場合に機能するいくつかのアプローチを見つけました。しかし、私が言ったように、追加の制約を追加したくないです。何か案は?
編集:上記のコードがスタックスペースをどのように消費しているか、および末尾再帰呼び出しを使用してそれを単純に回避できない理由を誰かが説明してくれれば幸いです。いくつかの場所で熱心な評価を使用してみましたが、役に立ちませんでした