def f_a_v2(m, n):
M = numpy.ones((m, n))
x = numpy.arange(m).reshape((m, 1))
y = numpy.arange(n).reshape((1, n))
M = M * x
M = M * y
return M
M = numpy.ones((m, n))
M with shape (m, n), filled entirely with the value 1.M[i, j] is 1 for every i, j.x = numpy.arange(m).reshape((m, 1))
y = numpy.arange(n).reshape((1, n))
numpy.arange(m)
Creates a numpy array of integers from 0 to m - 1.
Example: if m = 3, numpy.arange(3) is [0, 1, 2].
.reshape((m, 1))
Reshapes the 1D array into a 2D array with m rows and 1 column.
For m = 3, x becomes:
$$ \begin{bmatrix} 0\\ 1\\ 2 \end{bmatrix} $$
So, x is shape (3, 1).
numpy.arange(n).reshape((1, n))
0 to n - 1, then reshapes it into one row and n columns.n = 4, y becomes:$$ [0\ 1\ 2\ 3] $$
So, y has shape (1, 4).
M = M * x
M = M * y
First multiplication: M = M * x
Since M was all ones of shape (m, n), and x is shape (m, 1), the multiplication is done via numpy broadcasting.
x is “stretched” or “replicated” across all the columns of M.i in M gets multiplied by x[i, 0].M[i, j] = i for all valid i, j.
For example, if m = 3 and n = 4, after the first multiplication:
M = [[0, 0, 0, 0], # row i = 0 (0 * 1 = 0 for all columns)
[1, 1, 1, 1], # row i = 1 (1 * 1 = 1 for all columns)
[2, 2, 2, 2]] # row i = 2 (2 * 1 = 2 for all columns)
Second multiplication: M = M * y
Now M is multiplied by y which has shape (1, n). This time, the array y is broadcasted along each row.
For each column j, the multiplication factor becomes y[0, j] = j.
After this step, each element in M becomes i * j.
Continuing the example with m = 3 and n = 4:
M = [[0, 0, 0, 0], # row i = 0 multiplied by each column index j
[0, 1, 2, 3], # row i = 1 multiplied by each column index j
[0, 2, 4, 6]] # row i = 2 multiplied by each column index j
Hence, the result is a 2D array of shape (m, n) whose element at (i, j) is i * j.