From c1b66a813d4e5cc87d9db5cfb95617af03cfb866 Mon Sep 17 00:00:00 2001
From: radoskov <radoslav.skoviera@cvut.cz>
Date: Tue, 25 Feb 2025 21:43:13 +0100
Subject: [PATCH] Added TOC to lecture 1

---
 .../lecture_01/l1_intro_strctures.qmd         | 61 ++++++++++++++-----
 1 file changed, 45 insertions(+), 16 deletions(-)

diff --git a/src/pge_lectures/lecture_01/l1_intro_strctures.qmd b/src/pge_lectures/lecture_01/l1_intro_strctures.qmd
index 53785ad..112ae88 100644
--- a/src/pge_lectures/lecture_01/l1_intro_strctures.qmd
+++ b/src/pge_lectures/lecture_01/l1_intro_strctures.qmd
@@ -1,10 +1,17 @@
 # Basic data structures, intro to asymptotic complexity
 ---
-title: "Lecture 1"
+title: Programming for Engineers
+subtitle: "Lecture 1 - basic data structures"
+author: Radoslav ΔΉ koviera
+
 format:
+  pdf:
+    code-fold: false
   html:
     code-fold: false
 jupyter: python3
+toc: true
+jupyter: python3
 ---
 
 ## Basic data structures (in Python)
@@ -192,7 +199,9 @@ print("[1, 2, 3.0] == [1, 2, 3]: ", [1, 2, 3.0] == [1, 2, 3])  # True
 print("[1, 2, 3] == [3, 2, 1]: ", [1, 2, 3] == [3, 2, 1])  # False
 
 # Be (ALWAYS) aware of precision/numerical issues
-print("[1, 2, 3.0000000000000000001] == [1, 2, 3]: ", [1, 2, 3.0000000000000000001] == [1, 2, 3])  # True, depends on the precision
+print(
+    "[1, 2, 3.000000000000000001] == [1, 2, 3]: ",
+     [1, 2, 3.000000000000000001] == [1, 2, 3])  # True, depends on the precision
 ```
 
 ##### Precision side-quest
@@ -202,7 +211,7 @@ print("3 == 2.99999999", 3 == 2.99999999)  # False
 print("1/3 == 0.33333333", 1/3 == 0.33333333)  # False
 print("3.00000000000000001 == 3: ", 3.00000000000000001 == 3)  # True
 print("2.99999999999999999 == 3: ", 2.99999999999999999 == 3)  # True
-print("1/3 == 0.333333333333333333333: ", 1/3 == 0.333333333333333333333)  # True
+print("1/3 == 0.3333333333333333333: ", 1/3 == 0.3333333333333333333)  # True
 ```
 End of side-quest.
 
@@ -621,7 +630,8 @@ print(f"My list after running my_function: {my_list}")
 my_tuple = (1, 2, 3)  # precious data we don't want to change
 try:
     print(my_function(my_tuple))
-except TypeError:  # this will catch the error raised whe the function tries to change the tuple
+except TypeError:  # this will catch the error raised
+    # when the function tries to change the tuple
     print("Aha! The function tried to change my tuple!")
 ```
 
@@ -739,7 +749,9 @@ Most common way to iterate over a dictionary is to use the `items()` method, whi
 ```{python}
 import string
 
-alphabet_dict = {k: v for k, v in zip(string.ascii_letters, range(26)) if v < 12 and v % 2 == 0}
+alphabet_dict = {
+    k: v for k, v in zip(string.ascii_letters, range(26))
+     if v < 12 and v % 2 == 0}
 for key, value in alphabet_dict.items():
     print(f"The letter {key} is at position {value + 1} in the alphabet.")
 ```
@@ -747,7 +759,9 @@ for key, value in alphabet_dict.items():
 There are also `keys()` and `values()` methods that return the keys and values respectively.
 ```{python}
 for key in alphabet_dict.keys():
-    print(f"The letter {key} is at position {alphabet_dict[key] + 1} in the alphabet.")
+    print(f"The letter {key} is at position "
+    f"{alphabet_dict[key] + 1} in the alphabet.")
+    # Yes, you can break strings in Python like that...
 ```
 
 ```{python}
@@ -815,10 +829,19 @@ b = [1, 2, 3]
 c = a
 d = a[:]
 e = a.copy()
-print(a is b)  # the elements have the same value but `a` is not the same object as `b`
-print(a is c)  # `a` and `c` reference the same object
-print(a is d)  # `d` got the values from `a` but it is still a different object (different memory location)
-print(a is e)  # copy makes a new object with the same values (essentially, similar process to defining `d`)
+# the elements have the same value but `a` is not the same object as `b`
+print(a is b)
+
+# `a` and `c` reference the same object
+print(a is c)
+
+# `d` got the values from `a` but it is still a different object
+# (different memory location)
+print(a is d)
+
+# copy makes a new object with the same values
+# (essentially, similar process to defining `d`)
+print(a is e)
 ```
 
 It is not?
@@ -834,7 +857,8 @@ Some 'objects' are the same but it makes no sense to compare them using `is`. Al
 # this will get you a syntax warning
 print(1 is 1.0)  # False, because float != int
 print(1.0 is 1.0)  # True
-print((1, 2, 3) is (1, 2, 3))  # Unexpectedly, True but also not advised; just use equality `==`
+print((1, 2, 3) is (1, 2, 3))  # Unexpectedly, True but also not advised;
+#just use equality `==`
 ```
 For number type check, see below.
 
@@ -886,10 +910,12 @@ print(multiply_two_numbers_unchecked("3", 2))  # 33 !?
 Code with run-time type checking and assertions:
 ```{python}
 def multiply_two_numbers(a, b):
-    assert isinstance(a, (int, float)), f"'a' must be a number! But it was: {type(a)}"
-    assert isinstance(b, (int, float)), f"'b' must be a number! But it was: {type(b)}"
+    assert isinstance(a, (int, float)), "'a' must be a number! "
+    f"But it was: {type(a)}"
+    assert isinstance(b, (int, float)), "'b' must be a number! " f"But it was: {type(b)}"
     # Alternatively, this will also work:
-    assert issubclass(type(my_integer), (int, float)), f"'a' must be a number! But it was: {type(a)}"
+    assert issubclass(type(my_integer), (int, float)), "'a' must be a number! "
+    f"But it was: {type(a)}"
     return a * b
 
 print(multiply_two_numbers(3, 2))
@@ -909,7 +935,10 @@ More info about basic types and comparison: https://docs.python.org/3/library/st
 ```{python}
 from typing import Union, Optional
 
-def nice_multiply_two_numbers(a: Union[int, float], b: Union[int, float], raise_error: bool = True) -> Optional[Union[int, float]]:
+def nice_multiply_two_numbers(
+    a: Union[int, float],
+    b: Union[int, float],
+    raise_error: bool = True) -> Optional[Union[int, float]]:
     if not isinstance(a, (int, float)):
         if raise_error:
             raise TypeError(f"'a' must be a number! But it was: {type(a)}")
@@ -928,7 +957,7 @@ try:
     print(nice_multiply_two_numbers(3.0, 2))
     print(nice_multiply_two_numbers("3", 2))  # this will cause an error
 except TypeError as e:
-    print(f"One of the inputs had an incorrect type. See the error: {e}")
+    print(f"One of the inputs had an incorrect type. See the error:\n{e}")
 
 print("\nUsing None return type:")
 result = nice_multiply_two_numbers("3", 2, raise_error=False)
-- 
GitLab