Laboratory 1b: Python Review#

Programming skills

  1. Iteration

  2. Functions

  3. Conditional statements

Required hardware: None

Total: 100 points

Student information#

Write your name and email below.

Name:

Email:

Importing required libraries#

import numpy as np  # Numpy will assist in manipulating datastructures like arrays.
import matplotlib.pyplot as plt # Matplotlib is MATLAB implementation in python for the purpose of plotting and visualising data.
from matplotlib import rcParams  # rcParams

rcParams.update({'font.size': 20})  # for setting text size in plots

Variables and data types#

# --- INTEGER DEFINITION ---
# If no decimal place is provided, Python considers it an integer
a = 9

# --- FLOAT DEFINITION ---
# Specifying a decimal point creates a float
b = 6.05

# --- STRING DEFINITION ---
# Strings are collections of characters, defined with '' or ""
c = 'for'
d = "good"

# --- LIST DEFINITION ---
# Lists are flexible containers that can hold multiple data types
e = [a, b, c]

print("Datatype of variable a = ", type(a))
print("Datatype of variable b = ", type(b))
print("Datatype of variable c = ", type(c))
print("Datatype of variable d = ", type(d))
print("Datatype of variable e = ", type(e))
Datatype of variable a =  <class 'int'>
Datatype of variable b =  <class 'float'>
Datatype of variable c =  <class 'str'>
Datatype of variable d =  <class 'str'>
Datatype of variable e =  <class 'list'>
# Type conversion from foat to int and int to float
print(int(9.08))
print(float(98))
9
98.0

