Exercises — Indexing and slicing — basic, boolean masking, fancy indexing
Throughout, assume import numpy as np. Where an array is defined once, it carries to the next sub-part unless we redefine it.
Level 1 — Recognition
Goal: read a slice/mask/index and know what comes out, and whether it is a view or a copy.
Exercise 1.1 — Read a 1-D slice
Given a = np.arange(10) (so a = [0,1,2,3,4,5,6,7,8,9]), what is a[2:9:3], and how many elements does it have?
Recall Solution 1.1
WHAT we do: apply start=2, stop=9, step=3. Indices are (next would be , so we stop).
Count from the formula: .
Since a[i] == i here, the values equal the indices:
View or copy? A view — a slice only changes offset/strides.
Exercise 1.2 — Negative step
For the same a, what is a[::-1]? What is a[8:2:-2]?
Recall Solution 1.2
a[::-1] means start=default-last, stop=default-before-first, step=-1 → the array reversed:
a[8:2:-2]: start at index 8, walk down by 2, stop before index 2. Indices: (next is , excluded). Values: .
Count (negative step): replace with . ✓ Both are views.
Exercise 1.3 — Classify each result
For each, say view or copy: (a) a[1:4], (b) a[a > 5], (c) a[[1,1,3]], (d) a[::2].
Recall Solution 1.3
- (a)
a[1:4]— basic slice → view. - (b)
a[a > 5]— boolean mask → copy. - (c)
a[[1,1,3]]— fancy (integer-list) indexing → copy. - (d)
a[::2]— basic slice → view. Rule: Slices Stay (view); Masks & Fancy Make a copy.
Level 2 — Application
Goal: build masks and index arrays yourself and predict the output exactly.
Exercise 2.1 — Filter and count
Given x = np.array([3, -1, 4, -2, 5, -9, 0]). Give (a) the boolean mask x > 0, (b) x[x > 0], (c) how many elements survive.
Recall Solution 2.1
(a) Compare each element to 0, elementwise:
(b) Keep positions where the mask is True, in order: .
(c) Count of True = 3. (Note is False, so zero is dropped.)
Exercise 2.2 — Combining conditions
For the same x, compute x[(x < 0) & (x > -5)]. Then explain in one line why x[(x<0) and (x>-5)] errors.
Recall Solution 2.2
Two masks, then elementwise AND:
x < 0→x > -5→ (only fails> -5)&(elementwise AND) → SelectingTruepositions: values and → Whyanderrors: Python'sandwants a singleTrue/False, but each side is a 7-element array; NumPy refuses to collapse it →ValueError: ambiguous truth value. Use&(elementwise) and wrap each side in parentheses because&binds tighter than<.
Exercise 2.3 — Reorder with fancy indexing
Given v = np.array([10, 20, 30, 40]), produce v[[3, 0, 0, 2]]. What is its length vs v's?
Recall Solution 2.3
Fancy indexing reads positions in the order given, repeats allowed:
- position 3 → 40
- position 0 → 10
- position 0 → 10 (again!)
- position 2 → 30
Length = 4 = length of the index array (which happens to be 4), even though it repeats element 0. The result shape follows the index array, not
v.
Level 3 — Analysis
Goal: reason about 2-D shapes, the fancy-vs-block distinction, and view aliasing.
Let

