A survey toolkit has this sampling helper, and every caller gets it wrong somehow:
def sample(rows, k, rep, w, s):
import random
if s: random.seed(s) # reseeds the whole program's generator
if rep:
return random.choices(rows, w, k=k)
rows.sort(key=lambda r: str(r)) # changes the caller's list
return random.sample(rows, k) if k <= len(rows) else None
What does sample(data, 5, True, None, 0) do? Write its replacement, sample_rows(rows, k, *, replace=False, weights=None, seed=None):
rowsis any iterable (a list, a tuple, a generator); it is never changed. The result is always a new list ofkrows.- Without replacement (the default), the result is
rng.sample(pool, k), wherepoolislist(rows)in its original order. Withreplace=Trueit isrng.choices(pool, weights=weights, k=k). rngis the globalrandommodule whenseedisNone, and a newrandom.Random(seed)otherwise, so a seeded call is reproducible and does not disturb the global generator.- These calls raise
ValueErrorwith a clear message:k < 0;k > len(pool)without replacement;k > 0with an empty pool and replacement;weightsgiven withoutreplace=True;weightswhose length differs from the number of rows. replace,weightsandseedare keyword-only, sosample_rows(data, 5, True)raisesTypeError.
Setup helpers, available with Run: households(n, seed) generates survey rows, raises(fn, *args, **kwargs) returns the name of the exception a call raises (or None), and untouched(data) calls your function several ways and reports whether data and the global generator were left alone.
Examples
Input: sample_rows(["a", "b", "c", "d", "e"], 3, seed=7)
Output: ["c", "b", "d"]
Input: sample_rows((r for r in "xyz"), 4, replace=True, weights=[0, 0, 1], seed=1)
Output: ["z", "z", "z", "z"]
Input: raises(sample_rows, [1, 2], 3), raises(sample_rows, [1, 2], 1, True)
Output: ("ValueError", "TypeError")
Constraints
- Up to
10**5rows;seedisNoneor a whole number (including0).
Goals
- Replace positional flags with keyword-only options and sensible defaults
- Validate combinations of options and fail with clear errors
- Never change the caller's data, and always return the same kind of value