3  Control Structures

Control flow is a fundamental concept in programming that refers to the order in which statements are executed in a program. In Python, control flow is managed through conditional statements and loops. These constructs enable programmers to create programs that can make decisions and perform repetitive tasks based on certain conditions.

Show the code
import numpy as np
import matplotlib.pyplot as plt

# Define grid size
x = np.linspace(-3.0, 3.0, 400)
y = np.linspace(-3.0, 3.0, 400)
X, Y = np.meshgrid(x, y)

# Initialize the Z matrix
Z = np.zeros_like(X)

# Define a function to create loop patterns
def generate_loop_pattern(X, Y, num_loops):
    Z = np.zeros_like(X)
    for i in range(1, num_loops + 1):
        Z += np.sin(X * i) * np.cos(Y * i)
    return Z

# Generate loop patterns
num_loops = 5
Z = generate_loop_pattern(X, Y, num_loops)

# Create plot
plt.figure(figsize=(8, 6))

# Superimpose imshow and contour
plt.imshow(Z, extent=(-3, 3, -3, 3), origin='lower', cmap='Spectral', alpha=0.5)
contour = plt.contour(X, Y, Z, levels=10, colors='black')

# Add color bar and labels
#plt.colorbar(contour, label='Contour Levels')
plt.xlabel('X axis')
plt.ylabel('Y axis')

plt.show()
Pastel Loops

Pastel Loops

The need for control flow arises from the fact that many programs require a degree of flexibility in the way they operate. For example, a program that calculates the average of a list of numbers may need to handle lists of different lengths or perform additional calculations based on the input data. Control flow constructs allow programmers to create programs that can adapt to different scenarios and respond accordingly.

Python provides several control flow constructs, including:

The concept of control flow is not unique to Python and has been present in programming languages for decades. The first programming languages, such as Fortran and COBOL, were designed to handle complex mathematical calculations and did not include control flow constructs. As programming languages evolved and became more versatile, control flow constructs were introduced to enable programmers to create more complex programs.

Today, control flow is a core concept in programming and is used in a wide range of applications, from web development to scientific computing. By providing a flexible and powerful set of control flow constructs, Python enables programmers to create programs that can handle a wide range of scenarios and respond to changing conditions.

3.1 Conditionals

Often a certain process needs to executed if a certain condition is fulfilled. The result of this conditions has to be of the Boolean data type.

a = 0
b = 1100
a > b
False
if a > b:
    print('a is bigger than b')
    print(a**2)
else:
    print('b is bigger than a')
    print(b**2)
b is bigger than a
1210000
Important

Please do not forget to add the colon after the condition and also after the else statement. The indentation is used to specify the sub-process that the program executes when the condition is either satisfied or not satisfied.

3.1.1 Extended Conditions

The keyword elif is used to add additional conditions.

if a > b:
    print('a is bigger than b')
elif a == 0:
    print('a is 0')
else:
    print('a is neither zero or bigger than b')
a is 0

One Liners

The conditional statements can also be included as one-liners !

if a < 1: print('a is less than 1') # One liner if
a is less than 1
print('a is less than one') if a < 1 else print('a greater than 1') # One liner if.. else
a is less than one

and, or

If multiple conditions have to be tested, and and or keywords can be used !

if b > a and a == 0: print('b is greater than a and a is equal to 0')
b is greater than a and a is equal to 0
if b > a or a == 0: print('b is greater than a and a is equal to 0') # short-circuiting
b is greater than a and a is equal to 0
x = 50

if x > 10:
    print("Above ten,")
    if x > 20:
        print("and also above 20!")
        print(x**2)
    else:
        print("but not above 20.")
Above ten,
and also above 20!
2500

3.2 Loop Constructs

Loop constructs are used to repeatedly execute a block of code as long as a specific condition is met. Python provides two main types of loop constructs: the “for” loop and the “while” loop. These loop constructs are fundamental for iterating over sequences, performing repetitive tasks, and controlling the flow of your programs.

3.2.1 While Loop:

