Module 3 · Lesson 5

Outer Operations & Pairwise Computation via Broadcasting

np.outer(a, b) computes every product a[i] * b[j] and arranges them into a matrix of shape (len(a), len(b)). More generally, you can build any pairwise table — differences, distances, comparisons — by reshaping one array to a column (n, 1) and the other to a row (1, m), then letting broadcasting fill in the full (n, m) grid. This avoids a nested Python loop entirely.

a = np.array([1, 2, 3])
b = np.array([10, 20])

print(np.outer(a, b))               # every a[i] * b[j]

# Pairwise absolute differences, without np.outer:
diffs = np.abs(a[:, np.newaxis] - b[np.newaxis, :])
print(diffs)
print(diffs.shape)                   # (3, 2): one row per element of a

Output:

[[10 20]
 [20 40]
 [30 60]]
[[ 9 19]
 [ 8 18]
 [ 7 17]]
(3, 2)
5.1

Pairwise squared distances

Write pairwise_sq_dist(a, b) that takes two 1-D arrays and returns a 2-D array of shape (len(a), len(b)) where entry [i, j] is (a[i] - b[j]) ** 2. No nested loop — use broadcasting with np.newaxis.

💡 Hint

a[:, np.newaxis] has shape (len(a), 1); b[np.newaxis, :] has shape (1, len(b)). Subtract them, then square.

✅ Solution
def pairwise_sq_dist(a, b):
    return (a[:, np.newaxis] - b[np.newaxis, :]) ** 2
5.2

Multiplication table

Write multiplication_table(n) that returns the n x n multiplication table (entry [i, j] equals (i+1) * (j+1)) using np.outer on np.arange(1, n+1) — not a loop.

💡 Hint

np.arange(1, n + 1) gives [1, 2, ..., n]; pass it to np.outer twice (as both arguments).

✅ Solution
def multiplication_table(n):
    r = np.arange(1, n + 1)
    return np.outer(r, r)