Assignment #04

Unit 4

Until the next session, study the provided lecture material and solve the following exercises.

#04-02: Hello, conditional execution (version 2)

Go back to the program you wrote for the previous exercise #03-04. Make your program code as organised and easy-to-read as possible. To do so, pack the language classification that selects the welcome message into a function welcome_message(name, language) that you use in the main part of your script.

#04-03: Robustify years2days()

Today we coded a function years2days().

  1. So far, the error message (“traceback”) that we get when providing the function with a different data type other than int or float is very cryptic and a user might not learn from the message what they did wrong or why the function fails. Modify the function, so that it prints a meaningful message when the user tries to execute years2days("ten").
  2. So far, n_leapyears is simply added to the conversion result. The function would happily execute the statement years2days(1, n_leapyears=2), which is obviously meaningless. Implement a check that prints a message and does not execute the conversion if n_leapyears is more than years.
#04-04: Your own equivalent of numpy.array_equal()

Earlier we used the function numpy.array_equal to test whether two arrays had the same shape and same values. Code your own function that does the very same! Test whether your own function and the numpy function yield the same results.

Exercise #04-05: Define a function that filters lists

Below, I pasted the skeleton of a function I want you to code. Most importantly, I provide you with the documentation of the function. We read function documentation in the past (using help()), and we learned how to write the docstrings for our own functions, too. Here, you see a more comprehensive example of a function documentation. Carefully read the documentation and try to understand what the function is supposed to do. The examples help illustrate the information provided by the docstring and the parameter definitions.

  • None: We have not yet talked about the None keyword, but if you consult the Internet you will quickly see that it is easy to understand.
  • So far, we have used the print() function to craft (“error”) messages to the user. In the function below, I demonstrate you how a “proper” error can be raised.
def filter_list(values, min_value=None, max_value=None):
    """Filters a list of numbers to keep only the ones between one or two thresholds.

    You need to set at least one of min_value or max_value!

    Parameters
    ----------
    values : list
        a list of numbers (floats or ints)
    min_value : float or int, optional
        the minimum threshold to filter the data (inclusive)
    max_value : float or int, optional
        the maximum threshold to filter the data (inclusive)

    Examples
    --------
    >>> a = [1, 3, 4, 2.2]
    >>> filter_list(a, min_value=2)
    [3, 4, 2.2]
    >>> a  # a is unchanged
    [1, 3, 4, 2.2]
    >>> filter_list(a, max_value=3)
    [1, 3, 2.2]
    >>> filter_list(a, min_value=2, max_value=3)
    [3, 2.2]
    >>> filter_list([1, 2, 3, 4])
    Traceback (most recent call last):
     ...
    ValueError: Need to set at least one of min_value or max_value!
    """

    if min_value is None and max_value is None:
        raise ValueError('Need to set at least one of min_value or max_value!')

    output = []
    
    # <your own code here>
    
    return output
  1. You will have to iterate through the provided list of values …
  2. … and use a well-crafted conditional expression to decide which values …
  3. … need to be ‘added’ to the output list (find the right function yourself in our classroom material!)

Notebook

For the remaining two exercises, use the opportunity of solving the exercises in a notebook to familiarize yourself with the workflow of creating and deleting cells, writing markdown, and running code in a notebook. Download the file 04E_tasks.ipynb to find a notebook that I prepared for you.

The following exercises are taken from Fabien Maussion’s lecture material to the course Introduction to programming

#04-06: Read weather station data

Below, you will find a code block that reads the contents of the file Stationsliste_20230101.csv line-by-line. (Note, you have to put both the .csv file and this notebook in the same directory, preferably into 04_unit, for the code to work.)

# Open the csv file:
with open('Stationsliste_20230101.csv', encoding='latin-1') as file_handle:
    # Initialize a variable that counts the number of lines
    count = 0
    # Iterate through all lines in the csv file
    for line in file_handle:
        count += 1
        line = line.rstrip()

        # <your code will go here>
        
        # once you are starting to add your code, you can comment out 
        # the following three lines that produce the current output:
        print(line)
        if count > 5:
            break

Your task is to extend this code to ignore the first line, and then populate three lists with data: names, years, elevations. The lists contain the data converted to int and floats for the lists years and elevations. Here is the expected output for the first 5 lines and the list years (note that you will have to extract the years from the complete date):

>>> years
[2021, 2004, 2007, 2007, 1989]

The lists should contain 278 elements each.

Tip: The string methods .split() and .replace(), as well as string slicing, help you achieve that goal.

#04-07: Extracting useful information from the Austrian weather station csv file

Now, use the lists you just computed and the function filter_list you created earlier in exercise #04-05, as well as the python function max() and the list method index() to answer the following questions:

  • How many stations are located above 2000m?
  • What is the highest station elevation, and what is the station’s name?

Solutions

