Broadcasting
Column Broadcast
A 2-row × 3-column grid and a 2-element column vector added together by hand.
A nested loop walks each row index i, then each cell value x in that row,
adding the row's scalar from col[i]. The trace shows new_row filling cell
by cell and result growing row by row.
By hand
Outer loop over row index i. Inner loop over each cell value x in grid[i]:
add col[i] to x and append to new_row. After the inner loop, append
new_row to result.
naive.py
Replay: real traced execution (multi-file project)
grid = [[1, 2, 3], [4, 5, 6]]
col = [10, 20]
result = []
for i in range(len(grid)):
new_row = []
for x in grid[i]:
new_row.append(x + col[i])
result.append(new_row)
print('RESULT:', result)
grid ← [[1, 2, 3], [4, 5, 6]]
1grid = [[1, 2, 3], [4, 5, 6]]2col = [10, 20]values this step[[1, 2, 3], [4, 5, 6]]gridcol ← [10, 20]
1grid = [[1, 2, 3], [4, 5, 6]]2col = [10, 20]3result = []values this step[10, 20]colresult ← []
2col = [10, 20]3result = []4for i in range(len(grid)):values this step[]resulti ← 0
3result = []4for i in range(len(grid)):5 new_row = []values this step0inew_row ← []
4for i in range(len(grid)):5 new_row = []6 for x in grid[i]:values this step[]new_rowx ← 1
5new_row = []6for x in grid[i]:7 new_row.append(x + col[i])values this step1xnew_row ← [11]
6for x in grid[i]:7 new_row.append(x + col[i])8result.append(new_row)values this step[] → [11]new_rowx ← 2
5new_row = []6for x in grid[i]:7 new_row.append(x + col[i])values this step1 → 2xnew_row ← [11, 12]
6for x in grid[i]:7 new_row.append(x + col[i])8result.append(new_row)values this step[11] → [11, 12]new_rowx ← 3
5new_row = []6for x in grid[i]:7 new_row.append(x + col[i])values this step2 → 3xnew_row ← [11, 12, 13]
6for x in grid[i]:7 new_row.append(x + col[i])8result.append(new_row)values this step[11, 12] → [11, 12, 13]new_rowfor x in grid[i]:
5new_row = []6for x in grid[i]:7 new_row.append(x + col[i])result ← [[11, 12, 13]]
7 new_row.append(x + col[i])8 result.append(new_row)9print('RESULT:', result)values this step[] → [[11, 12, 13]]resulti ← 1
3result = []4for i in range(len(grid)):5 new_row = []values this step0 → 1inew_row ← []
4for i in range(len(grid)):5 new_row = []6 for x in grid[i]:values this step[11, 12, 13] → []new_rowx ← 4
5new_row = []6for x in grid[i]:7 new_row.append(x + col[i])values this step3 → 4xnew_row ← [24]
6for x in grid[i]:7 new_row.append(x + col[i])8result.append(new_row)values this step[] → [24]new_rowx ← 5
5new_row = []6for x in grid[i]:7 new_row.append(x + col[i])values this step4 → 5xnew_row ← [24, 25]
6for x in grid[i]:7 new_row.append(x + col[i])8result.append(new_row)values this step[24] → [24, 25]new_rowx ← 6
5new_row = []6for x in grid[i]:7 new_row.append(x + col[i])values this step5 → 6xnew_row ← [24, 25, 26]
6for x in grid[i]:7 new_row.append(x + col[i])8result.append(new_row)values this step[24, 25] → [24, 25, 26]new_rowfor x in grid[i]:
5new_row = []6for x in grid[i]:7 new_row.append(x + col[i])result ← [[11, 12, 13], [24, 25, 26]]
7 new_row.append(x + col[i])8 result.append(new_row)9print('RESULT:', result)values this step[[11, 12, 13]] → [[11, 12, 13], [24, 25, 26]]resultfor i in range(len(grid)):
3result = []4for i in range(len(grid)):5 new_row = []stdout ← RESULT: [[11, 12, 13], [24, 25, 26]]
8 result.append(new_row)9print('RESULT:', result)values this stepRESULT: [[11, 12, 13], [24, 25, 26]]stdout
With NumPy
c = np.array(col).reshape(2, 1) turns the 1-D vector into a (2, 1) column
array. a + c broadcasts c across all 3 columns, adding each row's scalar
to every element in that row.
library.py
import numpy as np
grid = [[1, 2, 3], [4, 5, 6]]
col = [10, 20]
a = np.array(grid)
c = np.array(col).reshape(2, 1)
result = a + c
print('shape:', a.shape)
print('dtype:', result.dtype)
print('values:', result.tolist())
print('RESULT:', result.tolist())
shape: (2, 3)
dtype: int64
values: [[11, 12, 13], [24, 25, 26]]
RESULT: [[11, 12, 13], [24, 25, 26]]
Implementation notes
- Broadcasting rule:
cis(2, 1),ais(2, 3)— the rows match and the column dimension ofcis 1, so NumPy stretches it across all 3 columns without copying memory. - Contrast with
add-vector-to-rowswhere the vector was(3,)and broadcast across rows. Here the vector is(2, 1)and broadcast across columns. - The
.reshape(2, 1)call is the key step — without it,colwould be(2,)and NumPy would attempt a row broadcast (wrong shape) and raise an error. - Shape, dtype, and values are shown explicitly here because
ndarray.__repr__output varies with NumPy version and print options.