Typorithm · medium

Owned random generators and reproducible sampling

pythonnumpynumerical-computingindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Owned random generators and reproducible sampling

0%

Concept and contract

Sample unique identifiers without changing global random state or promising cross-version identical streams.

A random generator is a stateful dependency. Passing a seed to a locally owned Generator makes an experiment repeatable under its recorded environment without resetting random operations elsewhere in the process. This lesson samples unique identifiers without replacement and returns them in draw order. It validates sample size and canonical identifier uniqueness before requesting a draw.

Reproducibility has more conditions than a seed alone. NumPy does not promise identical Generator streams across every version, build, machine and sequence of API calls. Record the library version and generator context, and use invariant tests alongside fixture-specific expected values. The course lab pins its numerical dependency for exactly this reason.

Mechanism and worked example

The worked function constructs default_rng(seed) and calls choice on an explicit int64 identifier array with replace=False.

The worked function constructs default_rng(seed) and calls choice on an explicit int64 identifier array with replace=False. Equal identifiers are rejected before sampling because sampling distinct positions from duplicated values would not establish unique canonical results. A request for more identifiers than exist is rejected rather than silently returning a partial sample.

The lab checks sample values under its pinned environment, exact dtype and shape, uniqueness and unchanged caller input. It also checks that the legacy global NumPy random state does not change. This makes ownership observable rather than inferring it from whether the result looks random.

Complete worked example

python

import numpy as np

def inspect_example(identifiers, count, seed):
    ids = np.asarray(identifiers, dtype=np.int64)
    if ids.ndim != 1 or np.unique(ids).size != ids.size:
        raise ValueError("expected unique identifiers")
    if type(count) is not int or not 0 <= count <= ids.size:
        raise ValueError("sample size exceeds the population")
    rng = np.random.default_rng(seed)
    result = rng.choice(ids, size=count, replace=False)
    return result
Reasoning
The worked function constructs default_rng(seed) and calls choice on an explicit int64 identifier array with replace=False. Equal identifiers are rejected before sampling because sampling distinct positions from duplicated values would not establish unique canonical results. A request for more identifiers than exist is rejected rather than silently returning a partial sample. The lab checks sample values under its pinned environment, exact dtype and shape, uniqueness and unchanged caller input. It also checks that the legacy global NumPy random state does not change. This makes ownership observable rather than inferring it from whether the result looks random.
Open in local Python playground ↗

Focused decision: context is in the complete example

python

rng = np.random.default_rng(seed)
result = rng.choice(ids, size=count, replace=False)
Focused decision
Type this short numerical decision after reading its complete context. The complete imports, array construction, validation, and result checks remain in the worked example. Typing records practice; only the downloadable computer lab executes Python.

Counterexamples and common mistakes

The controlled mistake seeds and consumes the global legacy generator.

The controlled mistake seeds and consumes the global legacy generator. It can make one isolated demonstration repeatable while unexpectedly changing another test or application component. A second mistake is enabling replacement and assuming that a sample count implies that many unique identifiers. A third is returning the first n rows and calling that a random sample because those values happen to be valid.

Sorting a random sample afterwards changes presentation order but not membership. It does not improve representativeness, remove sampling bias in the source population, or make a small sample a complete market estimate. Those are statistical questions outside the numerical generator interface.

Controlled mistake: inspect the failing numerical contract

python

import numpy as np

def inspect_example(identifiers, count, seed):
    ids = np.asarray(identifiers, dtype=np.int64)
    if ids.ndim != 1 or np.unique(ids).size != ids.size:
        raise ValueError("expected unique identifiers")
    if type(count) is not int or not 0 <= count <= ids.size:
        raise ValueError("sample size exceeds the population")
    np.random.seed(seed)
    result = np.random.choice(ids, size=count, replace=False)
    return result
Reasoning
The controlled mistake seeds and consumes the global legacy generator. It can make one isolated demonstration repeatable while unexpectedly changing another test or application component. A second mistake is enabling replacement and assuming that a sample count implies that many unique identifiers. A third is returning the first n rows and calling that a random sample because those values happen to be valid. Sorting a random sample afterwards changes presentation order but not membership. It does not improve representativeness, remove sampling bias in the source population, or make a small sample a complete market estimate. Those are statistical questions outside the numerical generator interface.
Open in local Python playground ↗