def welcome_message(name, language):
    """
    Generate a welcome message based on the chosen language.
    """
    if language == "EN":
        return f"How's it, {name}!"
    elif language == "DE":
        return f"Hallo, {name}!"
    elif language == "XX":
        return f"Greetings, {name}!"
    else:
        return "Unsupported language. Please choose EN, DE, or XX."


# Prompt the user for their name
name = input("Enter your name: ")

# Prompt the user for their preferred language
language = input("Enter your language (EN, DE, XX): ")

# Call the welcome_message function to generate the welcome message
message = welcome_message(name, language)

# Print the welcome message
print(message)
# Note that this solution implements a TypeError (instead of a simple print message), 
# which we encountered later in this unit

def years2days(years, n_leapyears=0):
    """
    Convert Years to Days

    This function takes a number of years as input and calculates 
    the equivalent number of days.
    """
    # Ensure meaningful parameter types
    try: 
        years = float(years)
        n_leapyears = int(n_leapyears)
    except:
        raise TypeError("years needs to be numeric, and n_leapyears needs to be an integer!")

    # Ensure meaningful leap year logic
    if n_leapyears > years:
        print("`n_leapyears` cannot meaningfully be greater than `years`! Returning without result.")
        return
    else:
        # finally, compute the conversion
        days = (years * 365) + n_leapyears
        return days
def custom_array_equal(arr1, arr2):
    """
    Check whether two numpy arrays are equal

    This function aims to implement a custom version of numpy.array_equal() by checking whether 
    two arrays have the same shape and contain the same values.
    """
    
    bool = (arr1.shape == arr2.shape) and (np.all(arr1 == arr2))
    return bool
def filter_list(values, min_value=None, max_value=None):
    """Filters a list of numbers to keep only the ones between one or two thresholds.

    You need to set at least one of min_value or max_value!

    Parameters
    ----------
    values : list
        a list of numbers (floats or ints)
    min_value : float or int, optional
        the minimum threshold to filter the data (inclusive)
    max_value : float or int, optional
        the maximum threshold to filter the data (inclusive)

    Examples
    --------
    >>> a = [1, 3, 4, 2.2]
    >>> filter_list(a, min_value=2)
    [3, 4, 2.2]
    >>> a  # a is unchanged
    [1, 3, 4, 2.2]
    >>> filter_list(a, max_value=3)
    [1, 3, 2.2]
    >>> filter_list(a, min_value=2, max_value=3)
    [3, 2.2]
    >>> filter_list([1, 2, 3, 4])
    Traceback (most recent call last):
     ...
    ValueError: Need to set at least one of min_value or max_value!
    >>> filter_list([1, 2, 3, "string"], min_value=2)
    Traceback (most recent call last):
     ...
    TypeError: All list elements need to be of type int or float!
    >>> filter_list([1, 2, 3, 4], min_value="eins")
    Traceback (most recent call last):
     ...
    TypeError: min_value needs to be of type int or float!
    
    """

    # ensure at least one filter threshold has been set
    if min_value is None and max_value is None:
        raise ValueError('Need to set at least one of min_value or max_value!')

    # enforce correct data types of min/max_value
    if min_value is not None and not isinstance(min_value, (int, float)):
        raise TypeError("min_value needs to be of type int or float!")
    if max_value is not None and not isinstance(max_value, (int, float)):
        raise TypeError("max_value needs to be of type int or float!")

    output = []

    # loop through all elements of the list
    for element in values:
        # enforce correct data types of list elements
        try:
            element = float(element)
        except:
            raise TypeError("All list elements need to be of type int or float!")
        
        # check if the element passes the minimum and maximum conditions (if specified)
        passes_min = (min_value is None) or (element >= min_value)
        passes_max = (max_value is None) or (element <= max_value)

        # append numbers that satisfy both conditions to the output list
        if passes_min and passes_max:
            output.append(element)

    return output
# Open the csv file:
with open('Stationsliste_20230101.csv', encoding='latin-1') as fhand:
    
    # Initialize a variable that counts the number of lines
    count = 0
    # Initialize output lists
    names = []
    years = []
    elevations = []
    
    # Iterate through all lines in the csv file
    for line in fhand:
        count += 1
        line = line.rstrip()

        # Ignore first line
        if count == 1:
            continue

        # separate line into individual data columns
        columns = line.split(";")
        
        # append name
        name = columns[1]
        if name[-1] == " ":
            name = name[:-1]
        names.append(name)
        
        # append year
        year = int(columns[6][0:4])
        years.append(year)

        # append elevation
        elev = float(columns[5])
        elevations.append(elev)
        
        # once you are starting to add your code, you can comment out 
        # the following three lines that produce the current output:
        # print(line)
        # if count > 5:
        #     break
high_elevs = filter_list(elevations, 2000.01)
print(f"There are {len(high_elevs)} stations located above 2000m.")

highest_index = elevations.index(max(elevations))
print(f"The station with the highest elevation of {elevations[highest_index]:.0f}m is called {names[highest_index]}.")

Alternatively, you can download the notebook I solved for you.