5  Self Study Session I

Important

This lecture consists of several self study sessions!

In these sections you are supposed to work on the given topics on your own and in your own time. We will try to provide helpful references for books and online material that can help you but you are not limited to these materials.

In order to get an idea on how much you understand of the material we provide exercises.

In this section we are mainly concerned with the basics of Python, nevertheless you are expected to use git and pdm to solve the problems at hand.

5.1 Topics

For each topic we list various materials and what is the basic idea behind it.

5.1.1 Variables, Data Types, Functions, Typing and Type Hints, Modules

Once you are familiar with how Python handles these topics you will be able to answer questions like:

  • What is a variable?
  • What is the scope of a variable?
  • What is the type of a variable?
  • How can I change the type of a variable?
  • Is Python typed?
  • Does talking about casting makes sense?
  • What is the difference between assignment and copy?
  • What happens to objects that are no longer referenced by any variable?
  • Is Python using call by value or call by reference?
  • What is the signature of a function?
  • What are optional arguments?
  • Can I figure out or define for function arguments some basic properties?
  • etc.

This topics are covered in the following resources (given in no particular order):

  • McKinney (2022) Online, Section 2 and 3
  • Matthes (2023) Section 2 to 8
  • Vasiliev (2022) Chapter 2
  • MCI Lecture notes of Julian Huber and Matthias Panny Online, Section 1 (German)
  • MCI Lecture notes of Julian Huber Online, Section 1.2; (Access code is provided in Sakai)
  • Overview article on type hinting dagster.io
  • Type hints at docs.python.org
  • Type hints cheat sheet form the docs of mypy Online

5.2 Exercises

Important

If, and only if, you are taking the class at MCI and are not reading these notes on your own, you are expected to hand in the exercises below as a git repository.

In order to get a bit of git and pdm training done we work for all exercises on GitHub.

  1. Create a new private project in GitHub -> you might want to use it for all later exercises as well.
  2. Use meaningful commit messages, see Conventional commits.
  3. Give the instructors (kandolfp) access to the project, see docs for help.
  4. Create a pdm project in your repository and commit the necessary files.
  5. Create an appropriate structure in your repository for the rest of the exercise (maybe have a look at the exercises to have a better idea first), not everything should be in the main folder.
  6. Try to structure your work on the exercises with git, i.e.
    • Don’t commit things that do not belong together in one single commit. Each exercise can be considered as a separate thing. Subparts of an exercise might be independent as well.
    • Use meaningful commit messages, see Conventional commits.
    • Make sure that you do not commit something that does not work - produces an error. If you have difficulties with an exercise you can also commit your best effort in this case.
  7. Add a README.md that explains what you are doing, how to run the exercises and anything else that is necessary (quick guide to pdm), maybe note your name somewhere (github nicks are not always easy to track down).
  8. Optional
    • Work with issues, you can reference the issue in the commit message, docs

Exercise 5.1 (Indent is important in Python) Explain the outputs of the following code snippets, (compare Matthes 2023, chap. 4):

band_members = ["Peter", "Bjorn", "John"]

for member in band_members:
print(member)
  Cell In[1], line 4
    print(member)
    ^
IndentationError: expected an indented block after 'for' statement on line 3
band_members = ["Peter", "Bjorn", "John"]

for member in band_members:
    print(f"{member} played great in this song.")
print(f"I can not wait to hear {member} play in the next song.")
Peter played great in this song.
Bjorn played great in this song.
John played great in this song.
I can not wait to hear John play in the next song.
band_members = ["Peter", "Bjorn", "John"]

for member in band_members:
    print(f"{member} played great in this song.")
    print(f"I can not wait to hear {member} play in the next song.")

    print(f"I can not wait to hear all of you at the next gig.")
Peter played great in this song.
I can not wait to hear Peter play in the next song.
I can not wait to hear all of you at the next gig.
Bjorn played great in this song.
I can not wait to hear Bjorn play in the next song.
I can not wait to hear all of you at the next gig.
John played great in this song.
I can not wait to hear John play in the next song.
I can not wait to hear all of you at the next gig.
band_members = ["Peter", "Bjorn", "John"]

for member in band_members:
    print(f"{member} played great in this song.")
    print(f"I can not wait to hear {member} play in the next song.")

print(f"I can not wait to hear all of you at the next gig.")
Peter played great in this song.
I can not wait to hear Peter play in the next song.
Bjorn played great in this song.
I can not wait to hear Bjorn play in the next song.
John played great in this song.
I can not wait to hear John play in the next song.
I can not wait to hear all of you at the next gig.

Exercise 5.2 (Lists, conditionals and loops) For two given lists list1 and list2 of integers with equal length define the following new lists:

  1. list_zip which combines the two lists to a single list like a zip. So start with the first element of list1, followed by the first element of list2, than the second elements until all elements of the lists are combined.
  2. list_odd which contains only the odd integers of list1, followed by the odd integers of list2.
  3. list_zip_reverse which combines the elements of the lists like for list_zip but starts at the last element of list1.