The “while” loop in Python repeatedly executes a block of code as long as a specified condition remains true. It’s often used when you don’t know in advance how many times the loop will run.

Here’s the basic structure of a “while” loop in Python:

while condition:
    # Code to be executed as long as the condition is true
  • condition: The loop continues executing as long as this condition evaluates to True.

In addition to these basic loop constructs, Python also supports control statements like break and continue to control the flow within loops. The break statement allows you to exit a loop prematurely based on a certain condition, while the continue statement allows you to skip the rest of the current iteration and move to the next one.

k = 1
while k < 6: # the code in the intedentation is executed until the condition is true !
    print('in loop') 
    print(k)
    k = k + 1
in loop
1
in loop
2
in loop
3
in loop
4
in loop
5

Sometimes it maybe wise to stop the computation in the framework of a while loop if a certain condition is met.

k = 1
while k < 1000:
    if k > 20:
        break
    print('The value of k is: {k_value}'.format(k_value=k)) # format is used to inject data into a string !
    k = k + 1
The value of k is: 1
The value of k is: 2
The value of k is: 3
The value of k is: 4
The value of k is: 5
The value of k is: 6
The value of k is: 7
The value of k is: 8
The value of k is: 9
The value of k is: 10
The value of k is: 11
The value of k is: 12
The value of k is: 13
The value of k is: 14
The value of k is: 15
The value of k is: 16
The value of k is: 17
The value of k is: 18
The value of k is: 19
The value of k is: 20

Without the break statement, the while loop would continue until the final value of 1000 !

count = 1
while count <= 10:
    if count == 5:
        count += 1
        continue  # Skip printing 5
    print(count)
    count += 1
1
2
3
4
6
7
8
9
10

We initialize a counter count to 1 and iterate while count is less than or equal to 10. When count is equal to 5, we use continue to skip the current iteration, so 5 is not printed. For all other values of count, we print the number and increment count by 1.


3.2.2 For Loop:

The “for” loop in Python is primarily used for iterating over sequences like lists, tuples, strings, and dictionaries, as well as other iterable objects. It iterates over each element in the sequence and executes a specified block of code for each element.

Here’s the basic structure of a “for” loop in Python:

for variable in iterable:
    # Code to be executed for each element
  • variable: This is a variable that represents the current element in the iterable.
  • iterable: The sequence or iterable object over which the loop iterates.

The for keyword is used to loop over a sequence (list, tuple, dictionary, set, or a string).

In Python, the for loop can be used with various types of iterables. An iterable is an object that can be iterated upon, meaning that you can loop over its elements one at a time.

my_list_of_data = [1, 'test', 3, 4]
for d in my_list_of_data:
    print(d)
1
test
3
4

In case a certain value in the sequence needs to be skipped from processing, the keyword continue can be used.

specimens = ['M50', 'XM20 ', 'YM50', 'M20', 'M56']
cleaned_specimens = []
for specimen in specimens:
    if specimen[0] == 'M':
        continue
    print(specimen)
    cleaned_specimens.append(specimen)
XM20 
YM50

The break keyword can be used to exit the loop !

cleaned_specimens
['XM20 ', 'YM50']
specimens = ['M50', 'M30', 'M20', 'M56']
for specimen in specimens:
    if specimen == 'M30':
        break
    print(specimen)
M50

Tuples inside lists can be accessed through the for loop.

list_of_tuples = [(1, 3), (10, 30), (100, 300)]
for small, big in list_of_tuples:
    print('This is the small number:' + str(small))
    print('This is the big number:' + str(big))
This is the small number:1
This is the big number:3
This is the small number:10
This is the big number:30
This is the small number:100
This is the big number:300

You can iterate over the characters of a string using a for loop.

my_text = 'data_file.xls'
file_extension = []
for character in my_text:  
    if character == '.':
        file_extension = []
    file_extension.append(character)
    
empty_delimiter = ''         
print(empty_delimiter.join(file_extension))
.xls

You can iterate over the keys, values or items of a dictionary using a for loop.

