3

一連の乱数/フロートを取得し、それらを使用して値/構造を生成する関数があります(つまり、ランダムな速度とボールが投げられたポイントの位置を取得し、着地する座標を出力します) . そして、数千を連続して生成する必要があります。

私がすべてを実装した方法は、各計算が stdGen を受け取り、それを使用していくつかの数値を生成し、新しい stdGen を渡して別の数値にチェーンできるようにすることです。

10000 個のアイテムに対してこれを行うには、generate_item n基本的に(value,gen)タプル (値は計算しようとしている値) を出力する一種のリストを作成しますgen。からの値generate_item n-1

ただし、このプログラムは約 1,000 件程度の結果で非現実的な速度でクロールするようです。そして、間違いなくスケーラブルではないようです。すべてのgenerate_item結果をメモリに保存しているという事実と関係があるのでしょうか?

それとも、私が上で説明したものよりも、Monads などを使用して Haskell でこの問題にアプローチする、より慣習的な方法はありますか?

ランダム値からアルゴリズムを生成するコードは、Ruby や Python などの高レベルのスクリプト言語でも数秒以内に 10k を生成することに注意してください。これらの計算はほとんど集中的ではありません。

コード

-- helper functions that take in StdGen and return (Result,new StdGen)
plum_radius :: StdGen -> (Float,StdGen)
unitpoint   :: Float -> StdGen -> ((Float,Float,Float),StdGen)
plum_speed  :: Float -> StdGen -> (Float,StdGen)

-- The overall calculation of the value
plum_point  :: StdGen -> (((Float,Float,Float),(Float,Float,Float)),StdGen)
plum_point gen  = (((px,py,pz),(vx,vy,vz)),gen_out)
  where
    (r, gen2)         = plum_radius gen
    ((px,py,pz),gen3) = unitpoint r gen2
    (s, gen4)         = plum_speed r gen3
    ((vx,vy,vz),gen5) = unitpoint s gen4
    gen_out           = gen5

-- Turning it into some kind of list
plum_data_list  :: StdGen -> Int -> (((Float,Float,Float),(Float,Float,Float)),StdGen)
plum_data_list seed_gen 0  = plum_point seed_gen
plum_data_list seed_gen i  = plum_point gen2
  where
    (_,gen2)  = plum_data_list seed_gen (i-1)

-- Getting 100 results
main = do
  gen <- getStdGen
  let data_list = map (plum_data_list gen) [1..100]
  putStrLn List.intercalate " " (map show data_list)
4

3 に答える 3

6

mersenne-twister とvector-randomパッケージを使用することを検討してください。これは、大量の高品質のランダム データを生成するために特に最適化されています。

リストは、大量のデータを割り当てるのには適していません。ストリーミングしている場合を除き、パックされた表現を使用することをお勧めします。

于 2013-03-07T10:08:34.560 に答える
5

まず、あなたが説明しているパターン(次の計算にチェーンされるStdGen値と別のタプルを取得して返す)は、モナドがエンコードするパターンとまったく同じです。コードをリファクタリングして使用することは、モナディックパターンに慣れるための良い方法かもしれません。StdGenState

あなたのパフォーマンスの問題に関してStdGenは、悪名高いほど遅いです。私はこのようなことをあまりしていませんが、メルセンヌツイスターの方が速いと聞きました。

ただし、コードを投稿することもできます。大きなリストを生成している場合、怠惰は、それをどのように行っているかに応じて、実際に有利または不利に働く可能性があるためです。しかし、あなたが何をしているのかを見ずに具体的なアドバイスをするのは難しいです。Lispなどの別の関数型言語から来ている場合に備えて、リスト(または他の遅延データ構造(ツリーなど)を生成するときは、末尾再帰を避けIntてください) 。それがより速いという直感は怠惰な言語に移りません。例:使用(実際に実際に使用するモナディックスタイルなしで記述)

randoms :: Int -> StdGen -> (StdGen, [Int])
randoms 0 g = (g, [])
randoms n g = let (g',  x)  = next g
                  (g'', xs) = randoms (n-1) g'
              in (g'', x : xs)

これにより、結果リストを「ストリーミング」できるため、後の部分を生成する前に、結果リストの前の部分にアクセスできます。(この状態の場合、結果StdGenにアクセスするとリスト全体を生成する必要があるため、少し微妙です。したがって、リストを消費するまで、これを慎重に回避する必要があります。高速のランダムジェネレーターがあればいいのですが。良好な操作をサポートしていれsplitば、ジェネレーターを返却する必要がなくなります)。

ああ、モナドのことでうまくいかない場合に備えて、これが状態モナドで書かれた上記の関数です:

randomsM :: Int -> State StdGen [Int]
randomsM 0 = return []
randomsM n = do
    x <- state next
    xs <- randomsM (n-1)
    return (x : xs)

対応を見ますか?

于 2013-03-07T09:18:03.630 に答える
4

他のポスターには良い点StdGenがありますが、パフォーマンスはあまり良くありません。おそらく、ジェネレーターを手動で渡す代わりに State を使用するようにしてください。plum_data_listしかし、最大の問題はあなたの機能だと思います。

ある種のルックアップを意図しているように見えますが、メモ化なしで再帰的に実装されているため、呼び出しは基本ケースに再帰する必要があります。つまり、からまでplum_data_list seed_gen 100の乱数発生器が必要です。これにより、これらの値のリストを生成しようとすると、2 次のパフォーマンスが得られます。plum_data_list seed_gen 99plum_data_list seed_gen 0

おそらく、より慣用的な方法は、plum_data_list seed_gen次のようにポイントの無限リストを生成させることです。

plum_data_list :: StdGen -> [((Float,Float,Float),(Float,Float,Float))]
plum_data_list seed_gen = first_point : plum_data_list seed_gen'
  where
    (first_point, seed_gen') = plum_point seed_gen

main次に、コードを のように変更するだけでtake 100 $ plum_data_list gen、線形パフォーマンスに戻ります。

于 2013-03-07T13:15:15.610 に答える