13

System.Randomアプリケーションでとtypeclassを使用して、Random乱数を生成しています。ただし、次のような関数を使用して、任意の長さのランダムなフロートのリストを生成したいと思います。randoms :: StdGen -> Int -> ([Float], StdGen)

新しいジェネレーターを入手するという制約なしに、私は簡単に書くことができました randoms gen n = (take n $ randoms gen) :: [Float]

ただし、これにより、最初に使用したのと同じランダムジェネレーターが残ります。つまり、この関数を2回続けて実行すると、別の場所に行ってジェネレーターを使用して新しいジェネレーターを取得しない限り、同じリストが表示されます。

ランダムジェネレーターを「更新」しながら、ランダム値の無限(または任意の長さ)のリストを生成するにはどうすればよいですか。

4

4 に答える 4

30

さて、あなたが持っている関数を見てみましょう:

random :: StdGen -> (Float, StdGen)  -- From System.Random

これをモナドでラップしてState、ステートフル計算を取得できます。

state :: (s -> (a, s)) -> State s a  -- From Control.Monad.Trans.State

random' :: State StdGen Float
random' = state random

これで、次を使用するだけで一連のフロートを生成できますreplicateM

replicateM :: (Monad m) => Int -> m a -> m [a]  -- From Control.Monad

randoms' :: Int -> State StdGen [Float]
randoms' n = replicateM n random'

最後に、をアンラップしStateて、明示的なジェネレーターの受け渡しを取り戻します。

randoms :: Int -> StdGen -> ([Float], StdGen)
randoms n = runState (randoms' n)

これらすべてを1つの関数定義に組み合わせると、次のようになります。

randoms :: Int -> StdGen -> ([Float], StdGen)
randoms n = runState (replicateM n (state random))

言い換えれば、プロセスを次のように説明できます。

  • モナドrandomで包むState
  • nそれを何度も複製する
  • 開封する

これが、モナドが非常に重要な概念である理由です。最初はトリッキーに見えることがありますが、モナドインターフェイスのレンズを通して見た場合、単純な計算になる傾向があります。

于 2013-01-07T20:46:42.397 に答える
5

Gabrielの答えは正解であり、これはMonadRandomパッケージの実装方法とほぼ同じです(ランダムジェネレーターでパラメーター化された状態Monad)。

毎回定義する手間が省け、モナド変換子も付属しているので、他のモナドをランダムな値を生成できるものに変換できます。

あなたの例は次のように簡単に実装できます:

(runRand $ take n `fmap` getRandoms) :: RandomGen g => g -> ([Int], g)

StdGenはたまたまRandomGenのインスタンスであるため、プラグを差し込むだけで実行できます。

于 2013-01-07T21:29:14.840 に答える
3

またはなしで、 from (およびStatefrom )splitを使用する代替手段:mapAccumLData.ListswapData.Tuple

nRandoms n gen =  mapAccumL(\g _ -> swap $ random g) gen [1..n]

とはいえ、なぜこれが何らかの形でより良いはずなのかについて、説得力のある議論はありません。

于 2013-01-07T21:28:10.780 に答える
1

より一般的ではありますが、必要なタイプと一致するタイプの関数を定義できます。

import System.Random

randoms' :: (RandomGen g, Random a) => g -> Int -> ([a], g)
randoms' g n =
  let (g1, g2) = split g
  in (take n $ randoms g1, g2)

使ってもsplit

split :: g -> (g, g)

このsplit操作により、2つの異なる乱数ジェネレーターを取得できます。これは関数型プログラムでは非常に便利ですが(たとえば、乱数ジェネレーターを再帰呼び出しに渡す場合)、統計的に堅牢なsplit…</p> の実装についてはほとんど作業が行われていません。

それでもあなたが望むことをしません。(Bool視覚的な比較を容易にするために、以下の例で使用します。)

ghci> g <- getStdGen
ghci> randoms' g 5 :: ([Bool], StdGen)
([False,False,False,True,False],1648254783 2147483398)
ghci> randoms' g 5 :: ([Bool], StdGen)
([False,False,False,True,False],1648254783 2147483398)

ランダム配列は同じであることに注意してください。

関数はジェネレーターを分割する手間がかかりますが、すぐに破棄します。代わりにg2、次のように後続の呼び出しにスレッド化して利用します。

ghci> let (a1,g2) = randoms' g 5 :: ([Bool], StdGen)
ghci> let (a2,_) = randoms' g2 5 :: ([Bool], StdGen)
ghci> (a1,a2)
([False,False,False,True,False],[True,True,True,False,True]

コードがIOモナドで実行されている場合は、次setStdGenのように、最後にグローバル乱数ジェネレーターを置き換えるために使用できます。

myAction :: Int -> IO ([Float],[Float])
myAction n = do
  g <- getStdGen
  let (f1,g2) = randoms' g n
  let (f2,g3) = randoms' g2 n
  setStdGen g3
  return (f1, f2)

スレッド化の状態は扱いにくく、エラーが発生しやすくなります。ボイラープレートを繰り返し使用するStateか、使用することを検討してください。ST

于 2013-01-07T21:33:43.733 に答える