material = {'name': 'Iron', 
            'Symbol': 'Fe', 
            'Atomic radius (pm)': 126}
for key, value in material.items():
    
    print(key, ':', value)
name : Iron
Symbol : Fe
Atomic radius (pm) : 126
Tip

One can quickly loop over integer sequences using the range function. A range object represents a sequence of numbers. You can iterate over the numbers in a range object using a for loop.

The command range() is often used to generate a list when performing iterative computations ! range(n) generates an iterator starting at 0 and ending at n-1.

# prints a function
for x in range(6):
    y = x + x**2 + x**0.3
    print(x, y)
0 0.0
1 3.0
2 7.231144413344916
3 13.39038917031591
4 21.5157165665104
5 31.62065659669276

In case the increment does not need to be in steps of one but in steps of another fixed value, the range(start, stop, step) specification can be used !

my_data = []
for z in range(0, 101, 10): # range(start:stop:step) 100 items starting at 0 ends at 99 ! 
    y = z + z**2 + z**0.3
    my_tuple = (z, y)
    my_data.append(my_tuple)
my_data
[(0, 0.0),
 (10, 111.99526231496888),
 (20, 422.4564560522316),
 (30, 932.7741911146721),
 (40, 1643.0242521453322),
 (50, 2553.233635032887),
 (60, 3663.41542989238),
 (70, 4973.5770862508725),
 (80, 6483.723291133272),
 (90, 8193.857205282227),
 (100, 10103.981071705535)]
import pickle
with open('my_new_data.pkl', 'wb') as my_file:
    pickle.dump(my_data, my_file) 

When iterating over items, sometimes, the counter is also required. In that case use enumerate(iterator)

a_more_newer_list = [1, 4, 16, 32, 64, 128, 256, 512, 1024]
for counter, value in enumerate(a_more_newer_list):
    y = value**0.3
    print(counter, value, y)
0 1 1.0
1 4 1.515716566510398
2 16 2.2973967099940698
3 32 2.82842712474619
4 64 3.4822022531844965
5 128 4.2870938501451725
6 256 5.278031643091577
7 512 6.498019170849884
8 1024 7.999999999999999
my_new_list_1 = [1, 4, 16, 32, 64, 128, 256, 512, 1024]
my_new_list_2 = [1*3, 4*3, 16*3, 32*3, 64*3, 128*3, 256*3, 512*3, 1024*3]

In case we have two lists and we want to loop over them simultaneously then use zip:

for l1, l2 in zip(my_new_list_1, my_new_list_2):
    print(l1, l2)
1 3
4 12
16 48
32 96
64 192
128 384
256 768
512 1536
1024 3072

3.3 Generator expressions:

A generator expression is a concise way to create a generator object, which is an iterable that generates values on-the-fly. You can iterate over the values generated by a generator expression using a for loop.

squares = (x**2 for x in range(1, 6))
squares
<generator object <genexpr> at 0x174c53ac0>
type(squares)
generator
# define a generator for generating squares
for square in squares:
    print(square)
1
4
9
16
25

3.4 Iterators

In Python, an iterator is an object that represents a sequence of values. Iterators can be used to loop over a sequence of values one at a time, without loading the entire sequence into memory at once. This can be especially useful for working with large datasets or for situations where memory is limited.

Historically, the concept of iterators has been important in computer science because it helps programmers optimize their code and improve its performance. Prior to the development of iterators, programmers would often use loops and conditionals to work with sequences of values. However, this approach could be inefficient and lead to performance issues when working with large datasets.

In Python, iterators are implemented using the iter() function, which takes a sequence object and returns an iterator object. You can then use the next() function to retrieve the next value from the iterator, or use a for loop to iterate over all the values in the sequence.

my_list = [1, 2, 3]
my_iter = iter(my_list)
type(my_iter)
list_iterator
print(next(my_iter))
1
for item in my_iter:
    print(item); # 3
2
3

