\[ \begin{align}\begin{aligned}\newcommand\blank{~\underline{\hspace{1.2cm}}~}\\% Bold symbols (vectors) \newcommand\bs[1]{\mathbf{#1}}\\% Differential \newcommand\dd[2][]{\mathrm{d}^{#1}{#2}} % use as \dd, \dd{x}, or \dd[2]{x}\\% Poor man's siunitx \newcommand\unit[1]{\mathrm{#1}} \newcommand\num[1]{#1} \newcommand\qty[2]{#1~\unit{#2}}\\\newcommand\per{/} \newcommand\squared{{}^2} \newcommand\cubed{{}^3} % % Scale \newcommand\milli{\unit{m}} \newcommand\centi{\unit{c}} \newcommand\kilo{\unit{k}} \newcommand\mega{\unit{M}} % % Percent \newcommand\percent{\unit{{\kern-4mu}\%}} % % Angle \newcommand\radian{\unit{rad}} \newcommand\degree{\unit{{\kern-4mu}^\circ}} % % Time \newcommand\second{\unit{s}} \newcommand\s{\second} \newcommand\minute{\unit{min}} \newcommand\hour{\unit{h}} % % Distance \newcommand\meter{\unit{m}} \newcommand\m{\meter} \newcommand\inch{\unit{in}} \newcommand\foot{\unit{ft}} % % Force \newcommand\newton{\unit{N}} \newcommand\kip{\unit{kip}} % kilopound in "freedom" units - edit made by Sri % % Mass \newcommand\gram{\unit{g}} \newcommand\g{\gram} \newcommand\kilogram{\unit{kg}} \newcommand\kg{\kilogram} \newcommand\grain{\unit{grain}} \newcommand\ounce{\unit{oz}} \newcommand\pound{\unit{lbs}} % % Temperature \newcommand\kelvin{\unit{K}} \newcommand\K{\kelvin} \newcommand\celsius{\unit{{\kern-4mu}^\circ C}} \newcommand\C{\celsius} \newcommand\fahrenheit{\unit{{\kern-4mu}^\circ F}} \newcommand\F{\fahrenheit} % % Area \newcommand\sqft{\unit{sq\,\foot}} % square foot % % Volume \newcommand\liter{\unit{L}} \newcommand\gallon{\unit{gal}} % % Frequency \newcommand\hertz{\unit{Hz}} \newcommand\rpm{\unit{rpm}} % % Voltage \newcommand\volt{\unit{V}} \newcommand\V{\volt} \newcommand\millivolt{\milli\volt} \newcommand\mV{\milli\volt} \newcommand\kilovolt{\kilo\volt} \newcommand\kV{\kilo\volt} % % Current \newcommand\ampere{\unit{A}} \newcommand\A{\ampere} \newcommand\milliampereA{\milli\ampere} \newcommand\mA{\milli\ampere} \newcommand\kiloampereA{\kilo\ampere} \newcommand\kA{\kilo\ampere} % % Resistance \newcommand\ohm{\Omega} \newcommand\milliohm{\milli\ohm} \newcommand\kiloohm{\kilo\ohm} % correct SI spelling \newcommand\kilohm{\kilo\ohm} % "American" spelling used in siunitx \newcommand\megaohm{\mega\ohm} % correct SI spelling \newcommand\megohm{\mega\ohm} % "American" spelling used in siunitx % % Capacitance \newcommand\farad{\unit{F}} \newcommand\F{\farad} \newcommand\microfarad{\micro\farad} \newcommand\muF{\micro\farad} % % Inductance \newcommand\henry{\unit{H}} \newcommand\H{\henry} \newcommand\millihenry{\milli\henry} \newcommand\mH{\milli\henry} % % Power \newcommand\watt{\unit{W}} \newcommand\W{\watt} \newcommand\milliwatt{\milli\watt} \newcommand\mW{\milli\watt} \newcommand\kilowatt{\kilo\watt} \newcommand\kW{\kilo\watt} % % Energy \newcommand\joule{\unit{J}} \newcommand\J{\joule} % % Composite units % % Torque \newcommand\ozin{\unit{\ounce}\,\unit{in}} \newcommand\newtonmeter{\unit{\newton\,\meter}} % % Pressure \newcommand\psf{\unit{psf}} % pounds per square foot \newcommand\pcf{\unit{pcf}} % pounds per cubic foot \newcommand\pascal{\unit{Pa}} \newcommand\Pa{\pascal} \newcommand\ksi{\unit{ksi}} % kilopound per square inch \newcommand\bar{\unit{bar}} \end{aligned}\end{align} \]

Apr 28, 2026 | 1525 words | 15 min read

13.1.1. Materials#

Repetition Structures#

Repetition structures, also known as iteration structures or loops, allow us to execute a block of code multiple times based on a condition or a sequence. When the number of iterations is known beforehand, we call it a definite loop. When the number of iterations is not known beforehand, we call it an indefinite loop.

Definite loop

the number of iterations is known beforehand

Indefinite loop

the number of iterations is not known beforehand

In Python, we have two different keywords we can use to create loops: while and for. Their usage is summarized below.

01_repetition_structures.py 02_while_loop.py 03_for_loop.py 04_nested_loops.py 05_control_statements.py

The while Loop#

A while loop is used to repeatedly execute a block of statements as long as its condition expression evaluates to True. They are typically used when the number of iterations is not known beforehand (i.e. an indefinite loop). However, they can also be used to create definite loops.

Definite while Loops#

To create a definite while loop, we:

  1. initialize a control variable before entering the loop

  2. use the control variable in the condition expression, entering the loop if the expression evaluates to True.

  3. update the control variable inside the loop such that the condition expression will eventually evaluate to False

Example:

count = 1  # initialize a control variable
while count <= 5:  # check the control variable
    print("Count is:", count)
    count += 1  # update the control variable
Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5

Infinite while Loops#

An infinite loop is a loop that never ends. This is usually caused by a mistake in the code. It occurs when the condition expression never evaluates to False. A common cause of infinite loops is forgetting to update the control variable inside the loop.

Example:

count = 1  # initialize a control variable
while count <= 5:  # check the control variable
    print("Count is:", count)
    # missing update of the control variable

Warning

The above code will create an infinite loop because the control variable count is never updated inside the loop. Infinite loops can cause your program to become unresponsive. If you accidentally create an infinite loop, you can stop it by pressing Ctrl + C in your terminal.

Indefinite while Loops#

To create an indefinite while loop, we set the condition expression such that it depends on some external factor such as user input. There is no way of knowing ahead of time, how many times the loop will execute.

Example:

In this example, the user enters "yes" the first two times they are prompted for input, and then enters "yes!" the third time, which causes the loop to terminate because "yes!" is not equal to "yes".

response = input("Would you like to start the program? (yes/no): ")
while response == "yes":
    print("The program is running.")
    response = input("Would you like to run the program again? (yes/no): ")
print("The program has ended.")
Would you like to start the program? (yes/no): yes
The program is running.
Would you like to run the program again? (yes/no): yes
The program is running.
Would you like to run the program again? (yes/no): yes!
The program has ended.

Sentinel-Controlled while Loops#

The sentinel-controlled loop is a common type of indefinite loop where the loop continues until the a a special value called the sentinel value is encountered. The sentinel value should be distinct from other values in the sequence. Common examples of sentinel values include: \(0\), \(-1\), q, Q, quit, done, and EOF (end of file).

Example:

number = int(input("Enter a number (-1 to stop): "))
while number != -1:
    number = int(input("Enter a number (-1 to stop): "))
print("You entered -1. The program has stopped.")
Enter a number (-1 to stop): 1
Enter a number (-1 to stop): 6
Enter a number (-1 to stop): 7
Enter a number (-1 to stop): -1
You entered -1. The program has stopped.

The for Loop#

A for loop is used to iterate over a sequence or other iterable objects. They are typically used when the number of iterations is known beforehand (i.e. a definite loop).

Definite for Loops#

To create a definite for loop, we use the for keyword followed by a loop variable, the in keyword, and an iterable object such as a list, tuple, string, or range. The loop variable takes on the value of each element in the iterable object one by one, and the block of code inside the loop is executed for each element.

Example:

for i in range(1, 6):  # iterate over a range of numbers from 1 to 5
    print("Count is:", i)
Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5
Table 13.1 Loop Comparison#

Feature

The for Loop

The while Loop

Use Case

When the number of iterations is known

When the number of iterations is not known

Syntax

for variable in iterable:

while condition:

Control Variable

Implicitly defined by the iterable

Explicitly defined and updated by the programmer

Readability

More concise and easier to read for definite loops

Can be less readable for complex conditions

Nested Loops#

A nested loop is a loop inside another loop. The inner loop is executed completely for each iteration of the outer loop. Both for loops and while loops can be nested within each other.

Example

# Nested for loop to print a multiplication table
for i in range(1, 6):  # Outer loop for rows
    for j in range(1, 6):  # Inner loop for columns
        print(f"{i * j:4}", end=' ')  # Print product with formatting
    print()  # New line after each row
   1    2    3    4    5 
   2    4    6    8   10 
   3    6    9   12   15 
   4    8   12   16   20 
   5   10   15   20   25 

Control Statements#

Control statements can be used to alter the flow of execution within loops.

break

Exits the loop immediately, regardless of the loop’s condition. Control is transferred to the statement immediately following the loop.

continue

Causes the loop to skip the remainder of its body and immediately retest its condition prior to reiterating.

pass

A null operation; nothing happens when it is executed. It is useful as a placeholder when a statement is required syntactically but no action is needed or desired.

return

While not strictly a loop control statement, it is often used within functions that contain loops to exit the function and return a value to the caller. A return statement can be used to exit a loop when that loop is inside a function.

More Data Structures#

Lists#

A list is a collection of elements of different types stored in potentially non-contiguous memory locations. Lists are mutable, meaning that their elements can be changed after creation. Lists are one of the most commonly used data structures in Python due to their flexibility and ease of use. See the Python Official Documentation on Lists for more details.

# Creating a list
my_list = [1, 2, 3, 4, 5]
print(my_list)

# Creating a list with different data types
my_list = [1, "Hello", 3.14, True]
print(my_list)

# Creating a list with a for loop
my_squares = []
for i in range(1, 6):
    my_squares.append(i ** 2)
print(my_squares)

# This can equivalently be done using list comprehension
my_squares = [i ** 2 for i in range(1, 6)]
print(my_squares)
[1, 2, 3, 4, 5]
[1, 'Hello', 3.14, True]
[1, 4, 9, 16, 25]
[1, 4, 9, 16, 25]

Common List Methods and Operations#

For a list m, index i, iterable object t, and arbitrary object x:

  • m[i] = x replaces the element at index i with x

  • m.append(x) adds x to the end of the list

  • m.insert(i, x) inserts x at index i

  • m.remove(x) removes the first occurrence of x

  • m.pop(i) removes the element at index i

  • m.clear() removes all elements

  • m.extend(t) adds the elements of t to the end of the list

  • m += t adds the elements of t to the end of the list

  • m *= n repeats the list n times

  • m.reverse() reverses the list

  • m.sort() sorts the list

  • m.copy() returns a shallow copy of the list

  • m.index(x) returns the index of the first occurrence of x

  • m.count(x) returns the number of occurrences of x

  • len(m) returns the number of elements in m

  • min(m) returns the smallest element in m

  • max(m) returns the largest element in m

  • sum(m) returns the sum of elements in m

  • sorted(m) returns a new sorted list

Indexing and Slicing:#

  • m[i] returns the element at index i

  • m[i:j] returns the elements from index i to j-1

  • m[i:j:k] returns the elements from index i to j-1 with step k

  • m[-i] returns the element at index -i

  • m[-i:] returns the elements from index -i to the end of the list

  • m[:-i] returns the elements from the beginning of the list to index -i

  • m[::-1] returns the elements in reverse order

Example#

Indexing and slicing can be applied to strings just like lists since they are also sequences.

Table 13.2 String indexing in Python#

H

e

l

l

o

W

o

r

l

d

,

H

a

i

l

P

u

r

d

u

e

!

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

-25

-24

-23

-22

-21

-20

-19

-18

-17

-16

-15

-14

-13

-12

-11

-10

-9

-8

-7

-6

-5

-4

-3

-2

-1

# string initialization
s = "Hello World, Hail Purdue!"

print(s[0])  # = 'H'
print()
print(s[18:24])  # = 'Purdue'
print(s[-7:-1])  # = 'Purdue'
print()
print(s[0:5])    # = 'Hello'
print(s[:5])     # = 'Hello'
print()
print(s[-1])         # = '!'
print(s[24])         # = '!'
print(s[-1:])        # = '!'
print(s[24:])        # = '!'
print(s[len(s)-1])   # = '!'
print(s[-1:len(s)])  # = '!'
print()
print(s[:-1])  # = 'Hello World, Hail Purdue'
print(s[:24])  # = 'Hello World, Hail Purdue'
print()
print(s[::-1])       # = '!eudruP liaH ,dlroW olleH'
print(s[-1:-26:-1])  # = '!eudruP liaH ,dlroW olleH'
H

Purdue
Purdue

Hello
Hello

!
!
!
!
!
!

Hello World, Hail Purdue
Hello World, Hail Purdue

!eudruP liaH ,dlroW olleH
!eudruP liaH ,dlroW olleH

Nested Lists for storing multi-dimensional data#

Lists can be nested within other lists to create multi-dimensional data structures, such as matrices (2D lists) or tensors (3D lists and higher). We use repeated indexing to access elements in nested lists. For example:

  • One-dimensional list: list_name[index]

  • Two-dimensional list: list_name[row][column]

  • Multidimensional list: list_name[dim1][dim2][dim3]...

Examples of creating different dimensional lists are shown below.

One-Dimensional List:#

Here we create a literal one-dimensional list containing elements of different types.

my_list = [1, "a", True, ...]
print(my_list)
[1, 'a', True, Ellipsis]
Two-Dimensional List:#

Here we create a literal two-dimensional list (matrix) containing integers, strings, and boolean values.

my_2d_list = [
    [1, 2, 3, 4],
    ["a", "b", "c"],
    [True, False]
]
print(my_2d_list)
[[1, 2, 3, 4], ['a', 'b', 'c'], [True, False]]

We can also use loops to create a 2D list (matrix).

# Creating a 2D list (matrix) with 2 rows and 3 columns using nested for loops
my_2d_list = []
for i in range(2):  # 2 rows
    row = []
    for j in range(3):  # 3 columns
        row.append(i * 3 + j)  # Fill with numbers from 0 to 5
    my_2d_list.append(row)
print(my_2d_list)
[[0, 1, 2], [3, 4, 5]]
Multidimensional List:#

Here we create a literal multidimensional list (3D and higher).

# Creating a 2x3x2 multidimensional list (2 rows, 3 columns, 2 depth)
my_3d_list = [
    [
        [1, 2, 3],
        [4, 5, 6]
    ],
    [
        [7, 8, 9],
        [10, 11, 12]
    ]
]
print(my_3d_list)
[[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]

This can equivalently be done with nested loops.

my_3d_list = []
counter = 1
for i in range(2): # Outer loop: Iterates through the 2 depth layers
    depth = []
    for j in range(2): # Middle loop: Iterates through the 2 sub-lists (rows)
        row = []
        # Inner loop: Appends the next 3 sequential numbers
        for k in range(3):
            row.append(counter)
            counter += 1
        depth.append(row)
    my_3d_list.append(depth)

print(my_3d_list)
[[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]

Or by nesting list comprehensions.

my_3d_list = [[[6*i + 3*j + k + 1 for k in range(3)] for j in range(2)] for i in range(2)]
print(my_3d_list)
[[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]

Python Arrays#

Python has a built-in array module for creating arrays, which are collections of elements of the same type stored in contiguous memory locations. Arrays are more efficient than lists for storing large amounts of homogeneous data; however, all elements must be of the same type and arrays can only be one-dimensional. See the Python Official Documentation on Arrays for more details. Due to these limitations, we will primarily use NumPy arrays when working with large datasets and for storing multi-dimensional data.

NumPy Arrays#

06_numpy_arrays.py

Creating NumPy arrays#

  • Import the NumPy library as np

  • Create an array using the np.array() function

Example

import numpy as np

# Creating a 1D array
array_1d = np.array([1, 2, 3, 4, 5])
print(f"array_1d = {array_1d}")

# Creating a 2D array (matrix)
array_2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"array_2d = \n{array_2d}")

# Creating an array from a PIllow Image object (will be relevant for Py5)
from PIL import Image
# Create a simple 2x2 image with RGB values
image = Image.new("RGB", (2, 2), color=(255, 0, 0))  # Red image
image_array = np.array(image)
print(f"image_array shape = {image_array.shape}")
array_1d = [1 2 3 4 5]
array_2d = 
[[1 2 3]
 [4 5 6]
 [7 8 9]]
image_array shape = (2, 2, 3)

NumPy array attributes#

  • shape Returns the dimensions of the array.

  • size Returns the total number of elements in the array.

  • ndim Returns the number of dimensions of the array.

  • dtype Returns the data type of the elements in the array.

Common NumPy array methods#

  • np.zeros(shape) Creates an array of zeros with the specified shape.

  • np.ones(shape) Creates an array of ones with the specified shape.

  • np.full(shape, value) Creates an array of the specified shape filled with the specified value.

  • np.arange(start, stop, step) Creates an array with a range of values from start to stop with the specified step.

  • np.linspace(start, stop, num) Creates an array with a range of values from start to stop with the specified number of elements.

  • np.random.rand(shape) Creates an array of random values with the specified shape.

  • np.reshape(array, new_shape) Reshapes the array to the specified new shape.

  • np.transpose(array) Transposes the array (swaps rows and columns).

  • np.concatenate((array1, array2), axis) Concatenates two arrays along the specified axis.

  • np.vstack((array1, array2)) Stacks arrays vertically.

  • np.hstack((array1, array2)) Stacks arrays horizontally.

  • np.split(array, indices_or_sections) Splits the array into multiple sub-arrays.

  • np.max(array) Returns the maximum value in the array.

  • np.min(array) Returns the minimum value in the array.

  • np.mean(array) Returns the mean of the values in the array.

  • np.std(array) Returns the standard deviation of the values in the array.

  • np.median(array) Returns the median of the values in the array.

  • np.dot(array1, array2) Computes the dot product of two arrays.

Common NumPy array operations#

With NumPy arrays, mathematical operations can be performed element-wise or using matrix operations. Here are some common operations:

Element-wise operations#

Element-wise operations are operations that are performed on each corresponding element of two arrays of the same shape.

Example

import numpy as np
# create two 2D arrays (matrices)
A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
B = np.array([[9, 8, 7], [6, 5, 4], [3, 2, 1]])

# element-wise addition
C = A + B
print(f"C = \n{C}")

# element-wise multiplication
D = A * B
print(f"D = {D}")
C = 
[[10 10 10]
 [10 10 10]
 [10 10 10]]
D = [[ 9 16 21]
 [24 25 24]
 [21 16  9]]
Broadcasting#

Broadcasting stretches the smaller array across the larger array so that they have compatible shapes for element-wise operations. This allows operations to be performed on arrays of different shapes. Details on the broadcasting rules can be found in the NumPy documentation.

Example

import numpy as np

# create a 2D array (matrix) and a 1D array (vector)
A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
B = np.array([1, 2, 3])

# broadcasting addition
C = A + B

print(f"C = \n{C}")
C = 
[[ 2  4  6]
 [ 5  7  9]
 [ 8 10 12]]
Transpose#

Transposing an array involves swapping its rows and columns. This is done using the np.transpose() function.

Example

import numpy as np

# create a 2D array (matrix)
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# transpose the matrix
transposed_matrix = matrix.transpose()

# Results
print(f"original matrix = \n{matrix}")
print(f"transposed_matrix = \n{transposed_matrix}")
original matrix = 
[[1 2 3]
 [4 5 6]
 [7 8 9]]
transposed_matrix = 
[[1 4 7]
 [2 5 8]
 [3 6 9]]
Slicing#

Slicing is used to access specific elements or sub-arrays within a NumPy array.

Example

import numpy as np

# create a 2D array (matrix)
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# slice the matrix to get the first two rows and first two columns
sub_matrix = matrix[0:2, 0:2]
print(f"sub_matrix = \n{sub_matrix}")
sub_matrix = 
[[1 2]
 [4 5]]