4

I want to generate a list with 26 random integers which sum is 301 with Haskell. I write the following:

import System.Random

f 1 sum = [sum]
f n sum = m : (f (n-1) (sum-m))
    where m = randomRIO (0,sum)

but it can't be compiled! I am confused with IO!

Occurs check: cannot construct the infinite type: a1 = IO a1
In the first argument of `(:)', namely `m'
In the expression: m : (f (n - 1) (sum - m))
In an equation for `f':
    f n sum
      = m : (f (n - 1) (sum - m))
      where
          m = randomRIO (0, sum)
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
speeding
  • 133
  • 1
  • 6
  • 2
    You have already got the answers regarding haskell, but I think that the algorithm is not ideal for generate random numbers. For example 'f 20 100' returns [85,14,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]. Tou will get long zero tail as the result of this algorithm. As first approach I will try to choose another strategy - divide your sum in random proportins. For example f 5 100 - divide the 100 in random proportion (for example 30 and 70) and call f for each of this numbers. – ceth Nov 02 '12 at 09:56

4 Answers4

8

The error message is somewhat confusing in this case, but the punchline is that you need to work in the IO monad, since it's using randomRIO which is in IO, and there is (by design) no way to run IO code from non-IO code.

f 1 sum = return [sum]
f n sum = do
  x  <- randomRIO (0, sum)
  xs <- f (n - 1) (sum - x)
  return (x : xs)
hammar
  • 138,522
  • 17
  • 304
  • 385
  • It's OK. Some results:*Main> f 26 301 [215,33,6,12,7,1,4,4,15,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] *Main> f 26 301 [284,1,1,13,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] *Main> f 26 301 [297,3,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] – speeding Nov 02 '12 at 12:38
5

As an aside to what hammer wrote, the error message becomes a lot more clear if you write the type you expect for the f function:

f :: Int -> Int -> [Int]
f 1 sum = [sum]
f n sum = m : (f (n-1) (sum-m))
    where m = randomRIO (0,sum)             

gives the error:

Couldn't match expected type `Int' with actual type `IO Int'
    In the first argument of `(:)', namely `m'
    In the expression: m : (f (n - 1) (sum - m))
    In an equation for `f':
        f n sum
          = m : (f (n - 1) (sum - m))
          where
              m = randomRIO (0, sum)
Failed, modules loaded: none.

Which pretty much tells you exactly what is wrong - that is m has type IO Int rather than Int

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
David Miani
  • 14,518
  • 2
  • 47
  • 66
5

As others have pointed out, your algorithm will not give uniformly-distributed output.

An easy way to get uniform output is:

  • Generate n-1 random numbers in the range from 0 to sum (inclusive)
  • Insert 0 and sum into the list of random numbers
  • Sort the resulting list
  • Return the list of differences between consecutive values in the sorted list

Example:

  • Suppose we want four integers with a sum of 100, we request three random values from the RNG and it gives us [72,33,43]
  • We insert 0 and 100 and sort the list, giving [0,33,43,72,100]
  • We compute the differences [33-0, 43-33, 72-43, 100-72]
  • The result would be [33,10,29,28]

In Haskell:

randomsWithSum :: (Num n, Ord n, Random n) => Int -> n -> IO [n]
randomsWithSum len sum =
    do b <- sequence $ take (len-1) $ repeat $ randomRIO (0,sum)
       let sb = sort (sum:b) in
           return $ zipWith (-) sb (0:sb)

For your example you would call this as randomsWithSum 26 (301::Int)

The same applies to floating-point types, e.g. randomsWithSum 4 (1::Double)


Edit Swapped the arguments, so that 26 `randomsWithSum` 301 does what its name suggests.

finnw
  • 47,861
  • 24
  • 143
  • 221
0

Following the comment by demas, I tried to tweak your algorithm. We probably want each of the n numbers be "the same" as all the others, so we'll just try until we get the correct sum. Maybe there is a better way.

-- f 0 rng = return []
-- f n rng = randomRIO (0,rng) >>= (\x-> fmap (x:) $ f (n-1) rng)

g n sumval = 
  let s = 2*sumval `div` n  -- expected value upto z is probably z/2,
      h i = do              --              if all are equally likely
              xs <- sequence $ replicate n (randomRIO (0,s))
              if sum xs == sumval 
                then return (xs, i)       -- i is number of attempts
                else h (i+1)
  in h 1

-- test:
Prelude System.Random> g 26 301
([15,23,15,0,13,8,23,11,13,19,5,2,10,19,4,8,3,9,19,16,8,16,18,4,20,0],2)
Prelude System.Random> g 26 301
([20,14,3,6,15,21,7,9,2,23,22,13,2,0,22,9,4,1,15,10,20,7,18,1,18,19],12)
Prelude System.Random> g 26 301
([4,3,4,14,10,16,20,11,19,15,23,18,10,18,12,7,3,8,4,9,11,5,17,4,20,16],44)
Prelude System.Random> g 26 301
([6,6,22,1,5,14,15,21,12,2,4,20,4,9,9,9,23,10,17,19,22,0,10,14,6,21],34)
Prelude System.Random> g 26 301
([20,9,3,1,17,22,10,14,16,16,18,13,15,7,6,3,2,23,13,13,17,18,2,2,8,13],169)
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • This has a different problem, though: It's impossible for any element to be more than ```2*sumval `div` n```. – hammar Nov 02 '12 at 11:46
  • @hammar that's by design. I think the expected value for each will be thus `sumval/n`, and there are `n` of them. – Will Ness Nov 02 '12 at 11:53
  • @hammar IOW that's not a problem, that's a proposed solution. – Will Ness Nov 02 '12 at 12:00
  • Yes, but it's not enough to just have the correct expectation. If every list with sum `sumval` should be equally probable, then the probability of the list `[sumval, 0, 0, 0, ...]` should be non-zero, but with this code it's zero for lists with 3 or more elements. – hammar Nov 02 '12 at 12:00
  • @hammar If to follow your interpretation, that the solution is simple: just call `permutations` after the original code. Whether each such list will be equally probable, is a separate question. Perhaps the problem is underspecified wrt to the range of elements. – Will Ness Nov 02 '12 at 12:05
  • @hammar here's what I tried to overcome: with lists where each elt's range is `[0..sumval]`, the randomness is exhausted halfway through the list - if we just keep the running sum, and when sumval is reached, all the rest **will have to be 0**. If we want all the numbers in the list equally random, perhaps we have to limit the range of each, as I did. But I don't know much (if at all) about probabilities. – Will Ness Nov 02 '12 at 12:11
  • 1
    Yes, I see what you were trying to do, but to get a uniform distribution is harder than that. And just shuffling the list after the original code won't do either, it'll still be biased towards lists with mostly zeroes. – hammar Nov 02 '12 at 12:14
  • 1
    @hammar right, so my algo won't have that problem. And since the Q is under-specified wrt to individual ranges, I think this is a valid answer. – Will Ness Nov 02 '12 at 12:15
  • 1
    There is a better way, but this isn't it. See http://stackoverflow.com/questions/5622608/choosing-n-numbers-with-fixed-sum – finnw Nov 02 '12 at 13:41