In this example, we create a list of values called my_list, and then use the iter() function to create an iterator object called my_iter. We then use the next() function to retrieve the first three values from the iterator, and use a for loop to iterate over the remaining values. Iterators are an important concept in Python and can be used in a variety of contexts, including working with files, databases, and other data sources. By understanding iterators and how they work, programmers can write more efficient and effective Python code.

Note

The for loop actually creates an iterator object and executes the next() method for each loop.

3.5 Itertools

The itertools module in Python provides a set of tools for working with iterable objects like lists, tuples, and iterators. These tools are designed to be fast and memory-efficient, and can be used to perform complex tasks with minimal code.

Here is a brief description on some of the most commonly used tools in the itertools module:

count(start=0, step=1) returns an iterator that generates a sequence of numbers starting from start and incrementing by step at each iteration.

import itertools

# Generate a sequence of numbers starting from 0 and incrementing by 2
for i in itertools.count(0, 2):
    if i > 10:
        break
    print(i)
0
2
4
6
8
10

cycle(iterable) returns an iterator that cycles through the elements of iterable indefinitely.

my_list = [1, 2, 3, 4]
counter = 0
for i in itertools.cycle(my_list):
    counter += 1
    if counter > 10:
        break
    print(i)
1
2
3
4
1
2
3
4
1
2

repeat(elem, n=None) returns an iterator that generates the elem value n times, or indefinitely if n is not specified.

for i in itertools.repeat(5, 5):
    print(i)
5
5
5
5
5

chain(*iterables) takes multiple iterables as arguments and returns a single iterator that produces the elements of each iterable in sequence.

list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
for i in itertools.chain(list1, list2, list3):
    print(i)
1
2
3
4
5
6
7
8
9

product(*iterables, repeat=1) returns an iterator that produces the Cartesian product of the input iterables, with repeat specifying the number of repetitions of each input iterable.

list1 = ['a', 'b']
list2 = [1, 2, 3]
for pair in itertools.product(list1, list2):
    print(pair)
('a', 1)
('a', 2)
('a', 3)
('b', 1)
('b', 2)
('b', 3)

3.6 Error Handling in Python

Error handling in Python is a technique used to gracefully handle unexpected or exceptional situations that may arise during the execution of a program. Python provides a way to catch and manage errors or exceptions using try and except blocks. Here’s a brief introduction to error handling in Python with an example:

In Python, exceptions are raised when an error occurs during program execution. These exceptions can be built-in errors (e.g., ValueError, IndexError) or custom exceptions defined by the programmer.

The basic syntax for error handling in Python is as follows:

try:
    # Code that may raise an exception
except ExceptionType1:
    # Code to handle ExceptionType1
except ExceptionType2:
    # Code to handle ExceptionType2
else:
    # Code to execute if no exceptions are raised
finally:
    # Code to execute regardless of whether an exception occurred or not
  • try: This block contains the code where you anticipate an exception might occur.
  • except: If an exception of the specified type occurs within the try block, the corresponding except block will be executed. You can have multiple except blocks to handle different types of exceptions.
  • else (optional): This block is executed if no exceptions occur in the try block.
  • finally (optional): This block is always executed, whether an exception occurred or not. It is typically used for cleanup operations, such as closing files or releasing resources.

Here’s an example that demonstrates error handling in Python:

try:
    num1 = 25
    num2 = 0
    result = num1 / num2
except ZeroDivisionError:
    print("Error: Division by zero is not allowed.")
except ValueError:
    print("Error: Please enter valid numeric values.")
else:
    print(f"Result of division: {result}")
finally:
    print("Program execution completed.")
Error: Division by zero is not allowed.
Program execution completed.

In this example:

  • We attempt to get two integer inputs from the user and perform division in the try block.
  • If the user enters non-numeric values, a ValueError exception is caught in the first except block.
  • If the user enters a denominator of zero, a ZeroDivisionError exception is caught in the second except block.
  • If no exceptions occur, the else block calculates and prints the result.
  • The finally block is always executed, providing a message that the program execution is completed.

Error handling allows your program to handle exceptional conditions without crashing, making your code more robust and user-friendly. It’s an essential part of writing reliable Python programs.

