Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add tests
  • Loading branch information
rhettinger committed May 6, 2020
commit ab6b380d020bf7c5d7b6521711bb425295701de0
4 changes: 3 additions & 1 deletion Lib/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,11 @@ def sample(self, population, k, *, weights=None):
population = tuple(population)
if not isinstance(population, _Sequence):
raise TypeError("Population must be a sequence. For dicts or sets, use sorted(d).")
n = len(population)
if weights is not None:
cum_weights = list(_accumulate(weights))
Comment thread
rhettinger marked this conversation as resolved.
Outdated
if len(cum_weights) != n:
raise ValueError('The number of weights does not match the population')
total = cum_weights.pop()
if not isinstance(total, int):
raise TypeError('Weights must be integers')
Expand All @@ -394,7 +397,6 @@ def sample(self, population, k, *, weights=None):
bisect = _bisect
return [population[bisect(cum_weights, s)] for s in selections]
randbelow = self._randbelow
n = len(population)
if not 0 <= k <= n:
raise ValueError("Sample larger than population or is negative")
result = [None] * k
Expand Down
39 changes: 38 additions & 1 deletion Lib/test/test_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from math import log, exp, pi, fsum, sin, factorial
from test import support
from fractions import Fraction

from collections import Counter

class TestBasicOps:
# Superclass with tests common to all generators.
Expand Down Expand Up @@ -161,6 +161,43 @@ def test_sample_on_sets(self):
population = {10, 20, 30, 40, 50, 60, 70}
self.gen.sample(population, k=5)

def test_sample_with_weights(self):
sample = self.gen.sample

# General case
colors = ['red', 'green', 'blue', 'orange', 'black', 'brown', 'amber']
weights = [500, 200, 20, 10, 5, 0, 1 ]
k = 700
summary = Counter(sample(colors, weights=weights, k=k))
self.assertEqual(sum(summary.values()), k)
for color, weight in zip(colors, weights):
self.assertLessEqual(summary[color], weight)
self.assertNotIn('brown', summary)

# Case that exhausts the population
k = sum(weights)
summary = Counter(sample(colors, weights=weights, k=k))
self.assertEqual(sum(summary.values()), k)
for color, weight in zip(colors, weights):
self.assertLessEqual(summary[color], weight)
self.assertNotIn('brown', summary)

# Case with population size of 1
summary = Counter(sample(['x'], weights=[10], k=8))
self.assertEqual(summary, Counter(x=8))

# Test error handling
with self.assertRaises(TypeError):
sample(['red', 'green', 'blue'], weights=10, k=10) # weights not iterable
with self.assertRaises(ValueError):
sample(['red', 'green', 'blue'], weights=[-3, -7, -8], k=2) # weights are negative
with self.assertRaises(ValueError):
sample(['red', 'green'], weights=[10, 10], k=21) # population too small
with self.assertRaises(ValueError):
sample(['red', 'green', 'blue'], weights=[1, 2], k=2) # too few weights
with self.assertRaises(ValueError):
sample(['red', 'green', 'blue'], weights=[1, 2, 3, 4], k=2) # too many weights

def test_choices(self):
choices = self.gen.choices
data = ['red', 'green', 'blue', 'yellow']
Expand Down