Here's a vectorized approach that uses NumPy's powerful broadcasting feature -
def multiplication_table_vectorized(n):
base = np.arange(n)+1
return base[:,None]*base
Runtime test -
In [33]: n = 100
In [34]: np.allclose(multiplication_table(n),multiplication_table_vectorized(n))
Out[34]: True
In [35]: %timeit multiplication_table(n)
100 loops, best of 3: 10.1 ms per loop
In [36]: %timeit multiplication_table_vectorized(n)
10000 loops, best of 3: 58.9 µs per loop
Explanation -
Let's take a toy example for explaining things here.
In [72]: n = 4 # Small n for toy example
In [73]: base = np.arange(n)+1 # Same as original: "base = list(range(1, n+1))"
In [74]: base # Checkback
Out[74]: array([1, 2, 3, 4])
In [75]: base[:,None] # Major thing happening as we extend base to a 2D array
# with all elements "pushed" as rows (axis=0) and thus
# creating a singleton dimension along columns (axis=1)
Out[75]:
array([[1],
[2],
[3],
[4]])
In [76]: base[:,None]*base # Broadcasting happens as elementwise multiplications
# take place between 2D extended version of 'base'
# and original 'base'. This is our desired output.
# To visualize a broadcasting :
# |--------->
# |
# |
# |
# V
Out[76]:
array([[ 1, 2, 3, 4],
[ 2, 4, 6, 8],
[ 3, 6, 9, 12],
[ 4, 8, 12, 16]])
For more information and examples on broadcasting, there's nothing better than the official docs. Broadcasting is one of the best vectorization tools around available with NumPy that allows for such automatic expansions.