3.7 Example from data: Hours of Sunshine

Sunshine hours play a crucial role in assessing the potential for renewable energy investment, particularly in solar power. Essentially, sunshine hours refer to the duration of time during which sunlight reaches the Earth’s surface. This metric is pivotal for determining the viability and effectiveness of solar energy generation in a particular region.

Regions with higher sunshine hours generally have greater solar energy potential. By analyzing historical data on sunshine hours, investors and policymakers can identify areas where solar energy projects are likely to yield optimal results. This information enables strategic decision-making regarding the allocation of resources for renewable energy infrastructure.

Moreover, sunshine hours serve as a key parameter for evaluating the economic feasibility of solar energy projects. Higher sunshine hours typically translate to increased energy production, which can lead to greater returns on investment over the project’s lifespan. Additionally, areas with abundant sunshine hours may benefit from reduced reliance on fossil fuels, thereby contributing to environmental sustainability and mitigating climate change.

The csv file with the following data-structure is available in the data folder. This is a short excert from the file.

Country City Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Year
Finland Helsinki 38.0 70.0 138.0 194.0 284.0 297.0 291.0 238.0 150.0 93.0 36.0 29.0 1858
France Lyon 74.0 101.0 170.0 191.0 221.0 254.0 283.0 253.0 195.0 130.0 76.0 54.0 2002
France Marseille 150.0 156.0 215.0 245.0 293.0 326.0 366.0 327.0 254.0 205.0 156.0 143.0 2836
France Nice 156.7 166.1 218.0 229.2 270.9 309.8 349.3 223.2 249.8 191.1 151.5 145.2 2760.5
France Paris 63.0 79.0 129.0 166.0 194.0 202.0 212.0 212.0 168.0 118.0 68.0 51.0 1662
Georgia Tbilisi 99.0 102.0 142.0 171.0 213.0 249.0 256.0 248.0 206.0 164.0 103.0 93.0 2046
Germany Berlin 47.0 74.0 121.0 159.0 220.0 222.0 217.0 211.0 156.0 112.0 51.0 37.0 1626
Germany Frankfurt 50.0 80.0 121.0 178.0 211.0 219.0 233.0 219.0 156.0 103.0 51.0 41.0 1662
Greece Athens 130.0 134.0 183.0 231.0 291.0 336.0 363.0 341.0 276.0 208.0 153.0 127.0 2773
Hungary Budapest 62.0 93.0 137.0 177.0 234.0 250.0 271.0 255.0 187.0 141.0 69.0 52.0 1988
Iceland Reykjavik 20.0 60.0 109.0 164.0 201.0 174.0 168.0 155.0 120.0 93.0 41.0 22.0 1326
Ireland Dublin 59.0 75.0 109.0 160.0 195.0 179.0 164.0 157.0 129.0 103.0 71.0 53.0 1453
Italy Cagliari 150.0 163.0 209.0 218.0 270.0 311.0 342.0 321.0 243.0 209.0 150.0 127.0 2726
Italy Milan 59.0 96.0 152.0 177.0 211.0 243.0 285.0 251.0 186.0 130.0 66.0 59.0 1915
Italy Naples 115.0 128.0 158.0 189.0 245.0 279.0 313.0 295.0 234.0 189.0 126.0 105.0 2375
Italy Rome 121.0 133.0 167.0 201.0 264.0 285.0 332.0 298.0 237.0 195.0 129.0 112.0 2473

3.7.1 Identify the top 10 cities with the highest sunlight hours per year

import csv

# Path to your CSV file
csv_file_path = 'data/Sunshine hours for cities in the world.csv'

import csv

