最初に、後で使用するためにいくつかのインポートを行いましょう。
import Control.Applicative
import Control.Monad
import System.Random
import Data.List hiding (partition)
コード修正
関数適用は中置演算子よりも優先順位が高いことを常に覚えておいてください: return k1 ++ k2
means(return k1) ++ k2
とround prop * n
means (round prop) * n
。を使用$
して、関数を適用する式から関数を分離できます。ただし、優先順位が非常に低いためf $ x = f x
です$
。return $ k1 ++ k2
たとえば、 を使用できます。
乗算する前に、Ints と Doubles を少し混ぜ合わせ(round prop * n)
て比率を丸めていましたが、最初に乗算したいので、 に適用fromIntegral
する必要がありますn
。私はこれのために別の関数を作りました
(.*) :: Double -> Int -> Int
d .* i = floor $ d * fromIntegral i
したがって、代わりに(round prop * n)
を使用できます(prop .* n)
。これにより、コードが少しクリーンアップされます。つまり、間違っている場合は、あちこちではなく 1 つの関数で修正できます。
エラーメッセージをより有益にするための型シグネチャと、2 番目の基本ケースを提供しました。丸めによって長さ 0 のリストが要求されることがあるため、終了しませんでした。
partition1 :: Int -> Int -> IO [Int]
partition1 0 total = return []
partition1 1 total = return [total]
partition1 n total = do
prop <- randomRIO(0.0, 1.0)
k1 <- partition1 (prop .* n) (prop .* total)
k2 <- partition1 (n - (prop .* n)) (total - (prop .* total))
return $ k1 ++ k2
また、よりわかりやすい名前を付けることもできました。
適切な合計を取得する
残念ながら、これはコンパイルされますが、Will Ness がコメントで指摘したように、問題があります。通常、合計が合計よりも少ない数値になります。partition 0 n
これは、non-zeroを呼び出してn
、長さ 0 のリストを合計するとゼロ以外になるように要求するためです。おっとっと。
アルゴリズムの背後にある考え方は、リストと合計をランダムに分割することですが、両方の比率を同じに保ち、分布が片側にならないようにすることです (元の質問の問題)。
そのアイデアを使用しましょう。ただし、長さゼロを要求しないようにします。prop が 0 でも 1 でもないようにする必要があります。
partition2 :: Int -> Int -> IO [Int]
partition2 0 total = return []
partition2 1 total = return [total]
partition2 n total = do
new_n <- randomRIO(1,n-1)
let prop = fromIntegral new_n / fromIntegral n
k1 <- partition2 new_n (prop .* total)
k2 <- partition2 (n - new_n) (total - (prop .* total))
return $ k1 ++ k2
これで、間違った合計が得られることはありません。万歳!
ランダムはフェアと同じではない
しかし、おっと:partition2 18 10000
私たちに与えます
[555,555,555,555,556,556,555,556,556,556,555,555,556,556,555,556,556,556]
問題は、フェアがランダムと同じではないということです。このアルゴリズムは非常に公平ですが、あまりランダムではありません。長さとは別にプロポーションを選択させましょう。
partition3 :: Int -> Int -> IO [Int]
partition3 0 total = return []
partition3 1 total = return [total]
partition3 n total = do
new_n <- randomRIO(1,n-1)
new_total <- randomRIO(0,total) -- it's fine to have zeros.
k1 <- partition3 new_n new_total
k2 <- partition3 (n - new_n) (total - new_total)
return $ k1 ++ k2
それは良く見えます:partition3 15 20000
私にくれました
[1134,123,317,725,1031,3897,8089,2111,164,911,25,0,126,938,409]
ランダムは公平ではありませんが、偏りもありません
これは明らかにはるかに優れていますが、基本的に、私たちが行っているバイナリ パーティショニングはバイアスを導入しています。
見ることで多くの実行をテストできます
check :: (Int -> Int -> IO [Int]) -> Int -> Int -> Int -> IO ()
check f n total times = mapM_ print =<< map average.transpose.map (righttotal total) <$> replicateM times (f n total)
where average xs = fromIntegral (sum xs)/fromIntegral total
righttotal tot xs | sum xs == tot = xs
| otherwise = error $ "wrong total: " ++ show (sum xs)
の1回の実行で私にcheck partition3 11 10000 1000
与えた
180.7627
97.6642
79.7251
66.9267
64.5253
59.4046
56.9186
66.6599
70.6639
88.9945
167.7545
n
大量のテスト データと分析に立ち入らずに、興味深いことに、 が の因数ではない場合に不均衡な量の 0 が存在total
し、分布が均一ではなく、カップの形をしている - アルゴリズムは一方の端にデータを詰め込むことになります.
逃げ道
サブリストにどれだけ入っているかを少しずつ選択する代わりに、小計が一度に終了するすべての場所を生成しましょう。もちろん、そのうちの 1 つは合計である必要があり、生成したら並べ替えたほうがよいでしょう。
stopgaps :: Int -> Int -> IO [Int]
stopgaps parts total = sort.(total:) <$> replicateM (parts-1) (randomRIO (0,total))
ここでは、正しい範囲で乱数replicateM :: Int -> m a -> m [a]
を生成するために使用します。parts-1
縁の下の力持ちをプラグインしたい:
mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
リストに沿って蓄積し、新しいリストを生成します。
gapsToLengths :: [Int] -> (Int,[Int])
gapsToLengths = mapAccumL between 0
where between previous new = (new,new - previous)
partition4 :: Int -> Int -> IO [Int]
partition4 parts total = snd.gapsToLengths <$> stopgaps parts total
それは機能しますか?
partition4 11 10000
、かなり印刷された のいくつかのテスト実行:
[ 786, 20, 607, 677, 1244, 1137, 990, 50, 1716, 813, 1960]
[ 406, 110, 2556, 126, 1289, 567, 348, 1230, 171, 613, 2584]
[ 368, 1794, 136, 1266, 583, 93, 1514, 66, 1594, 1685, 901]
[ 657, 1296, 1754, 411, 691, 1865, 531, 270, 1941, 286, 298]
[2905, 313, 842, 796, 698, 1104, 82, 1475, 22, 619, 1144]
[1411, 966, 530, 129, 81, 561, 1779, 1179, 301, 607, 2456]
[1143, 409, 903, 27, 855, 354, 887, 1898, 1880, 301, 1343]
[ 260, 643, 96, 323, 142, 74, 401, 977, 3685, 2690, 709]
[1350, 979, 377, 765, 137, 1295, 615, 592, 2099, 1088, 703]
[2411, 958, 330, 1433, 1355, 680, 1075, 41, 988, 81, 648]
それはランダムに見えます。偏りがないことを確認しましょう。
check partition4 11 10000 1000
92.6425
93.4513
92.3544
90.8508
88.0297
91.7731
88.7939
86.5268
86.3502
95.2499
93.9774
やっと!