Typorithm · easy

Copies, views and observable mutation

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

Copies, views and observable mutation

0%

Concept and contract

Distinguish shared basic slices from independent advanced indexing and deliberate copies.

An ndarray has a data buffer and metadata describing how to interpret it. A view can change the metadata while referring to the same underlying buffer; a copy owns different data. Basic slicing normally creates a view, while advanced integer indexing creates a copy. Two arrays with equal values therefore need not have the same ownership behavior. The practical question is what changes when one of them is written.

This example owns its starting array, makes an explicitly independent selection, changes that selection, and reports both arrays. It never mutates the caller list. That distinction lets us demonstrate an internal mutation without introducing a side effect into the public function contract.

Mechanism and worked example

The worked function selects the first two values with a basic slice followed by copy.

The worked function selects the first two values with a basic slice followed by copy. It adds ten to the selected buffer and returns the unchanged original, the changed selection and the result of shares_memory. That boolean supports a concrete relationship check rather than relying on Python identity or on how the arrays print. The tests include a one-element vector because a valid slice can be shorter than the requested stop.

Advanced indexing with a list of integer positions produces an independent selection as well. However, an assignment directly to data[indexes] writes into data; it is not equivalent to mutating a separately stored selection. Explain the full expression before inferring ownership.

Complete worked example

python

import numpy as np

def inspect_example(values):
    data = np.array(values, dtype=np.int64, copy=True)
    if data.ndim != 1 or data.size == 0:
        raise ValueError("expected a non-empty vector")
    selected = data[:2].copy()
    selected += 10
    return {"original": data.tolist(), "selected": selected.tolist(),
            "shared": bool(np.shares_memory(data, selected))}
Reasoning
The worked function selects the first two values with a basic slice followed by copy. It adds ten to the selected buffer and returns the unchanged original, the changed selection and the result of shares_memory. That boolean supports a concrete relationship check rather than relying on Python identity or on how the arrays print. The tests include a one-element vector because a valid slice can be shorter than the requested stop. Advanced indexing with a list of integer positions produces an independent selection as well. However, an assignment directly to data[indexes] writes into data; it is not equivalent to mutating a separately stored selection. Explain the full expression before inferring ownership.
Open in local Python playground ↗

Focused decision: context is in the complete example

python

selected = data[:2].copy()
selected += 10
shared = np.shares_memory(data, selected)
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 removes copy.

The controlled mistake removes copy. The numerical update still succeeds, but now the original owned array changes too. This can corrupt a later denominator, a cached feature matrix or a supposedly untouched baseline. Checking only the selected result would miss the defect. Another mistake is assuming that base is always the immediate array you named in source code; views may refer through an ownership chain.

shares_memory answers a storage relationship for these small bounded examples. It is not a measurement of the total process memory or a guarantee that every exotic overlap problem is cheap to solve. Keep the explanation tied to the concrete contiguous arrays used in the exercise.

Controlled mistake: inspect the failing numerical contract

python

import numpy as np

def inspect_example(values):
    data = np.array(values, dtype=np.int64, copy=True)
    if data.ndim != 1 or data.size == 0:
        raise ValueError("expected a non-empty vector")
    selected = data[:2]
    selected += 10
    return {"original": data.tolist(), "selected": selected.tolist(),
            "shared": bool(np.shares_memory(data, selected))}
Reasoning
The controlled mistake removes copy. The numerical update still succeeds, but now the original owned array changes too. This can corrupt a later denominator, a cached feature matrix or a supposedly untouched baseline. Checking only the selected result would miss the defect. Another mistake is assuming that base is always the immediate array you named in source code; views may refer through an ownership chain. shares_memory answers a storage relationship for these small bounded examples. It is not a measurement of the total process memory or a guarantee that every exotic overlap problem is cheap to solve. Keep the explanation tied to the concrete contiguous arrays used in the exercise.
Open in local Python playground ↗

Independent practice

Change the demonstration so that it selects the final two values independently instead of the first two.

Change the demonstration so that it selects the final two values independently instead of the first two. Keep the increment, report format and nonmutation contract unchanged. A single boundary edit should be enough once the copy is in the correct place.

Predict both reported arrays for [1, 2, 3, 4]. Only the selected copy should become [13, 14]; the original should stay [1, 2, 3, 4]. If the original changes, fixing the reported boolean does not fix the ownership error.

Step 1: Independently select and increment the final two values. 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(values):
    data = np.array(values, dtype=np.int64, copy=True)
    if data.ndim != 1 or data.size == 0:
        raise ValueError("expected a non-empty vector")
    selected = data[:2].copy()
    selected += 10
    return {"original": data.tolist(), "selected": selected.tolist(),
            "shared": bool(np.shares_memory(data, selected))}

# Independent task: Independently select and increment the final two values.
Reasoning
Independently select and increment the final two values.
Open in local Python playground ↗
  • Hint 1: Preserve .copy() before any mutation.
  • Hint 2: Change the slice to -2: and leave the overlap check in place.

Solution, tests and trade-offs

The solution retains the explicit copy and changes the row-free vector slice to -2:.

The solution retains the explicit copy and changes the row-free vector slice to -2:. The lab verifies the original values, selected values and actual overlap result, then separately checks the caller input. The layered checks distinguish an internal alias bug from mutation of external input. Empty input is rejected here because there would be no meaningful selected observation to change.

Copying every temporary is not a universal optimization. A view can be appropriate when shared mutation is deliberate and documented. The lesson teaches making that choice explicit and testing it, rather than prescribing one ownership policy for all numerical code.

Practice solution and acceptance checks

python

import numpy as np

def inspect_example(values):
    data = np.array(values, dtype=np.int64, copy=True)
    if data.ndim != 1 or data.size == 0:
        raise ValueError("expected a non-empty vector")
    selected = data[-2:].copy()
    selected += 10
    return {"original": data.tolist(), "selected": selected.tolist(),
            "shared": bool(np.shares_memory(data, selected))}
Reasoning
The solution retains the explicit copy and changes the row-free vector slice to -2:. The lab verifies the original values, selected values and actual overlap result, then separately checks the caller input. The layered checks distinguish an internal alias bug from mutation of external input. Empty input is rejected here because there would be no meaningful selected observation to change. Copying every temporary is not a universal optimization. A view can be appropriate when shared mutation is deliberate and documented. The lesson teaches making that choice explicit and testing it, rather than prescribing one ownership policy for all numerical code.
Open in local Python playground ↗

Interview reasoning and next step

Use an interview example to show how a slice can modify its parent, how a copied selection avoids that effect, and why the identity operator is not an overlap test.

Use an interview example to show how a slice can modify its parent, how a copied selection avoids that effect, and why the identity operator is not an overlap test. Explain the difference between advanced indexing used to create a variable and advanced indexing used on the left side of an assignment.

On the PC, type the copy-before-mutation sequence and recall the decision from the contract. In the app, reason about the two reported buffers. Next, reshape and transpose arrays while preserving their intended axis meaning and recognizing when a layout conversion may require copying.

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-ownership. 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.

Distinguish shared basic slices from independent advanced indexing and deliberate copies.