From 432fcfd396340b7ef2a0ce4bdbd0298eb0fd1e8e Mon Sep 17 00:00:00 2001 From: Pascal Su Date: Tue, 26 Nov 2024 18:22:26 +0100 Subject: [PATCH] Update on notebook basic_datatypes (#256) * Fixed some issues, added some exercises and rewrote some of the text. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Commiting "is None" for flake8 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added Split and len to methods for strings. * Added docstrings to exercises and changed 1 exercise * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixing some minor mistakes in the basic datatypes notebook * Update tutorial/tests/test_basic_datatypes.py Fixing an error I missed causing all the exercises to break. Co-authored-by: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> * Fixes for Review of Simone * Fixing popitem() description * Added error formating and complex to float example * pre-commit adjusts * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * making typehints more consistent * fix on fistandlastelement * List can contain strings * fix list comment * Import in cells and rounding down * Update basic_datatypes.ipynb Co-authored-by: Aliaksandr Yakutovich * spread lines in unpacking section and fix my_list3 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update basic_datatypes.ipynb * Update basic_datatypes.ipynb Co-authored-by: Edoardo Baldi * Update basic_datatypes.ipynb Co-authored-by: Edoardo Baldi * Update basic_datatypes.ipynb Co-authored-by: Edoardo Baldi * Adhere to the 'only one line per sentence' rule --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> Co-authored-by: Aliaksandr Yakutovich Co-authored-by: edoardob90 Co-authored-by: Edoardo Baldi --- .pre-commit-config.yaml | 2 +- basic_datatypes.ipynb | 476 +++++++++++++++++++++---- tutorial/tests/test_basic_datatypes.py | 87 +++-- 3 files changed, 479 insertions(+), 86 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c62419bc..d2416d08 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,8 +18,8 @@ repos: types_or: [python, pyi] args: [--fix] name: ruff (fix) - # Ruff formatter - id: ruff-format + # Ruff formatter types_or: [python, pyi] name: ruff (format) - repo: https://github.com/google/yamlfmt diff --git a/basic_datatypes.ipynb b/basic_datatypes.ipynb index b42ae27d..d921b5bc 100644 --- a/basic_datatypes.ipynb +++ b/basic_datatypes.ipynb @@ -37,6 +37,7 @@ "* [String](#String)\n", " + [Exercises on strings](#Exercises-on-strings)\n", "* [Mutable and immutable objects](#Mutable-and-immutable-objects)\n", + " + [Exercises on mutability](#Exercises-on-mutability)\n", "* [Unpacking](#Unpacking)\n", " + [The `*` operator](#The-*-operator)\n", " + [The `**` operator](#The-**-operator)\n", @@ -57,20 +58,6 @@ "* Python For Everybody: [Variables, Expressions, and Statements](https://www.py4e.com/lessons/memory)" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# Some relevant modules\n", - "import sys\n", - "import math\n", - "from tutorial.quiz import basic_datatypes as bdt" - ] - }, { "cell_type": "markdown", "metadata": { @@ -171,7 +158,7 @@ "metadata": {}, "source": [ "
\n", - "Remember:: Python is a dynamically typed language. The type of a variable is determined at runtime.\n", + "Remember: Python is a dynamically typed language. The type of a variable is determined at runtime.\n", "
" ] }, @@ -261,8 +248,8 @@ "metadata": {}, "source": [ "
\n", - "Remember:: The division operation `/` always returns a `float`.\n", - "If you want the division to return `int`, you have to use the `//` operator.\n", + "Remember: The division operation / always returns a float.\n", + "If you want the division to return int, you have to use the // operator.\n", "
" ] }, @@ -287,7 +274,7 @@ "outputs": [], "source": [ "x = 5.0\n", - "y = 2.0\n", + "y = -2.0\n", "print(f\"Addition: {x + y}\")\n", "print(f\"Subtraction: {x - y}\")\n", "print(f\"Multiplication: {x * y}\")\n", @@ -302,8 +289,8 @@ "metadata": {}, "source": [ "
\n", - "Note:: The floor division operation `//` returns a `float` in case of floats.\n", - "However, the returned float is rounded down to the nearest integer, which is 2.0 in the example above.\n", + "Note: The floor division operator // performs division and returns the largest integer less than or equal to the result.\n", + "When operating on floats, it returns a float result, like -3.0 in the example above. When operating on integers, it returns an integer result.\n", "
" ] }, @@ -321,8 +308,8 @@ "In the cell below you should write the code that solves the following exercises:\n", "\n", " 1. Write a Python program to that gets three float numbers and retuns $(a + b) * c$.\n", - " 2. Write a Python program that computes the area of a circle with radius $r$.\n", - " 2. Write a Python program that returns a solution of quadratic equation $ax^2 + bx + c = 0$. We consider only the case when solutions are real numbers." + " 2. Write a Python program that computes the area of a circle with radius $r$. Use `math.pi` from the math library as the value of pi.\n", + " 2. Write a Python program that returns the solutions of the quadratic equation $ax^2 + bx + c = 0$. We consider only the case when there are two distinct solutions and both are real numbers." ] }, { @@ -346,6 +333,16 @@ "source": [ "%%ipytest\n", "def solution_addition_multiplication(a: float, b:float, c:float) -> float:\n", + " \"\"\"Adds a and b, then multiplies the result by c\n", + "\n", + " Args:\n", + " a : first number\n", + " b : second number\n", + " c : third number\n", + " \n", + " Returns:\n", + " - The result of the calculation \n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -360,7 +357,16 @@ "outputs": [], "source": [ "%%ipytest\n", + "import math\n", "def solution_circle_area(r: float) -> float:\n", + " \"\"\"Calculates the area of a circle\n", + "\n", + " Args:\n", + " r : radius of the circle\n", + "\n", + " Returns:\n", + " - The area of the circle\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -376,7 +382,20 @@ "source": [ "%%ipytest\n", "\n", - "def solution_quadratic_equation(a: float, b: float, c: float) -> float:\n", + "def solution_quadratic_equation(a: float, b: float, c: float) -> tuple[float, float]:\n", + " \"\"\"Calculates the solutions of a quadratic equation\n", + "\n", + " We assume there are two real solutions to the equation and we expect the return of both.\n", + "\n", + " Args:\n", + " a : coefficient a\n", + " b : coefficient b\n", + " c : coefficient c\n", + "\n", + " Returns:\n", + " - The first solution\n", + " - The second solution\n", + " \"\"\" \n", " # Your code starts here\n", " solution1 = 0.0\n", " solution2 = 0.0\n", @@ -409,6 +428,7 @@ "Keep in mind that it is not always possible to perform type promotion.\n", "For instance, Python is not able to sum float and string - it will instead raise `TypeError`.\n", "Something like follows:\n", + "\n", "```python\n", "---------------------------------------------------------------------------\n", "TypeError Traceback (most recent call last)\n", @@ -541,6 +561,20 @@ "print(f\"string -> int: Result is {res}. Type of the result {type(res)}\")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "complex_number = complex(5.0)\n", + "\n", + "# complex -> float should fail regardless of the complex number.\n", + "res = float(complex_number)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -556,6 +590,7 @@ }, "outputs": [], "source": [ + "from tutorial.quiz import basic_datatypes as bdt\n", "bdt.IntegerFloatDivistion()" ] }, @@ -612,12 +647,12 @@ "metadata": {}, "source": [ "
\n", - "Attention: This is not recommended to use equality operator `==` to compare floats.\n", + "Warning: It's not recommended to use equality operator == to compare floats.\n", "
\n", "\n", "We suggest using the `math.isclose()` function instead.\n", "The reason is that the equality operator `==` is too precise for floats.\n", - "Two very close numbers can be different in computer memory.\n", + "This is mainly because a computer will store numbers in the binary format but we write them using the decimal system. During the conversion we get slight deviations.\n", "For example, the following two numbers are different:\n", "```python\n", "a = 0.1 + 0.2\n", @@ -633,7 +668,10 @@ "math.isclose(a, b)\n", "```\n", "\n", - "Try it for yourself in the cell below." + "Try it for yourself in the cell below.\n", + "\n", + "**Note:** The `math.isclose` will have a default of $1\\times 10^{-9}$ deviation tolerance.\n", + "If you would like a more detailed explanation of this phenomenon have look at the [Documentation](https://docs.python.org/3/tutorial/floatingpoint.html)" ] }, { @@ -688,11 +726,22 @@ "outputs": [], "source": [ "%%ipytest\n", + "import math\n", "\n", "def solution_a_plus_b_equals_c(a: float, b: float, c: float) -> float:\n", + " \"\"\"Checks if a + b equals c\n", + "\n", + " Args:\n", + " a : first number\n", + " b : second number\n", + " c : third number\n", + "\n", + " Returns:\n", + " - True if a + b equals c, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", - " # Your code ends here\n" + " # Your code ends here" ] }, { @@ -705,7 +754,15 @@ "source": [ "%%ipytest\n", "\n", - "def solution_number_is_even(number: float) -> bool:\n", + "def solution_number_is_even(number: int) -> bool:\n", + " \"\"\"Checks if a number is even\n", + "\n", + " Args:\n", + " number : the number to check\n", + "\n", + " Returns:\n", + " - True if the number is even, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return number\n", " # Your code ends here\n" @@ -720,6 +777,14 @@ "%%ipytest\n", "\n", "def solution_number_is_greater_than_zero(number: float) -> bool:\n", + " \"\"\"Checks if a number is greater than zero\n", + "\n", + " Args:\n", + " number : the number to check\n", + "\n", + " Returns:\n", + " - True if the number is greater than zero, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -910,7 +975,15 @@ "source": [ "%%ipytest\n", "\n", - "def solution_number_is_positive_and_even(number: float) -> bool:\n", + "def solution_number_is_positive_and_even(number: int) -> bool:\n", + " \"\"\"Checks if a number is positive and even\n", + "\n", + " Args:\n", + " number : the number to check\n", + "\n", + " Returns:\n", + " - True if the number is positive and even, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -926,7 +999,15 @@ "source": [ "%%ipytest\n", "\n", - "def solution_number_is_lower_than_0_or_greater_than_100(number: float) -> bool:\n", + "def solution_number_is_lower_than_0_or_greater_equal_to_100(number: int) -> bool:\n", + " \"\"\"Checks if a number is lower than 0 or greater or equal to 100\n", + " \n", + " Args:\n", + " number : the number to check\n", + "\n", + " Returns:\n", + " - True if the number is lower than 0 or greater or equal to 100, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1155,7 +1236,7 @@ "metadata": {}, "source": [ "
\n", - "Warning: Always use the identity operators to check if a variable is `None` or not.\n", + "Warning: Always use the identity operators to check if a variable is None or not.\n", "
" ] }, @@ -1170,9 +1251,11 @@ "my_list1 = [1, 2, 3]\n", "my_list2 = [1, 2, 3]\n", "my_list3 = my_list1\n", + "my_list4 = my_list1.copy()\n", "\n", "print(f\"my_list1 is equal to my_list2: {my_list1 == my_list2}, but my_list1 is not my_list2: {my_list1 is not my_list2}\")\n", - "print(f\"my_list1 is equal to my_list3: {my_list1 == my_list3}, and my_list1 is my_list3: {my_list1 is my_list3}\")" + "print(f\"my_list1 is equal to my_list3: {my_list1 == my_list3}, and my_list1 is my_list3: {my_list1 is my_list3}\")\n", + "print(f\"Similar to my_list2, a.copy() of my_list1 is equal to my_list1: {my_list4 == my_list1}, but my_list1 is not my_list4: {my_list1 is not my_list4}\")" ] }, { @@ -1244,7 +1327,7 @@ "## Exercises on lists\n", "\n", "1. Write a program that takes a list of numbers and returns a new list with every second element removed.\n", - "2. Write a program that takes a list and returns the first and last element of the list.\n", + "2. Write a program that takes a list and returns the first and last element of the list as a tuple.\n", "3. Write a program that returns `True` if the first and last element of the list are the same, otherwise return `False`.\n", "4. Write a program that takes two lists and returns `True` if they are equal.\n", "5. Write a program that takes two lists and returns `True` if they equal but not the same object.\n", @@ -1273,6 +1356,14 @@ "%%ipytest\n", "\n", "def solution_remove_every_second_element_from_list(my_list: list[float]) -> list[float]:\n", + " \"\"\"Removes every second element from the list\n", + "\n", + " Args:\n", + " my_list : list of floats\n", + "\n", + " Returns:\n", + " - A list without every second element\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1288,7 +1379,16 @@ "source": [ "%%ipytest\n", "\n", - "def solution_return_first_and_last_element_from_list(my_list: list[float]) -> list[float]:\n", + "def solution_return_first_and_last_element_from_list(my_list: list) -> tuple:\n", + " \"\"\"Returns the first and last element of a list\n", + "\n", + " Args:\n", + " my_list : list\n", + "\n", + " Returns:\n", + " - first element of the list\n", + " - last element of the list\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1304,7 +1404,15 @@ "source": [ "%%ipytest\n", "\n", - "def solution_first_and_last_element_are_equal(my_list: list[float]) -> bool:\n", + "def solution_first_and_last_element_are_equal(my_list: list) -> bool:\n", + " \"\"\"Checks if the first and last element of a list are equal\n", + "\n", + " Args:\n", + " my_list : list\n", + "\n", + " Returns:\n", + " - True if the first and last element are equal, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1321,6 +1429,15 @@ "%%ipytest\n", "\n", "def solution_lists_are_equal(list1: list, list2: list) -> bool:\n", + " \"\"\"Checks if two lists are equal\n", + "\n", + " Args:\n", + " list1 : first list\n", + " list2 : second list\n", + "\n", + " Returns:\n", + " - True if the lists are equal, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1337,6 +1454,15 @@ "%%ipytest\n", "\n", "def solution_lists_are_not_same_but_equal(list1: list, list2: list) -> bool:\n", + " \"\"\"Checks if two lists are equal but not the same\n", + "\n", + " Args:\n", + " list1 : first list\n", + " list2 : second list\n", + "\n", + " Returns:\n", + " - True if the lists are equal but not the same, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1353,6 +1479,15 @@ "%%ipytest\n", "\n", "def solution_greater_or_equal(list1: list, list2: list) -> bool:\n", + " \"\"\"Checks if the first list is greater or equal to the second list\n", + "\n", + " Args:\n", + " list1 : first list\n", + " list2 : second list\n", + "\n", + " Returns:\n", + " - True if the first list is greater or equal to the second list, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1489,6 +1624,8 @@ }, "outputs": [], "source": [ + "import sys\n", + "\n", "my_list = [1, 2.5, True, None]\n", "my_tuple = (1, 2.5, True, None)\n", "\n", @@ -1833,6 +1970,15 @@ "%%ipytest\n", "\n", "def solution_sets_union(set1: set, set2: set) -> set:\n", + " \"\"\"Returns the union of two sets\n", + "\n", + " Args:\n", + " set1 : first set\n", + " set2 : second set\n", + "\n", + " Returns:\n", + " - The union of the two sets\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1847,6 +1993,15 @@ "%%ipytest\n", "\n", "def solution_sets_intersection(set1: set, set2: set) -> set:\n", + " \"\"\"Returns the intersection of two sets\n", + "\n", + " Args:\n", + " set1 : first set\n", + " set2 : second set\n", + "\n", + " Returns:\n", + " - The intersection of the two sets\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1861,6 +2016,15 @@ "%%ipytest\n", "\n", "def solution_sets_difference(set1: set, set2: set) -> set:\n", + " \"\"\"Returns the difference of two sets\n", + "\n", + " Args:\n", + " set1 : first set\n", + " set2 : second set\n", + "\n", + " Returns:\n", + " - The difference of the two sets\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1875,6 +2039,15 @@ "%%ipytest\n", "\n", "def solution_sets_symmetric_difference(set1: set, set2: set) -> set:\n", + " \"\"\"Returns the symmetric difference of two sets\n", + "\n", + " Args:\n", + " set1 : first set\n", + " set2 : second set\n", + "\n", + " Returns:\n", + " - The symmetric difference of the two sets\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here " @@ -1888,7 +2061,16 @@ "source": [ "%%ipytest\n", "\n", - "def solution_sets_subset(set1: set, set2: set) -> set:\n", + "def solution_sets_subset(set1: set, set2: set) -> bool:\n", + " \"\"\"Checks if the first set is a subset of the second set\n", + "\n", + " Args:\n", + " set1 : first set\n", + " set2 : second set\n", + "\n", + " Returns:\n", + " - True if the first set is a subset of the second set, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1902,7 +2084,16 @@ "source": [ "%%ipytest\n", "\n", - "def solution_sets_superset(set1: set, set2: set) -> set:\n", + "def solution_sets_superset(set1: set, set2: set) -> bool:\n", + " \"\"\"Checks if the first set is a superset of the second set\n", + "\n", + " Args:\n", + " set1 : first set\n", + " set2 : second set\n", + "\n", + " Returns:\n", + " - True if the first set is a superset of the second set, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1918,7 +2109,16 @@ "source": [ "%%ipytest\n", "\n", - "def solution_sets_disjoint(set1: set, set2: set) -> set:\n", + "def solution_sets_disjoint(set1: set, set2: set) -> bool:\n", + " \"\"\"Checks if two sets are disjoint\n", + "\n", + " Args:\n", + " set1 : first set\n", + " set2 : second set\n", + "\n", + " Returns:\n", + " - True if the sets are disjoint, False otherwise\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -1981,9 +2181,9 @@ "
\n", "\n", " Warning: The keys of the dictionary must be hashable.\n", - "This means that the `hash` function must work for such them: `hash(key)`.\n", + "This means that the `hash` function must work on them: `hash(key)`.\n", "The most common hashable types are numbers, strings, and tuples.\n", - "The lists are not hashable, so they cannot be used as keys.\n", + "Lists are not hashable, so they cannot be used as keys.\n", "\n", "
" ] @@ -1996,7 +2196,7 @@ "\n", " * `dict.get(key)` - returns the value of the key if it is in the dictionary, `None` otherwise\n", " * `dict.pop(key[, default])` - removes the key-value pair from the dictionary and returns the value. If the `default` value is provided it will be returned if the key is not in the dictionary.\n", - " * `dict.popitem()` - removes and returns an arbitrary key-value pair from the dictionary\n", + " * `dict.popitem()` - removes and returns the last inserted key-value pair from the dictionary\n", " * `dict.clear()` - removes all the key-value pairs from the dictionary\n", " * `dict.copy()` - returns a copy of the dictionary\n", " * `dict.update(other_dict)` - updates the dictionary with the key-value pairs from the other dictionary\n", @@ -2033,7 +2233,7 @@ "print(f\"Getting a value with the 'pop' method, if the key does not exist: {my_dict.pop('d', None)}\")\n", "print(f\"my_dict is unchanged, since it did not contain 'd': {my_dict}\")\n", "\n", - "print(f\"Remove and return an arbitrary set element: {my_dict.popitem()}\")\n", + "print(f\"Remove and return the last inserted element from a dictionary: {my_dict.popitem()}\")\n", "print(f\"my_dict is now one element shorter: {my_dict}\")\n", "\n", "my_dict.clear()\n", @@ -2088,6 +2288,15 @@ "from typing import TypeVar, Hashable, Any\n", "\n", "def solution_dict_return_value(my_dict: dict[Hashable, Any], key: Hashable) -> Any:\n", + " \"\"\"Returns the value associated with a key in a dictionary, if the key exists. Otherwise returns None\n", + "\n", + " Args:\n", + " my_dict : dictionary\n", + " key : key to look for\n", + "\n", + " Returns:\n", + " - The value of the key or None\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -2105,6 +2314,15 @@ "from typing import TypeVar, Hashable, Any\n", "\n", "def solution_dict_return_delete_value(my_dict: dict[Hashable, Any], key: Hashable) -> Any:\n", + " \"\"\"Deletes and returns the value associated with a key in a dictionary, if the key exists. Otherwise returns None\n", + "\n", + " Args:\n", + " my_dict : dictionary\n", + " key : key to look for and delete\n", + "\n", + " Returns:\n", + " - The value of the key or None\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -2123,6 +2341,15 @@ "from typing import TypeVar, Hashable, Any\n", "\n", "def solution_update_one_dict_with_another(dict1: dict[Hashable, Any], dict2: dict[Hashable, Any]) -> dict[Hashable, Any]:\n", + " \"\"\"Updates the first dictionary with the second dictionary\n", + " \n", + " Args:\n", + " dict1 : first dictionary\n", + " dict2 : second dictionary\n", + "\n", + " Returns:\n", + " - The updated dictionary\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -2210,24 +2437,26 @@ "source": [ "Here are the methods commonly used when dealing with strings:\n", "\n", - "* `string.capitalize()` - returns a copy of the string with the first character capitalized and the rest lowercased\n", - "* `string.lower()` - returns a copy of the string with all the characters lowercased\n", - "* `string.upper()` - returns a copy of the string with all the characters uppercased\n", - "* `string.count(substring, start, end)` - returns the number of non-overlapping occurrences of substring in the string\n", - "* `string.startswith(prefix, start, end)` - returns `True` if the string starts with the prefix, `False` otherwise\n", - "* `string.endswith(suffix, start, end)` - returns `True` if the string ends with the suffix, `False` otherwise\n", - "* `string.find(substring, start, end)` - returns the lowest index in the string where substring is found, or `-1` if not found\n", - "* `string.replace(old, new, number)` - returns a copy of the string with `number` occurrences of old replaced by new. If `number` is omitted, all occurrences are replaced\n", + "* `string.capitalize()` - returns a copy of the string with the first character capitalized and the rest lowercased.\n", + "* `string.lower()` - returns a copy of the string with all the characters lowercased.\n", + "* `string.upper()` - returns a copy of the string with all the characters uppercased.\n", + "* `string.count(substring, start, end)` - returns the number of non-overlapping occurrences of substring in the string.\n", + "* `len(string)` - returns the length, so number of characters in the string.\n", + "* `string.startswith(prefix, start, end)` - returns `True` if the string starts with the prefix, `False` otherwise.\n", + "* `string.endswith(suffix, start, end)` - returns `True` if the string ends with the suffix, `False` otherwise.\n", + "* `string.find(substring, start, end)` - returns the lowest index in the string where substring is found, or `-1` if not found.\n", + "* `string.replace(old, new, number)` - returns a copy of the string with `number` occurrences of old replaced by new. If `number` is omitted, all occurrences are replaced.\n", + "* `string.strip(characters)` - returns a copy of the string with any leading or trailing characters whitespaces removed. If a string `characters` is given, instead all leading or trailing characters which are in the string `characters` are removed.\n", "* `string.split(separator, maxsplit)` - returns a list of the words in the string, using a separator as the delimiter string. If `maxsplit` is given, at most `maxsplit` splits are done. If `separator` is not provided, the string will be split on any whitespace character.\n", - "* `string.join(iterable)` - returns a string consisting of the strings in the iterable joined by the string on which the method is called\n", - "* `string.splitlines(keepends)` - returns a list of the lines in the string, breaking at line boundaries. If `keepends` is `True`, the line breaks are included in the resulting list\n", - "* `string.isalnum()` - returns `True` if the string consists of alphanumeric characters, `False` otherwise\n", - "* `string.isalpha()` - returns `True` if the string consists of alphabetic characters, `False` otherwise\n", - "* `string.isdigit()` - returns `True` if the string consists of digits, `False` otherwise\n", - "* `string.islower()` - returns `True` if the string consists of lowercase characters, `False` otherwise\n", - "* `string.isupper()` - returns `True` if the string consists of uppercase characters, `False` otherwise\n", - "* `string.isspace()` - returns `True` if the string consists of whitespace characters, `False` otherwise\n", - "* `string.istitle()` - returns `True` if the string consists of words that start with an uppercase character followed by lowercase characters, `False` otherwise\n", + "* `string.join(iterable)` - returns a string consisting of the strings in the iterable joined by the string on which the method is called.\n", + "* `string.splitlines(keepends)` - returns a list of the lines in the string, breaking at line boundaries. If `keepends` is `True`, the line breaks are included in the resulting list.\n", + "* `string.isalnum()` - returns `True` if the string consists of alphanumeric characters, `False` otherwise.\n", + "* `string.isalpha()` - returns `True` if the string consists of alphabetic characters, `False` otherwise.\n", + "* `string.isdigit()` - returns `True` if the string consists of digits, `False` otherwise.\n", + "* `string.islower()` - returns `True` if the string consists of lowercase characters, `False` otherwise.\n", + "* `string.isupper()` - returns `True` if the string consists of uppercase characters, `False` otherwise.\n", + "* `string.isspace()` - returns `True` if the string consists of whitespace characters, `False` otherwise.\n", + "* `string.istitle()` - returns `True` if the string consists of words that start with an uppercase character followed by lowercase characters, `False` otherwise.\n", "\n", "... and many more." ] @@ -2267,6 +2496,14 @@ "%%ipytest\n", "\n", "def solution_string_capitalize(my_string: str) -> str:\n", + " \"\"\"Capitalizes a string\n", + "\n", + " Args:\n", + " my_string : string to capitalize\n", + "\n", + " Returns:\n", + " - The capitalized string\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -2283,6 +2520,14 @@ "%%ipytest\n", "\n", "def solution_string_lower_case(my_string: str) -> str:\n", + " \"\"\"Converts a string to lower case\n", + "\n", + " Args:\n", + " my_string : string to convert\n", + "\n", + " Returns:\n", + " - The lower case string\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -2298,7 +2543,15 @@ "source": [ "%%ipytest\n", "\n", - "def solution_string_word_split(my_string: str) -> str:\n", + "def solution_string_word_split(my_string: str) -> list[str]:\n", + " \"\"\"Splits a string into words\n", + "\n", + " Args:\n", + " my_string : string to split\n", + "\n", + " Returns:\n", + " - The list of words\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -2314,7 +2567,15 @@ "source": [ "%%ipytest\n", "\n", - "def solution_string_join_commas(my_list: str) -> str:\n", + "def solution_string_join_commas(my_list: list[str]) -> str:\n", + " \"\"\"Joins a list of strings into a single string with commas\n", + "\n", + " Args:\n", + " my_list : list of strings\n", + "\n", + " Returns:\n", + " - The string with commas\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -2331,6 +2592,14 @@ "%%ipytest\n", "\n", "def solution_string_split_lines(my_string: str) -> list[str]:\n", + " \"\"\"Splits a string into lines\n", + "\n", + " Args:\n", + " my_string : string to split\n", + "\n", + " Returns:\n", + " - The list of lines\n", + " \"\"\"\n", " # Your code starts here\n", " return\n", " # Your code ends here" @@ -2417,6 +2686,76 @@ "print(f\"At the same time my_list2 is also updated to {my_list2} (id={id(my_list2)}), so both my_list1 and my_list2 are still referring to same object ({my_list1 is my_list2}).\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercises on mutability\n", + "\n", + "1. Write a program that receives a set of integers as input and returns two sets, one with all even numbers and one with all odd numbers.\n", + "2. Write a program that receives a tuple of integers as input and returns a tuple where every value is increased by one. Hint: Try to think in lists first, then use type casting with the tuple() operator to create a tuple!\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%reload_ext tutorial.tests.testsuite" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%ipytest\n", + "\n", + "def solution_sets_of_even_and_odd(my_set: set[int]) -> tuple[set[int]]:\n", + " \"\"\"Creates two sets, one with even and one with odd numbers out of the input set\n", + "\n", + " Args:\n", + " my_set : set of integers\n", + "\n", + " Returns:\n", + " - Set with even numbers\n", + " - Set with odd numbers\n", + " \"\"\"\n", + " # Your code starts here\n", + " return\n", + " # Your code ends here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "%%ipytest\n", + "\n", + "def solution_tuple_increased_by_one(my_tuple: tuple[int]) -> tuple[int]:\n", + " \"\"\"Increases all elements of a tuple by one\n", + "\n", + " Args:\n", + " my_tuple : tuple of integers\n", + "\n", + " Returns:\n", + " - A tuple with all elements increased by one\n", + " \"\"\"\n", + " # Your code starts here\n", + " return\n", + " # Your code ends here" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -2424,6 +2763,7 @@ "# Unpacking\n", "\n", "Unpacking is a way to extract the elements of a sequence into variables.\n", + "\n", "This is useful when we want to assign the elements of a sequence to variables." ] }, @@ -2450,7 +2790,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Any iterable can be considered a \"packed\" container. **Unpacking** works by assigning the variables' names on the left of `=` to the values on the right **according to their relative position** (the 1st of the left = 1st on the right, 2nd on the left = 2nd on the right, and so on).\n", + "Any iterable can be considered a \"packed\" container.\n", + "**Unpacking** works by assigning the variables' names on the left of `=` to the values on the right **according to their relative position** (the 1st of the left = 1st on the right, 2nd on the left = 2nd on the right, and so on).\n", "\n", "There are two operators in Python that enable you to have more control on how to unpack iterables: these are the `*` and `**` operators." ] @@ -2491,6 +2832,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "When unpacking you will recieve a list instead of whatever iterable you had to begin with.\n", + "This may be relevant when further processing data.\n", + "\n", "We could do the same with slicing, it's just less readable:" ] }, @@ -2502,6 +2846,7 @@ }, "outputs": [], "source": [ + "s = 'Python'\n", "a, b, c, d = s[0], s[1], s[2:-1], s[-1] # In this case, 'c' IS NOT a list but a sub-string because we used slicing.\n", "\n", "print(a, b, c, d)" @@ -2543,7 +2888,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The `*` operator works with **any iterable**. However be aware that for sets the order is not guaranteed.\n", + "The `*` operator works with **any iterable**.\n", + "However, be aware that for sets the order is **not** guaranteed.\n", "\n", "Here are several examples:" ] @@ -2566,7 +2912,7 @@ "# Unpacking set\n", "s = {10, -99, 3, 'd'}\n", "\n", - "a, *b, c = s # there's no guarantee that a = 10, c = 'd', b = [-99, 3]\n", + "a, *b, c = s # there's no guarantee that a = 10, b = [-99, 3], c = 'd'\n", "print(f\"a = {a} b = {b} c = {c}\")" ] }, @@ -2740,7 +3086,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.10.15" }, "vscode": { "interpreter": { diff --git a/tutorial/tests/test_basic_datatypes.py b/tutorial/tests/test_basic_datatypes.py index 9b2f13cd..390ed842 100644 --- a/tutorial/tests/test_basic_datatypes.py +++ b/tutorial/tests/test_basic_datatypes.py @@ -146,17 +146,17 @@ def test_number_is_positive_and_even( assert function_to_test(number) == reference_number_is_positive_and_even(number) -def reference_number_is_lower_than_0_or_greater_than_100(number: int) -> bool: +def reference_number_is_lower_than_0_or_greater_equal_to_100(number: int) -> bool: return number < 0 or number >= 100 @pytest.mark.parametrize("number", [1, 2, 3, 4, -1, -2, -3, -4, 0, 100, 101, 102, 103]) -def test_number_is_lower_than_0_or_greater_than_100( +def test_number_is_lower_than_0_or_greater_equal_to_100( number: int, function_to_test: Callable[[int], bool] ): assert function_to_test( number - ) == reference_number_is_lower_than_0_or_greater_than_100(number) + ) == reference_number_is_lower_than_0_or_greater_equal_to_100(number) LISTS = [ @@ -209,9 +209,11 @@ def reference_return_first_and_last_element_from_list(my_list: list) -> tuple: @pytest.mark.parametrize("my_list", LISTS) def test_return_first_and_last_element_from_list(my_list, function_to_test): - assert function_to_test( - my_list - ) == reference_return_first_and_last_element_from_list(my_list) + # If result is a tuple, transform to list + result = function_to_test(my_list) + if isinstance(result, list): + result = tuple(result) + assert result == reference_return_first_and_last_element_from_list(my_list) def reference_first_and_last_element_are_equal(my_list: list) -> bool: @@ -267,7 +269,7 @@ def test_greater_or_equal(list1, list2, function_to_test): def reference_sets_union(set1: set, set2: set) -> set: - return set1.union(set2) + return set1 | set2 @pytest.mark.parametrize("set1, set2", SET1_2) @@ -277,7 +279,7 @@ def test_sets_union(set1, set2, function_to_test): def reference_sets_intersection(set1: set, set2: set) -> set: - return set1.intersection(set2) + return set1 & set2 @pytest.mark.parametrize("set1, set2", SET1_2) @@ -287,7 +289,7 @@ def test_sets_intersection(set1, set2, function_to_test): def reference_sets_difference(set1: set, set2: set) -> set: - return set1.difference(set2) + return set1 - set2 @pytest.mark.parametrize("set1, set2", SET1_2) @@ -297,7 +299,7 @@ def test_sets_difference(set1, set2, function_to_test): def reference_sets_symmetric_difference(set1: set, set2: set) -> set: - return set1.symmetric_difference(set2) + return set1 ^ set2 @pytest.mark.parametrize("set1, set2", SET1_2) @@ -309,7 +311,7 @@ def test_sets_symmetric_difference(set1, set2, function_to_test): def reference_sets_subset(set1: set, set2: set) -> bool: - return set1.issubset(set2) + return set1 <= set2 @pytest.mark.parametrize("set1, set2", SET1_2) @@ -319,7 +321,7 @@ def test_sets_subset(set1, set2, function_to_test): def reference_sets_superset(set1: set, set2: set) -> bool: - return set1.issuperset(set2) + return set1 >= set2 @pytest.mark.parametrize("set1, set2", SET1_2) @@ -329,7 +331,7 @@ def test_sets_superset(set1, set2, function_to_test): def reference_sets_disjoint(set1: set, set2: set) -> bool: - return set1.isdisjoint(set2) + return (set1 & set2) == set() @pytest.mark.parametrize("set1, set2", SET1_2) @@ -393,7 +395,8 @@ def test_dict_return_delete_value(my_dict, key, function_to_test): def reference_update_one_dict_with_another( dict1: dict[Hashable, Any], dict2: dict[Hashable, Any] ) -> dict[Hashable, Any]: - return dict1.update(dict2) + dict1.update(dict2) + return dict1 @pytest.mark.parametrize( @@ -402,10 +405,14 @@ def reference_update_one_dict_with_another( def test_update_one_dict_with_another(my_dict1, my_dict2, function_to_test): my_dict1_original1 = my_dict1.copy() my_dict1_original2 = my_dict1.copy() - function_to_test(my_dict1_original1, my_dict2) - reference_update_one_dict_with_another(my_dict1_original2, my_dict2) + new_dict = function_to_test(my_dict1_original1, my_dict2) + if new_dict is None: + new_dict = my_dict1_original1 + ref_dict = reference_update_one_dict_with_another(my_dict1_original2, my_dict2) + if ref_dict is None: + ref_dict = my_dict1_original2 - assert my_dict1_original1 == my_dict1_original2 + assert new_dict == ref_dict STRINGS = [ @@ -454,8 +461,6 @@ def reference_string_join_commas(my_string: Iterable[str]) -> str: @pytest.mark.parametrize("my_string", STRINGS) def test_string_join_commas(my_string, function_to_test): my_string_splitted = my_string.split() - print(my_string_splitted) - print(reference_string_join_commas(my_string_splitted)) assert function_to_test(my_string_splitted) == reference_string_join_commas( my_string_splitted ) @@ -467,5 +472,47 @@ def reference_string_split_lines(my_string: str) -> list[str]: def test_string_split_lines(function_to_test): my_string = "\n".join(STRINGS) - assert function_to_test(my_string) == STRINGS assert function_to_test(my_string) == reference_string_split_lines(my_string) + + +INT_SETS = [set(), {1, 2, 3}, {3, -1, 0, 4, 42, 1002}] + + +def reference_sets_of_even_and_odd(my_set: set[int]) -> tuple[set[int]]: + even_set = my_set.copy() + odd_set = my_set.copy() + for n in my_set: + if n % 2 == 0: + odd_set.remove(n) + else: + even_set.remove(n) + return even_set, odd_set + + +@pytest.mark.parametrize("my_set", INT_SETS) +def test_sets_of_even_and_odd(my_set, function_to_test): + sol_set = my_set.copy() + ref_set = my_set.copy() + sol_even_set, sol_odd_set = function_to_test(sol_set) + ref_even_set, ref_odd_set = reference_sets_of_even_and_odd(ref_set) + + assert sol_even_set == ref_even_set + assert sol_odd_set == ref_odd_set + + +INT_TUPLES = [(), (1, 2, 3), (3, -1, 0, 4, 42, 1002)] + + +def reference_tuple_increased_by_one(my_tuple: tuple[int]) -> tuple[int]: + increase = [] + for n in my_tuple: + increase.append(n + 1) + return tuple(increase) + + +@pytest.mark.parametrize("my_tuple", INT_TUPLES) +def test_tuple_increased_by_one(my_tuple, function_to_test): + new_tuple = function_to_test(my_tuple) + ref_tuple = reference_tuple_increased_by_one(my_tuple) + + assert new_tuple == ref_tuple