我已阅读this question并认为此算法并非最佳。例如,'f 20 100'返回的列表如[85,14,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0];结果,我经常得到一个很长的零尾。
我认为这是一项有趣的任务,并决定创建我自己的实现:)
我决定按随机比例划分数字:
g 1 sum = return [sum]
g n sum = do
prop <- randomRIO(0.0, 1.0)
k1 <- g (round prop * n) (round( prop * sum))
k2 <- g (n - (round prop * n)) (sum - (round prop * sum))
return k1 ++ k2
但我的代码不起作用:
Couldn't match expected type `IO [a0]' with actual type `[a1]'
In the expression: return k1 ++ k2
In the expression:
do { prop <- randomRIO (0.0, 1.0);
k1 <- g (round prop * n) (round (prop * sum));
k2 <- g (n - (round prop * n)) (sum - (round prop * sum));
return k1 ++ k2 }
In an equation for `g':
g n sum
= do { prop <- randomRIO (0.0, 1.0);
k1 <- g (round prop * n) (round (prop * sum));
k2 <- g (n - (round prop * n)) (sum - (round prop * sum));
.... }
正如我所见,我无法连接IO列表。我该如何解决?
答案 0 :(得分:6)
您询问的类型错误是由您应该写
引起的return (k1 ++ k2)
而不是
return k1 ++ k2
请注意return
只是Haskell中的一个函数,并且函数应用程序比任何其他中缀运算符都更强大,因此您的代码读取到Haskell,就好像您已经编写了
(return k1) ++ k2
但请注意,您的代码还存在其他问题。
答案 1 :(得分:2)
首先让我们稍后进行一些导入:
import Control.Applicative
import Control.Monad
import System.Random
import Data.List hiding (partition)
始终记住函数应用程序的优先级高于中缀运算符:return k1 ++ k2
表示(return k1) ++ k2
,round prop * n
表示(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)
。它清理了一些代码,意味着如果它错了,我们可以在一个函数中修复它,而不是在整个地方修复它。
我提供了一个类型签名,以使错误消息更具信息性,还有第二个基本情况 - 它没有终止,因为有时舍入会导致它要求长度为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
称为非零n
,要求长度为0的列表总和为非零值。糟糕。
你的算法背后的想法是随机分割列表和总数,但保持两者的比例相同,以保持分布不是片面的(原问题中的问题)。
让我们使用这个想法,但是要防止它要求长度为零 - 我们需要道具既不是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
现在它永远不会给我们错误的总数。万岁!
但是oops: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)
在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
不是total
的因素时,有一个不成比例的0,并且分布不均匀,它是杯形的 - 算法最终会在一端塞满数据。
不要一点一点地选择子列表中的内容,而是生成小计将一次结束的所有位置。当然其中一个必须是总数,一旦我们生成它们,我们最好对它们进行排序。
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
最后!
答案 2 :(得分:0)
这是我用来简化QuickCheck使用的模块的一部分。代码中有趣的部分由无端Brent Yorgey编写,并使用二进制数系统,如上面评论中链接的博客文章中所述。 pickDistribution
函数是一些示例粘合代码,用于生成具有特定权重的非负数字列表(您可以使用resize来选择特定权重)。
{-# LANGUAGE MultiParamTypeClasses #-}
module QuickCheckUtils where
import Control.Monad.Reader
import Test.QuickCheck
import Test.QuickCheck.Gen
instance MonadReader Int Gen where
ask = MkGen (\r n -> n)
local f (MkGen g) = MkGen (\r -> g r . f)
-- pickDistribution n chooses uniformly at random from all lists of length n of
-- non-negative numbers that sum to the current weight
pickDistribution :: Int -> Gen [Int]
pickDistribution n = do
m <- ask
let j = fromIntegral (m+n-1)
k = fromIntegral (n-1)
i <- choose (1, binom j k)
return . map fromIntegral . combToComposition $ toComb j k (i-1)
-- code from Brent {{{
-- Comb n cs represents a choice cs of distinct numbers between 0 and
-- (n-1) inclusive.
data Comb = Comb Integer [Integer] deriving Show
type Comp = [Integer]
-- Convert a choice of (n-1) out of (m+n-1) things into a composition
-- of m, that is, an ordered list of natural numbers with sum m.
combToComposition :: Comb -> Comp
combToComposition (Comb n cs) = map pred $ zipWith (-) cs' (tail cs')
where cs' = [n] ++ cs ++ [-1]
-- Convert a number into "base binomial", i.e. generate the
-- ith combination in lexicographical order. See TAOCP 7.2.1.3, Theorem L.
toComb :: Integer -- ^ Total number of things
-> Integer -- ^ Number to select
-> Integer -- ^ Index into the lexicographic ordering of combinations
-> Comb -- ^ Corresponding combination
toComb n k i = Comb n (toComb' k i (n-1) (binom (n-1) k))
binom _ 0 = 1
binom 0 _ = 0
binom n k = binom (n-1) (k-1) * n `div` k
toComb' 0 _ _ _ = []
toComb' k i j jCk
| jCk > i = toComb' k i (j-1) (jCk * (j-k) `div` j)
| otherwise = j : toComb' (k-1) (i - jCk) (j-1) (jCk * k `div` j)
-- }}}