Exercise 3.1 — Block slice
What is A[0:2, 1:3]? What shape?
Recall Solution 3.1
First index 0:2 picks rows {0,1}; second index 1:3 picks cols {1,2}. The result is their rectangular intersection — the yellow block in the figure:
Both indices are ranges → result stays 2-D.
Exercise 3.2 — Fancy pairs vs block
What is A[[0,2],[1,3]]? Contrast with the block interpretation and give the shape.
Recall Solution 3.2
Fancy index arrays are zipped pointwise, not crossed. Pair them up:
- element 0: row 0, col 1 →
A[0,1] = 1 - element 1: row 2, col 3 →
A[2,3] = 11This is the magenta diagonal pick in the figure — a 1-D result, not the 2×2 block of rows {0,2} × cols {1,3}.
Exercise 3.3 — Make it an actual block
Using rows {0,2} and cols {1,3}, produce the true 2×2 block. What is it?
Recall Solution 3.3
Use np.ix_ to cross the index lists (it reshapes them so they broadcast into a grid):
A[np.ix_([0,2],[1,3])]Rows {0,2} × cols {1,3} =
Exercise 3.4 — View aliasing
Run:
b = A[0] # first row
b[0] = 99What is A[0,0] now, and why?
Recall Solution 3.4
A[0] is a view onto row 0 (basic indexing, shares memory). Writing b[0] = 99 writes into the same buffer, so:
To edit independently: b = A[0].copy().
Level 4 — Synthesis
Goal: chain the three methods and mutate arrays in place.
Exercise 4.1 — Clip negatives in place
Given y = np.array([5, -3, 8, -1, 0, -7]), set every negative element to 0 in place, then report y.
Recall Solution 4.1
Boolean assignment: the mask on the left, a scalar on the right — the scalar broadcasts into every True slot:
y[y < 0] = 0Mask y < 0 = → positions 1,3,5 become 0:
Exercise 4.2 — Top-3 by fancy indexing
Given s = np.array([7, 2, 9, 4, 6]), return its three largest values, largest first, using an index array.
Recall Solution 4.2
Step 1 (WHAT): np.argsort(s) returns indices that sort ascending: s = [7,2,9,4,6] → argsort [1,3,4,0,2] (values ).
Step 2 (WHY reverse): we want largest first, so reverse and take the first three indices: np.argsort(s)[::-1][:3] = [2,0,4].
Step 3: fancy-index with those positions:
s[np.argsort(s)[::-1][:3]]Positions 2,0,4 → values :
Exercise 4.3 — Combine mask then reorder
Given w = np.array([3, -2, 8, -5, 1, 6]): keep only the positive values, then return them sorted descending.
Recall Solution 4.3
Step 1 — mask (copy): pos = w[w > 0] → .
Step 2 — sort descending: np.sort is ascending, so reverse it: np.sort(pos)[::-1].
np.sort([3,8,1,6]) = [1,3,6,8]; reversed → .
Level 5 — Mastery
Goal: predict subtle shape/copy behaviour and design the correct tool.
Exercise 5.1 — Shape follows the index array
Given a = np.array([10,20,30,40]) and idx = np.array([[0,1],[2,3]]), what is a[idx] and its shape?
Recall Solution 5.1
Fancy indexing gives the result the shape of the index array, filling each slot with a[that index]:
A 1-D source became a 2-D result — driven entirely by idx's shape.
Exercise 5.2 — Boolean mask flattens
Given A from Level 3, what is A[A % 2 == 0] and its shape?
Recall Solution 5.2
The mask A % 2 == 0 marks the even cells. Even values in row-major order across
are :
Always 1-D: the True cells don't form a rectangle, so NumPy cannot keep 2-D structure — it gathers them into a flat copy.
Exercise 5.3 — Fancy write with repeats
Given c = np.zeros(5, dtype=int), run c[[0, 1, 1, 2]] = [10, 20, 30, 40]. What is c?
Recall Solution 5.3
Assignment walks the index/value pairs left-to-right:
c[0] = 10c[1] = 20c[1] = 30← overwrites the previous 20c[2] = 40Key insight: with a repeated index, the last write wins — no accumulation. (For accumulation you'd neednp.add.at(c, [0,1,1,2], [10,20,30,40]), which would givec[1]=50.)
Exercise 5.4 — Choose the tool
You have M = np.arange(20).reshape(4,5) and want the sub-matrix at rows {0,3} and columns {1,4} as a 2×2 block. Write the expression and give the result.
Recall Solution 5.4
Two separate index lists zipped pointwise would give only M[0,1], M[3,4] (a 1-D diagonal). For the Cartesian block, use np.ix_:
M[np.ix_([0,3],[1,4])]With :
Recall Self-test summary (reveal after all exercises)
Slice count a[2:9:3] ::: 3 elements → indices 2,5,8
a[8:2:-2] ::: [8,6,4] (stop excluded, downward)
x[(x<0) & (x>-5)] for x=[3,-1,4,-2,5,-9,0] ::: [-1,-2]
v[[3,0,0,2]] for v=[10,20,30,40] ::: [40,10,10,30]
A[[0,2],[1,3]] ::: [1,11], shape (2,) — pointwise pairs
A[np.ix_([0,2],[1,3])] ::: 1,3,[9,11]], the true block
A[A%2==0] ::: [0,2,4,6,8,10], flattened to 1-D
c[[0,1,1,2]] = [10,20,30,40] ::: [10,30,40,0,0] — last write wins
Connections
- Views vs Copies — memory model (Exercises 1.3, 3.4)
- Broadcasting (scalar-into-mask assignment, 4.1)
- np.where and conditional selection (alternative to masked assignment)
- Vectorization — replacing Python loops (why we never loop these)
- NumPy arrays — shape, strides, dtype (why slices are cheap views)
- Pandas .loc / .iloc indexing (same ideas at DataFrame level)