Independent practice

Keep the same sampled membership but return the identifiers in increasing order.

Keep the same sampled membership but return the identifiers in increasing order. Preserve the local generator, seed handling, no-replacement policy and validation. The task should add a presentation-order operation after the draw rather than change how random choices are made.

Run the baseline twice with the same inputs and compare the full result. Then predict that the changed result contains exactly those identifiers in sorted order. Include a zero-size request and a request for the entire input to distinguish an empty sample from an invalid oversized one.

Step 1: Sort the sampled identifiers for presentation without changing sampled membership. Step 2: Retain the working imports, input validation and array shapes. Change the stated policy, run the supplied practice cases, then explain the newly accepted or rejected boundary.

Practice starter: solve before reading the solution

python

import numpy as np

def inspect_example(identifiers, count, seed):
    ids = np.asarray(identifiers, dtype=np.int64)
    if ids.ndim != 1 or np.unique(ids).size != ids.size:
        raise ValueError("expected unique identifiers")
    if type(count) is not int or not 0 <= count <= ids.size:
        raise ValueError("sample size exceeds the population")
    rng = np.random.default_rng(seed)
    result = rng.choice(ids, size=count, replace=False)
    return result

# Independent task: Sort the sampled identifiers for presentation without changing sampled membership.
Reasoning
Sort the sampled identifiers for presentation without changing sampled membership.
Open in local Python playground ↗
  • Hint 1: Keep the generator and choice call unchanged.
  • Hint 2: Apply np.sort to result after sampling.

Solution, tests and trade-offs

The solution sorts the returned draw without touching the original identifier array.

The solution sorts the returned draw without touching the original identifier array. The practice fixtures compare membership and order, while the global-state check still rejects a legacy seeding shortcut. A zero-size sample is an empty int64 vector; it is not an error when the source population is non-empty or empty and the requested count is zero.

Deterministic fixture outputs are evidence about this recorded build, not a permanent promise about all NumPy releases. For broader compatibility, test uniqueness, membership and requested size independently. A later statistics track will add sampling design and uncertainty rather than treating an RNG call as a sufficient research method.

Practice solution and acceptance checks

python

import numpy as np

def inspect_example(identifiers, count, seed):
    ids = np.asarray(identifiers, dtype=np.int64)
    if ids.ndim != 1 or np.unique(ids).size != ids.size:
        raise ValueError("expected unique identifiers")
    if type(count) is not int or not 0 <= count <= ids.size:
        raise ValueError("sample size exceeds the population")
    rng = np.random.default_rng(seed)
    result = rng.choice(ids, size=count, replace=False)
    return np.sort(result)
Reasoning
The solution sorts the returned draw without touching the original identifier array. The practice fixtures compare membership and order, while the global-state check still rejects a legacy seeding shortcut. A zero-size sample is an empty int64 vector; it is not an error when the source population is non-empty or empty and the requested count is zero. Deterministic fixture outputs are evidence about this recorded build, not a permanent promise about all NumPy releases. For broader compatibility, test uniqueness, membership and requested size independently. A later statistics track will add sampling design and uncertainty rather than treating an RNG call as a sufficient research method.
Open in local Python playground ↗

Interview reasoning and next step

Explain local generator ownership, sampling without replacement and the difference between membership and presentation order.

Explain local generator ownership, sampling without replacement and the difference between membership and presentation order. State what metadata is needed to reproduce a numerical experiment and why changing the order or sizes of random calls can change subsequent results. Discuss why uniqueness of source identifiers matters before drawing positions.

Type the short default_rng and choice sequence on the PC, then recall replace=False. In the app, reason about duplicate identifiers and oversized requests. Next, persist a numerical array in an owned file and validate the data that is actually loaded back.

Optional stretch — after the core task works: Add one independently reasoned boundary case and explain its expected value before running it.

Computer practice: https://applydjinn.com/typorithm/numpy-lab-20261106.zip. Install pinned dependencies in an isolated Python environment. Run verify.py, then change the working exercise baseline and run --practice np-random. The real NumPy arrays, numerical contracts, random state checks and temporary numeric files use owned fixtures only. No production database, feed, account or paid service is used. This lab executes Python; the PC typing trainer and native reading progress do not execute it.

Sample unique identifiers without changing global random state or promising cross-version identical streams.