number generator

Duncan Booth duncan.booth at invalid.invalid
Mon Mar 12 12:14:13 CET 2007


aleax at mac.com (Alex Martelli) wrote:

> Without any specification regarding the distributions required for the
> "5 random numbers" it's really impossible to say whether these are
> better or worse than other proposed solutions.

FWIW, I decided it would be fun to see what kind of implementation I 
could come up test driven and avoiding reading the thread or background 
references too much first. So starting from:

import unittest

def partition(total, n, min):
    return [50]

class PartitionTests(unittest.TestCase):
    def testsinglevalue(self):
        self.assertEqual([50], partition(50, 1, 1))

if __name__=='__main__':
    unittest.main()

I eventually worked my way through 15 revisions to the code below.

The tests were added in the order you see them below. The commented out 
function is the one I had arrived at before I added the first 
distribution test which triggered a major refactor (although the code 
ended up remarkably similar): if you uncomment it all but the 
distribution tests pass.

I don't really like the arbitrary limits for the distribution tests, but 
I'm not sure how else to test that sort of thing. And as Alex said, 
without knowing what distribution the OP wanted the definition I chose 
to use is completely arbitrary.

----- partition.py -------
import unittest, collections
from random import randint, sample

def partition(total, n, min):
    maxtotal = total - n*(min-1)
    posts = sorted(sample(xrange(1, maxtotal), n-1))
    return [ (b-a)+min-1 for (a,b) in zip([0]+posts, posts+[maxtotal]) ]

# def partition(total, n, min):
#     maxtotal = total - (n*min)
#     sums = sorted(randint(0, maxtotal) for i in range(n-1))
#     return [(b-a)+min for (a,b) in zip([0]+sums, sums+[maxtotal])]

class PartitionTests(unittest.TestCase):
    def testsinglevalue(self):
        self.assertEqual([50], partition(50, 1, 1))

    def testnvalues(self):
        self.assertEqual([1]*5, partition(5, 5, 1))

    def testnminusone(self):
        self.assertEqual([1]*4+[2], sorted(partition(6, 5, 1)))

    def testnminusone2(self):
        # Check we get all possible results eventually
        expected = set([(1,1,2), (1,2,1), (2,1,1)])
        for i in range(100):
            got = tuple(partition(4,3,1))
            if got in expected:
                expected.remove(got)
            if not len(expected):
                break
        self.assertEqual(len(expected), 0)

    def testdistribution(self):
        # Make sure we get each of 3 possible outcomes roughly
        # equally often.
        actual = collections.defaultdict(int)
        for i in range(1000):
            actual[tuple(partition(4,3,1))] += 1
        counts = actual.values()
        assert (min(counts) > 250)
        assert (max(counts) < 400)

    def testdistribution2(self):
        # More arbitrary limits for the distribution. 10 possible
        # outcomes this time.
        actual = collections.defaultdict(int)
        ntries = 10000
        for i in range(ntries):
            actual[tuple(partition(6,3,1))] += 1
        counts = actual.values()
        assert (min(counts) > 900)
        assert (max(counts) < 1100)

    def testmintwo(self):
        self.assertEqual([2]*50, partition(100,50,2))

    def testminzero(self):
        self.assertEqual([0]*20, partition(0,20,0))

    def testcantdoit(self):
        self.assertRaises(ValueError, partition, 100, 51, 2)

if __name__=='__main__':
    unittest.main()

--------------------------



More information about the Python-list mailing list