Common operations on variables#

  1. Addition/Subtraction: +, - — for numeric variables these perform arithmetic (e.g., \(3 + 2 = 5\)). For sequences like lists or strings, + performs concatenation.

  2. Multiplication/Division: *, /* multiplies numbers and can repeat sequences; / performs floating-point division (e.g., \(5 / 2 = 2.5\)).

  3. Exponentiation: ** — raises a number to a power (e.g., \(2^3\) is written as 2 ** 3).

  4. Root: Use np.sqrt() for square roots or fractional powers for \(n^{th}\) roots (e.g., 8 ** (1/3) for \(\sqrt[3]{8}\)).

Data structures#

In computer languages, data structures are systematic ways to represent collections of data. This allows us to manipulate, transform, or search through an entire dataset efficiently.

The most common structures in Python are lists and arrays. Both are sequential, meaning elements are arranged linearly and can be accessed by their position (index).

Lists and arrays#

Python lists#

A general-purpose container that can hold a mixture of different types (integers, strings, or even other lists).

  • Pros: Flexible and built into Python.

  • Cons: Slower for mathematical operations and uses more memory.

NumPy arrays#

Specialized for numerical data. All elements are normally of the same type (for example, all floats).

  • Pros: Extremely fast and supports vectorization (applying mathematics to an entire array at once).

  • Cons: Less flexible with mixed data types.

Common list commands#

  1. Append: list.append(x) adds element x to the end.

  2. Delete: del list[i] removes the element at index i.

  3. Insert: list.insert(i, x) adds element x at index position i.

# Example for working with list
list1 = [3, 14.98, 'cab', np.sqrt(2)]
print(list1)

list1.append(np.sqrt(9))
print(list1)

list1.insert(2, -20)  # Adds -20 at index 2 (the 3rd position)
print(list1)

Iteration and indexing#

Both lists and arrays are ordered sequences. Imagine them as a row of boxes, where each box has a specific address called an index.

Zero-based indexing: In Python, we start counting from 0, not 1.

Index

0

1

2

3

4

Content

10

20

30

40

50

  • my_list[0] would give you 10.

  • my_list[4] would give you 50.

We use iterators (such as a for loop) to visit each box in sequence and perform an action.

# example for using for-loop
for i in range(1, 10):
    print("iterating variable value = ", i)
    print("Square of the iterating variable = ", i*i)
# example for summing elements using an index-based loop
array1 = np.array([1, -1, 9, -4, 5])
sum_val = 0
for i in range(0, 5):
    sum_val = sum_val + array1[i]  # the '[i]' keyword after the 'array1' allows one to access the 'ith' element of the array.

print("Total sum of all the elemets =", sum_val)

Problem 1 (5 points). Compute

\[\sum_{i=1}^{50} i.\]

Start with the code below and fill in the missing pieces.

total = 0

for i in range(____, ____):
    total = total + ____

print(total)

Conditional statements#

An if-else statement is a conditional branch. It allows the program to make decisions based on whether a specific condition is True or False.

  1. if: The computer checks the condition. If it is true, the code block directly under it runs.

  2. elif (optional): Short for “else if.” It is checked only if the first if condition was false.

  3. else: The catch-all case. It runs only if none of the preceding conditions were met.

Syntax:

if condition == True:
    # run this block
else:
    # run this block instead
# Determine whether a number is positive, negative, or zero
num = 10
if num > 0:
  print("number is positive")
elif num < 0:
  print("number is negative")
else:
  print("number is zero")
number is positive
# Example for if-else
number_range_lower_bound = 1
number_range_upper_bound = 11

for i in range(number_range_lower_bound, number_range_upper_bound):
    if i % 2 == 0:     # Here the '%' operator checks for remainder. If a number is divisible it return 0, else it gives out the remainder.
        print("iterator ", i, " is even")
    else:  # whenever the remainder is non-zero the if-block fails and the else-block is executed.
        print("iterator ", i, " is odd")

# Note: The computer will execute any one of the condition blocks. When if-block is satisfied, the else-block won't be executed and vice-versa.

Problem 2 (10 points). Iterate through the integers from 1 through 50 and find all the numbers divisible by 3.

Store the divisible numbers in a new list and print the list.

# Write code here

Built-in functions#

Python provides many ready-to-use functions:

  1. np.array(my_list): Converts a Python list into a NumPy array.

  2. len(object): Returns the number of items in a list, array, or string.

# Example of list to array conversion and their dimensions.
list1 = [3, 14.98, 'cab', np.sqrt(2)]
array3 = np.array(list1)
print(array3, array3[2])
print(len(array3[2]), len(list1))

Problem 3 (15 points). Define a list named x containing the integers from \(-10\) through \(10\). Then define a new list y using

\[y = 4x^2.\]

Plot y versus x. The graph should resemble a parabola.

# Write your code here

User-defined functions#

A user-defined function is a reusable block of code that performs a specific task. We define functions using the def keyword.

How to define a function:

  1. def keyword: Tells Python you are creating a function.

  2. Function name: A descriptive name (for example, calculate_area).

  3. Arguments (inputs): Variables passed inside the parentheses ().

  4. Colon :: Ends the function header.

  5. Indented body: Contains the function’s logic.

  6. return statement: Sends the result back to the caller.

Example:

def greet_user(name):
    message = "Hello " + name
    return message
# Example function for adding
def add_nos(a, b):
    c = a + b
    return c
# Example for calling the function defined above
x, y = 9, 6
z = add_nos(x, y)
print("Result obtained from the function above = ", z)

Problem 4 (15 points). Define a function that finds both roots of a quadratic polynomial:

\[ax^2 + bx + c = 0.\]

You may use the quadratic formula:

\[root_1 = \frac{-b + \sqrt{b^2 - 4ac}}{2a}\]
\[root_2 = \frac{-b - \sqrt{b^2 - 4ac}}{2a}\]
# define function
# call function

NumPy array functions#

  1. np.linspace(start, stop, N): Generates \(N\) equally spaced elements.

    • Example: x = np.linspace(0, 10, 5) creates [0, 2.5, 5, 7.5, 10].

  2. np.asarray(list): Converts a Python list to a NumPy array.

    • Example: arr = np.asarray([1, 2, 3]) converts the list to an array.

  3. np.size(array): Returns the total number of elements in the array.

    • Example: np.size(np.array([10, 20])) returns 2.

Problem 5 (20 points). Approximate the integral

\[I = \int_0^1 x^2\,dx\]

using a left Riemann sum.

A Riemann sum approximates the area under \(f(x)\) by summing rectangle areas. The figure below illustrates this idea (source).

riemann_sum_image.png

Let \(L_N\) denote the left Riemann sum when \([0,1]\) is divided into \(N\) equal subintervals:

\[L_N = \sum_{i=0}^{N-1} f(x_i)\,\Delta x,\]

where \(\Delta x = 1/N\) and \(x_i=i\Delta x\).

(a) 5 points: Write code that computes \(L_N\) for \(N=10\).

(b) 5 points: Convert the code from part (a) into a function that computes \(L_N\) for any positive integer \(N\).

(c) 10 points: For Nvals = [10, 100, 1000], compute \(L_N\) for each value and plot \(N\) versus \(L_N\). Explain whether \(L_N\) approaches the exact value of \(I\).

# Write your code here

Problem 6 (20 points). Find where consecutive values in a list change sign. Let x contain 100 equally spaced values from \(-2\) to \(2\), and let y store the corresponding values of

\[y=x^3-x.\]

(a) 5 points: Write a function that calculates y.

(b) 5 points: Given two numbers \(a\) and \(b\), explain what the sign of their product \(ab\) tells you about the signs of \(a\) and \(b\).

(c) 5 points: Write code that identifies where consecutive values in y change sign.

(d) 5 points: Write code that counts how many times y changes sign.

 # Write your code here

Boolean indexing#

Boolean indexing lets you select data from an array using a corresponding array of True and False values.

x = np.array([-3, -1, 2, 5, -4])
x>0
array([False, False,  True,  True, False])

Problem 7: Boolean-indexing check (5 points total). Run the code above and answer the following short questions:

(a) What type of object is the output?

(b) How long is it?

(c) Which entries are True?

(d) What happens when you change the last line to x < 0?

x = np.array([-3, -1, 2, 5, -4])
mask = x > 0
x[mask]
array([2, 5])

Continue Problem 7 using the second code example:

(e) What does x[x > 0] return?

(f) What does x[x > 2] return?

This technique is called Boolean indexing: selecting elements from an array with a Boolean (True/False) array of the same length.

# Example for boolean indexing
x = np.array([1, 3, 5, 7, 6, 4, 2])
y = np.array([True, True, True, False, False, True, False])
print("Filtered x: ", x[y])
Filtered x:  [1 3 5 4]
# Another example for boolean indexing for fiteration
# A list of temperature readings in Celsius
temps = np.array([-5.2, 12.5, 0.4, -1.8, 25.0, 3.2, -10.5])

# 1. Create a Boolean mask
# This checks which elements are greater than 0
mask = temps > 0
print("Boolean Mask:", mask)
# Output: [False  True  True False  True  True False]

# 2. Use the mask to index the array
# This returns only the values where the mask is True
above_freezing = temps[mask]

print("Values above freezing:", above_freezing)
# Output: [12.5  0.4 25.  3.2]
Boolean Mask: [False  True  True False  True  True False]
Values above freezing: [12.5  0.4 25.   3.2]

Slicing#

We can also take slices of an array.

arr[a:b]
  • This selects elements starting at index a

  • This stops selecting elements before index b

  • Index b is not included

arr[1:]
  • This selects elements starting at index 1

  • Recall, in python, arrays start at index 0

  • The : with nothing after it means go to the end

  • It returns all elements except the first one

arr[:-1]
  • The : with nothing before it means start at index 0

  • The -1 refers to the last index

  • It returns all elements except the last one

# --- Example of [:-1] and [1:] Indexing ---
arr = np.array([10, 20, 30, 40, 50])
print(arr)

# arr[:-1] gets all elements EXCEPT the last one: [10, 20, 30, 40]
current_elements = arr[:-1]
print(current_elements)

# arr[1:] gets all elements EXCEPT the first one: [20, 30, 40, 50]
next_elements = arr[1:]
print(next_elements)

# By comparing them, we can find the difference or sign change between neighbors
diff = next_elements - current_elements
print("Difference between neighbors:", diff)
[10 20 30 40 50]
[10 20 30 40]
[20 30 40 50]
Difference between neighbors: [10 10 10 10]

Problem 8 (10 points). Redo Problem 6 using NumPy tools such as np.array(), np.sign(), and Boolean indexing.

Hint: To compare adjacent elements efficiently, use slices such as array[:-1] and array[1:], as shown above.

# Write your code here