Example:

For list1 = [1, 2, 3, 4, 5] and list2 = [11, 12, 13, 14, 15], we get

  • list_zip = [1, 11, 2, 12, 3, 13, 4, 14, 5, 15]
  • list_odd = [1, 3, 5, 11, 13, 15]
  • list_zip_reverse = [5, 15, 4, 14, 3, 13, 2, 12, 1, 11]

Exercise 5.3 (Fizz Buzz) Write a program that prints the integers from 1 to 100 (inclusive). If, however, the number is a multiple of three then print Fizz instead, and if the number is a multiple of five then print Buzz.

If multiple conditions hold true then all replacements should be printed, for example 15 should print FizzBuzz.

Exercise 5.4 (Difference between assignment, copy and deep copy) Have a look at the module copy and the functions copy, deepcopy as well as the standard assignment operator =.

Run the following code and explain its output:

import copy
list1 = [[1, 2, 3], [4, 5, 6], ["a", "b", "c"], 2]
list2 = list1
list3 = copy.copy(list1)
list4 = copy.deepcopy(list1)

print("List # \tID\tEntries")
print("1\t", id(list1), "\t", list1)
print("2\t", id(list2), "\t", list2)
print("3\t", id(list3), "\t", list3)
print("4\t", id(list4), "\t", list4)

list1[3] = -2

print("List # \tID\tEntries")
print("1\t", id(list1), "\t", list1)
print("2\t", id(list2), "\t", list2)
print("3\t", id(list3), "\t", list3)
print("4\t", id(list4), "\t", list4)

list2[2][2] = 9

print("List # \tID\tEntries")
print("1\t", id(list1), "\t", list1)
print("2\t", id(list2), "\t", list2)
print("3\t", id(list3), "\t", list3)
print("4\t", id(list4), "\t", list4)

list1.append([0, 8, 15])

print("List # \tID\tEntries")
print("1\t", id(list1), "\t", list1)
print("2\t", id(list2), "\t", list2)
print("3\t", id(list3), "\t", list3)
print("4\t", id(list4), "\t", list4)
List #  ID  Entries
1    140253503281728     [[1, 2, 3], [4, 5, 6], ['a', 'b', 'c'], 2]
2    140253503281728     [[1, 2, 3], [4, 5, 6], ['a', 'b', 'c'], 2]
3    140253503280704     [[1, 2, 3], [4, 5, 6], ['a', 'b', 'c'], 2]
4    140253503281408     [[1, 2, 3], [4, 5, 6], ['a', 'b', 'c'], 2]
List #  ID  Entries
1    140253503281728     [[1, 2, 3], [4, 5, 6], ['a', 'b', 'c'], -2]
2    140253503281728     [[1, 2, 3], [4, 5, 6], ['a', 'b', 'c'], -2]
3    140253503280704     [[1, 2, 3], [4, 5, 6], ['a', 'b', 'c'], 2]
4    140253503281408     [[1, 2, 3], [4, 5, 6], ['a', 'b', 'c'], 2]
List #  ID  Entries
1    140253503281728     [[1, 2, 3], [4, 5, 6], ['a', 'b', 9], -2]
2    140253503281728     [[1, 2, 3], [4, 5, 6], ['a', 'b', 9], -2]
3    140253503280704     [[1, 2, 3], [4, 5, 6], ['a', 'b', 9], 2]
4    140253503281408     [[1, 2, 3], [4, 5, 6], ['a', 'b', 'c'], 2]
List #  ID  Entries
1    140253503281728     [[1, 2, 3], [4, 5, 6], ['a', 'b', 9], -2, [0, 8, 15]]
2    140253503281728     [[1, 2, 3], [4, 5, 6], ['a', 'b', 9], -2, [0, 8, 15]]
3    140253503280704     [[1, 2, 3], [4, 5, 6], ['a', 'b', 9], 2]
4    140253503281408     [[1, 2, 3], [4, 5, 6], ['a', 'b', 'c'], 2]

Hint: The function id returns the identity of an object. It is guaranteed to be unique and constant for every object as long as it exists in memory.

Exercise 5.5 (Monte Carlo simulations with random numbers, functions, conditionals and loops) A circle with radius \(r\) has an area of \[A_{circle} = \pi r^2\] and the square that encases it \[A_{square} = 4 r^2.\]

The ratio between the area of the circle and the area of the square is \[ \frac{A_{circle}}{A_{square}} = \frac{\pi r^2}{4 r^2} = \frac{\pi}{4} \] and therefore we can define \(\pi\) as \[ \pi = 4\frac{A_{circle}}{A_{square}}. \] The same is true if we just take the first quadrant, so \(\frac14\) of the square as well as the circle. This simplification will make the code more compact and faster.