with open(csv_file_path, mode='r') as file:
    csv_reader = csv.reader(file)

    # Read data into a list of lists
    data = []

    # Skip the header if your CSV has one
    # headers = data.pop(0)

    # Ensure the last column is numeric (int or float)
    for row in csv_reader:
        try:
            row[-1] = float(row[-1])  # Convert to float
            data.append(row)
        except ValueError:
            pass  # Handle the error or skip rows with non-numeric last column

    
    # Sorting in descending order
    sorted_data = sorted(data, key=lambda x: x[-1], reverse=True)
    city_data = []
    # Print the 10 highest sun
    counter = 1
    for row in sorted_data:
        
        city_data.append((row[0] + '-' + row[1], row[-1]))
        counter += 1
        if counter > 10:
            break
city_data        
[('United States-Yuma', 4015.3),
 ('Egypt-Marsa Alam', 3958.0),
 ('Egypt-Dakhla Oasis', 3943.4),
 ('Chile-Calama', 3926.2),
 ('United States-Phoenix', 3871.6),
 ('Namibia-Keetmanshoop', 3870.0),
 ('United States-Las Vegas', 3825.3),
 ('United States-Tucson', 3806.0),
 ('United States-El Paso', 3762.5),
 ('Sudan-Khartoum', 3737.1)]

3.7.2 Get the average sunlight a country recieves per year and plot this data

import csv

# Initialize an empty dictionary for the data
country_city_sunlight = {}

# Open the CSV file
with open(csv_file_path, 'r') as file:
    csv_reader = csv.reader(file)
    
    # Skip the header row
    next(csv_reader)
    
    # Iterate over each row in the CSV
    for row in csv_reader:
        country, city, *sunlight_hours = row
        sunlight_hours = tuple(map(float, sunlight_hours))  # Convert sunlight hours to float and make a tuple
        
        # Check if the country is already in the dictionary
        if country not in country_city_sunlight:
            country_city_sunlight[country] = {}
        
        # Add the city and its sunlight hours to the country's dictionary
        country_city_sunlight[country][city] = sunlight_hours[-1]

country_sunlight = {}

for country, city_data in country_city_sunlight.items():
    country_sunlight[country] = 0
    for city, sunlight in city_data.items():
        country_sunlight[country] += sunlight
    country_sunlight[country] = country_sunlight[country]/len(city_data.items()) 
        
        
country_sunlight = sorted(country_sunlight.items(), key=lambda x: x[1], reverse=True)
Show the code
import matplotlib.pyplot as plt

# Unpacking the tuples into two lists
labels, values = zip(*country_sunlight)

# Normalize values for colormap
norm = plt.Normalize(vmin=min(values), vmax=max(values))
cmap = plt.get_cmap('Spectral_r') # Use 'Spectral_r' to reverse the colormap
colors = [cmap(norm(value)) for value in values]

# Creating the plot
plt.figure(figsize=(6, 18))
bars = plt.barh(labels, values, color=colors)


# Adding some labels and title for clarity
plt.xlabel('Average Sunlight per Year [Hours]')
plt.ylabel('Country')

plt.tight_layout()  # Adjust layout to make room for the horizontal bar labels
plt.show()
Sunshine hours per country

Sunshine hours per country

3.8 Exercises

3.8.1 Theory

  1. Write a brief explanation of the difference between a “for” loop and a “while” loop in Python.
  2. Explain the purpose of the “break” and “continue” statements in Python.
  3. Describe the concept of “nested” control flow in Python, and provide an example.
  4. What is the difference between an “if” statement and an “if-else” statement in Python? Provide an example of when you would use each.
  5. Explain the concept of “short-circuiting” in Python and provide an example.

3.8.2 Coding

Write a Python code that:

  1. takes a list of numbers as input and returns the sum of all the even numbers in the list.
  2. reads in a list of integers from the user and prints the maximum value in the list, as well as the index at which it occurs.
  3. prompts the user to enter a number and then uses a “for” loop to print out the first 10 multiples of that number.
  4. reads in a list of integers and then removes all duplicates from the list. The program should print the modified list.
  5. Generate a list of prime numbers upto 1000 ! Hint: Use append() function to add items to the list !
  6. Generate a list of the Fibonacci numbers ! Hint: \(f_n = f_{n-1} + f_{n-2}\)

3.9 Further Reading

  1. https://docs.python.org/3/tutorial/controlflow.html