Elementwise Math
Power and Sqrt
Each element squared in a first loop, then square-rooted in a second loop.
The trace shows squares filling up completely before the second loop starts
building roots, making the two-pass structure explicit.
By hand
Two separate loops over values: the first squares each element into
squares; the second takes math.sqrt and rounds to two decimal places into
roots.
naive.py
Replay: real traced execution (multi-file project)
import math
values = [1, 2, 3, 4, 5, 6]
squares = []
for v in values:
squares.append(v * v)
roots = []
for v in values:
roots.append(round(math.sqrt(v), 2))
print('RESULT:', (squares, roots))
import math
1import math2values = [1, 2, 3, 4, 5, 6]values ← [1, 2, 3, 4, 5, 6]
1import math2values = [1, 2, 3, 4, 5, 6]3squares = []values this step[1, 2, 3, 4, 5, 6]valuessquares ← []
2values = [1, 2, 3, 4, 5, 6]3squares = []4for v in values:values this step[]squaresv ← 1, squares ← [1]
pass 1 of 63squares = []4for v in values:5 squares.append(v * v)6roots = []values this step1v[] → [1]squaresAll 6 passes — pass 1 is the card above pass vsquares1 1 [] → [1] 2 1 → 2 [1] → [1, 4] 3 2 → 3 [1, 4] → [1, 4, 9] 4 3 → 4 [1, 4, 9] → [1, 4, 9, 16] 5 4 → 5 [1, 4, 9, 16] → [1, 4, 9, 16, 25] 6 5 → 6 [1, 4, 9, 16, 25] → [1, 4, 9, 16, 25, 36] for v in values:
3squares = []4for v in values:5 squares.append(v * v)roots ← []
5 squares.append(v * v)6roots = []7for v in values:values this step[]rootsv ← 1, roots ← [1.0]
pass 1 of 66roots = []7for v in values:8 roots.append(round(math.sqrt(v), 2))9print('RESULT:', (squares, roots))values this step6 → 1v[] → [1.0]rootsAll 6 passes — pass 1 is the card above pass vroots1 6 → 1 [] → [1.0] 2 1 → 2 [1.0] → [1.0, 1.41] 3 2 → 3 [1.0, 1.41] → [1.0, 1.41, 1.73] 4 3 → 4 [1.0, 1.41, 1.73] → [1.0, 1.41, 1.73, 2.0] 5 4 → 5 [1.0, 1.41, 1.73, 2.0] → [1.0, 1.41, 1.73, 2.0, 2.24] 6 5 → 6 [1.0, 1.41, 1.73, 2.0, 2.24] → [1.0, 1.41, 1.73, 2.0, 2.24, 2.45] for v in values:
6roots = []7for v in values:8 roots.append(round(math.sqrt(v), 2))stdout ← RESULT: ([1, 4, 9, 16, 25, 36], [1.0, 1.41, 1.73, 2.0, 2.24, 2.45])
8 roots.append(round(math.sqrt(v), 2))9print('RESULT:', (squares, roots))values this stepRESULT: ([1, 4, 9, 16, 25, 36], [1.0, 1.41, 1.73, 2.0, 2.24, 2.45])stdout
With NumPy
a ** 2 raises every element to the power 2 in one call. np.sqrt(a) applies
the square-root ufunc elementwise. Both operations return new arrays without an
explicit loop. The snapshot shows both results in labeled blocks.
library.py
import numpy as np
values = [1, 2, 3, 4, 5, 6]
a = np.array(values)
squares = a ** 2
roots = np.sqrt(a)
sq_vals = squares.tolist()
rt_vals = [round(v, 2) for v in roots.tolist()]
print(f'squares: shape: {squares.shape} dtype: {squares.dtype} values: {sq_vals}')
print(f'roots: shape: {roots.shape} dtype: {roots.dtype} values: {rt_vals}')
print('RESULT:', (sq_vals, rt_vals))
squares: shape: (6,) dtype: int64 values: [1, 4, 9, 16, 25, 36]
roots: shape: (6,) dtype: float64 values: [1.0, 1.41, 1.73, 2.0, 2.24, 2.45]
RESULT: ([1, 4, 9, 16, 25, 36], [1.0, 1.41, 1.73, 2.0, 2.24, 2.45])
Implementation notes
np.sqrt,np.sin,np.log, and similar functions are ufuncs (universal functions). A ufunc applies a C-level scalar kernel to every element of its input array, returning an array of the same shape — the NumPy equivalent of the hand-written loop here.- For this integer array,
a ** 2keeps the integer dtype (int64), whilenp.sqrt(a)promotes tofloat64.np.sqrtpreserves an input's float dtype — e.g. afloat32input staysfloat32. - Shape, dtype, and values are shown explicitly here because
ndarray.__repr__output varies with NumPy version and print options.