The algorithm therefore becomes:

  1. For a given number \(N\) of uniformly scattered points in the quadrant, determine if these points are in the circle (distance less than 1) or not. We call the number of points in the circle \(M\).
  2. Estimate \(\pi\) by computing \[ \pi \approx 4 \frac{M}{N}. \tag{5.1}\]

To write this in Python follow these steps:

  1. Search for a module that allows to generate random values in python.
  2. Define a function def in_unit_circle(N) that computes and returns \(M\) from the function input \(N\), which is a single positive integer. Hint: You can interpret two numbers between \(0\) and \(1\) as cartesian coordinates of a point and the squared sum of them will tell you the distance of this point from the origin.
  3. Define a function def estimate_pi(N) that computes and returns the estimation of \(\pi\) with Equation 5.1.
  4. Search for a module that provides the exact value of \(\pi\) and write a function that returns the absolute difference between the above function and \(\pi\).
  5. Test your function with different values of \(N\). Hint: \(N\) needs to be quite large to have multiple digits of \(\pi\) correct.

Exercise 5.6 (Type hinting) For the functions extimate_pi and in_unit_circle in Exercise 5.5 provide type hints to suggest only integers are allowed.

How can you give a further hint that the integer is always positive?

Is the type enforced during runtime?

Exercise 5.7 (Functions can be arguments) Provide a version of the function estimate_pi(N) from Exercise 5.5 that has a second argument that specifies the function to call e.g. in_unit_circle.

Provide type hints for the new function.

Note: We will come back to this example later where this will be of use.

Exercise 5.8 (Recursion with Fibonacci) Create a function that computes the \(n\)th Fibonacci number with recursion, i.e. a function calling itself, here is the sequence:

\[ x_0 = 0,\quad x_1 = 1,\quad x_n = x_{n-1} + x_{n-2},\quad n\geq 2. \] The first couple of elements are: \(0\), \(1\), \(1\), \(2\), \(3\), \(5\), \(8\), \(13\), \(21\), \(34\), \(55\), \(89\), \(144\), \(233\), \(377\), \(610\), \(\ldots\)

Exercise 5.9 (Working with dictionaries) Compute the arabic number from a roman numeral, for positive integers from \(1\) to \(3999\). In order to do so use the dict as provided below, if you see fit you may change or add entries:

roman = {I:1, IV: 4, V: 5, IX: 9, X: 10, XL: 40, 
         L: 50, XC: 90, C: 100, CD: 400, D: 500,
         CM:900, M: 1000}

Examples

Roman Arabic
MMMDCCXXIV 3724
MXCIV 1094
VIII 8

Exercise 5.10 (Set, List and Dictionary Comprehensions)  

  1. Transform a list of tuples into a list of numbers using (nested) list comprehension, i.e. the list flat should be created and filled in one call from the content of list_of_sets.
list_of_sets = [(1, 2, 3), (4, 5, 6), (7, 8)]
flat = [1, 2, 3, 4, 5, 6, 7, 8]
  1. Is it still working if we include a set with a single entry?
list_of_sets = [(1, 2, 3), (4, 5, 6), (7, 8), (9)]
flat = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  1. What happens when we indicate that it is supposed to be a set clearly?
list_of_sets = [(1, 2, 3), (4, 5, 6), (7, 8), (9,)]
flat = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  1. What happens if we have lists instead of sets?
list_of_lists = [[1, 2, 3], [4, 5, 6], [7, 8], [9]]
flat = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  1. Split a sentence give as string into a list of words (ignore ,, . for now).
sentence = "I spy with my little eye a tricky" \
           " list and dict comprehension coming up"
words = ["I", "spy", "with", "my", "little", "eye",
         "a", "tricky", "list", "and", "dict",
         "comprehension", "coming", "up"]
  1. From this list of words, get a set (therefore unique) lengths of these words, i.e. spy -> 3. Hint: you might want to look into the function map.

  2. Combine everything to get from a sentence a dictionary with length of word as key and a set of words as value. Test it with a couple of different sentences.

  3. Can you reduce this into dictionary comprehension , i.e. create the dictionary from scratch in one go.

Exercise 5.11 (Creating a module) Create your own module. The module should have the following functions:

  • For a given year, check if it is a leap year.
  • For a given date, return the day of the week, use the algorithm outlined here. Hint: Dictionaries can help you for some of the mappings.
  • Return the week number of the year of a certain date, you can use the algorithm outlined here.
  • Provide doc strings for each of your methods and for the module itself.

Exercise 5.12 (Using your own module) Use the module created in Exercise 5.11 and build a program on it.

  • Include the local module via pdm.
  • Use the module to generate from a date the following output
    • leap year -> true/false
    • day of the week
    • week of the year
  • How can you deal with european and american styled day-month order?
  • Update your module by adding the above described function where the returned value should be a dictionary with keys: leapyear, weekday,week.
  • Can you use the new function in the module right away or do you need to reinstall it somehow?