From 4fd19b0288dd05b34b0afa1f892438f9fd5897ed Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Mon, 5 May 2025 13:53:47 +0200 Subject: [PATCH 1/9] update functional programming --- 11_functional_programming.ipynb | 471 ++++++++++-------- tutorial/quiz/functional_programming.py | 18 +- .../tests/test_11_functional_programming.py | 141 +++++- 3 files changed, 390 insertions(+), 240 deletions(-) diff --git a/11_functional_programming.ipynb b/11_functional_programming.ipynb index 48401994..ba974ff5 100644 --- a/11_functional_programming.ipynb +++ b/11_functional_programming.ipynb @@ -11,25 +11,34 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Table of contents\n", - "\n", - "- [Immutability](#Immutability)\n", - "- [Composition](#Composition)\n", - "- [Higher Order Functions / Functions as Values](#Higher-Order-Functions-/-Functions-as-Values)\n", - "- [Referential transparency](#Referential-transparency)\n", - "- [Type system](#Type-system)\n", - "- [Filtering](#Filtering)\n", - "- [Reducing](#Reducing)\n", - "- [List comprehensions](#List-comprehensions)\n", - "- [Combinining and splitting iterators](#Combinining-and-splitting-iterators)\n", - "- [Exercises](#Exercises)\n", - " - [Exercise 1: Pure or impure functions? 🌶️](#Exercise-1:-Pure-or-impure-functions?-🌶️)\n", - " - [Exercise 2: Keeping only multiples of n 🌶️](#Exercise-2:-Keeping-only-multiples-of-n-🌶️)\n", - " - [Exercise 3: Transposing a Matrix 🌶️🌶️](#Exercise-3:-Transposing-a-Matrix-🌶️🌶️)\n", - " - [Exercise 4: Flattening list of lists 🌶️🌶️](#Exercise-4:-Flattening-list-of-lists-🌶️🌶️)\n", - " - [Exercise 5: Counting initials 🌶️🌶️🌶️](#Exercise-5:-Counting-initials-🌶️🌶️🌶️)\n", - " - [Exercise 6: Counting initials frequency 🌶️🌶️🌶️](#Exercise-6:-Counting-initials-frequency-🌶️🌶️🌶️)\n", - " - [Exercise 7: Finding palindromes 🌶️🌶️🌶️](#Exercise-7:-Finding-palindromes-🌶️🌶️🌶️)\n" + "## Table of Contents\n", + " - [References](#References)\n", + " - [Introduction](#Introduction)\n", + " - [Why Functional Programming](#Why-Functional-Programming)\n", + " - [The basic principles of functional programming](#The-basic-principles-of-functional-programming)\n", + " - [Pure Functions (Purity)](#Pure-Functions-(Purity))\n", + " - [Example](#Example)\n", + " - [Quiz on pure functions](#Quiz-on-pure-functions)\n", + " - [Immutability](#Immutability)\n", + " - [Composition](#Composition)\n", + " - [Example](#Example)\n", + " - [Higher Order Functions / Functions as Values](#Higher-Order-Functions-/-Functions-as-Values)\n", + " - [Referential transparency](#Referential-transparency)\n", + " - [Type systems](#Type-systems)\n", + " - [Mapping / Iteration ](#Mapping-/-Iteration)\n", + " - [Mapping](#Mapping)\n", + " - [Filtering](#Filtering)\n", + " - [Reducing](#Reducing)\n", + " - [Examples: Iteration and Mapping](#Examples:-Iteration-and-Mapping)\n", + " - [List comprehensions](#List-comprehensions)\n", + " - [Example: Keeping only multiples of n](#Example:-Keeping-only-multiples-of-n)\n", + " - [Combinining and splitting iterators](#Combinining-and-splitting-iterators)\n", + " - [Exercises](#Exercises)\n", + " - [Exercise 1: Transposing a Matrix](#Exercise-1:-Transposing-a-Matrix)\n", + " - [Exercise 2: Flattening list of lists](#Exercise-2:-Flattening-list-of-lists)\n", + " - [Exercise 3: Counting initials](#Exercise-3:-Counting-initials)\n", + " - [Exercise 4: Counting initials frequency](#Exercise-4:-Counting-initials-frequency)\n", + " - [Exercise 5: Finding palindromes](#Exercise-5:-Finding-palindromes)" ] }, { @@ -40,8 +49,7 @@ "source": [ "# Important modules\n", "import functools\n", - "import itertools\n", - "import tutorial.functional_programming as fp" + "import itertools" ] }, { @@ -55,8 +63,7 @@ "\n", "---\n", "\n", - "\n", - "## References\n", + "# References\n", "Here are some additional references to help you in self-learning. Next to each link, we write if this is a video, text or another type of material\n", "\n", "- The [functools reference](https://docs.python.org/3/library/functools.html) from the Python standard library (text)\n", @@ -67,28 +74,26 @@ "- A [general introduction on functional programming](https://www.youtube.com/watch?v=8z_bUIl_uPo). Very worth watching as it uses Python for the examples (video)\n", "- [Principles of functional programming](https://dev.to/jamesrweb/principles-of-functional-programming-4b7c)\n", "\n", - "\n", - "\n", - "## Introduction\n", + "# Introduction\n", "Functional programming is an approach to programming where programs are built by composing and running functions that perform a series of transformations on data.\n", "This contrasts with the approach of *imperative programming*, where programs are written as a series of statements which modify the *state* of the computation environment.\n", "Typically, within functional programming, great focus is placed on *composition*, *immutability* and *purity*.\n", "We are going to define these terms in more detail later.\n", "\n", - "## Why Functional Programming\n", + "# Why Functional Programming\n", "Why do we choose functional programming? There are a series of advantages to this approach, namely:\n", "- **Debugging** and **testing** are easy: there are no surprises because every function only does one thing and does not affect any other piece of the program.\n", "- **Parallelization** is trivial: functions are just small *boxes* that take one input and produce an output and do not depend on other parts of the code in an implicit manner through global variables or other shared pieces of state. Thus, it's easy to make several functions run in parallel.\n", "\n", - "## The basic principles of functional programming\n", + "# The basic principles of functional programming\n", "All modern programming languages have *functions* (or methods, procedures, subroutines, subprograms); these are groups of program statements that perform a certain computation. Functions are defined once for the whole program and can be reused at will throughout the program whenever we need to perform the specific computation they are defined for. Using functions, we can split our code in smaller units that are only responsible for a specific *functionality*; this helps us structuring our code in a clean and understandable form. \n", "\n", "The main principles we use in functional programming are:\n", "\n", "- *Purity*\n", - "- *Functions as values* (*higher order functions*)\n", "- *Immutability of data*\n", "- *Composition*\n", + "- *Functions as values* (*higher order functions*)\n", "- *Referential transparency*\n", "- *Type systems*\n", "\n", @@ -104,16 +109,16 @@ "tags": [] }, "source": [ - "### Pure Functions (Purity)" + "## Pure Functions (Purity)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In functional programming, we try to strive for *purity*, that is we want to define and use functions that only depend on their input, always return the same output for the same inputs and do not have any *side effects*, that is they do not indirectly affect any other part of our program.\n", + "In functional programming, we try to strive for *purity*. We want to define and use functions that only depend on their input, always return the same output for the same inputs and do not have any *side effects*, that is they do not indirectly affect any other part of our program.\n", "You can think of these functions as mathematical functions. \n", - "Other examples of side effects are:\n", + "Some examples of side effects are:\n", "- printing to the program output\n", "- reading or writing files\n", "- generating and using random numbers\n", @@ -223,7 +228,50 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As you see from the output, the function modified the list `x`. Therefore, this function is not pure. This leads us to the next principle, **immutability**." + "As you see from the output, the function modified the list `x`. Therefore, this function is **not pure**. This leads us to the next principle, **immutability**." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example\n", + "\n", + "
\n", + "

Question

\n", + " Inside the solution function below, rewrite the previous example, so that it now creates a pure function instead.\n", + " The function should append a given array with a given element.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%reload_ext tutorial.tests.testsuite" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%ipytest\n", + "\n", + "def solution_pure_function(array: list, new_element: str):\n", + " \"\"\"A function that appends a given array with a given element.\n", + "\n", + " Args:\n", + " array: the initial list\n", + " new_element: the element to be added to the initial list\n", + " Returns:\n", + " - the appended list\n", + " \"\"\"\n", + "\n", + " return" ] }, { @@ -232,7 +280,7 @@ "tags": [] }, "source": [ - "#### Quiz of pure functions" + "### Quiz on pure functions" ] }, { @@ -253,7 +301,7 @@ "tags": [] }, "source": [ - "### Immutability" + "## Immutability" ] }, { @@ -265,10 +313,9 @@ "tags": [] }, "source": [ - "\n", "When writing programs in functional style, we usually avoid functions like `do_something`.\n", "Instead of modifying existing data (*mutation*), you write functions that transform your data and return new objects.\n", - "In the case of the function above, we would rewrite it as follows:" + "Another way to rewrite the function above could be:" ] }, { @@ -300,7 +347,7 @@ "source": [ "The output shows that `do_something_immutable` does not change `x`.\n", "It also does not reference to `x` outside of the scope but takes it as an argument.\n", - "Instead of modfying the original `x` list, it returns a new list.\n", + "Instead of modifying the original `x` list, it returns a new list.\n", "When we want to keep immutability, this is the style we work with.\n", "If we adopt this style, it is easier to reason about the flow of our program as there are no variable that are being modified *at distance* by other function calls." ] @@ -311,7 +358,7 @@ "tags": [] }, "source": [ - "### Composition" + "## Composition" ] }, { @@ -325,7 +372,7 @@ "source": [ "Another important aspect of functional programming is `composition`. This means that whenever we want to perform multiple operations, we avoid writing intermediate variables, especially so when these are in the global state of the program.\n", "\n", - "Instead, we design our functions in such a way that one functions output can be directly fed into the next function.\n", + "Instead, we design our functions in such a way that one function's output can be directly fed into the next function.\n", "\n", "Additionally – but this is not as useful in Python – we use the associative property of function application:\n", "\n", @@ -367,13 +414,55 @@ "- We do our work only once: every time we need the same action, we just call the same function" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example\n", + "\n", + "
\n", + "

Question

\n", + " Inside the solution function below, write the solution of equation x^2 + y^2, using lambda expressions.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%reload_ext tutorial.tests.testsuite" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%ipytest\n", + "\n", + "def solution_composition(x: int, y: int):\n", + " \"\"\"A function that solves the equation x^2 + y^2, using lambda functions.\n", + "\n", + " Args:\n", + " x: the first integer\n", + " y: the second integer\n", + " Returns:\n", + " - the result of the equation\n", + " \"\"\"\n", + "\n", + " return" + ] + }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ - "### Higher Order Functions / Functions as Values" + "## Higher Order Functions / Functions as Values" ] }, { @@ -418,7 +507,7 @@ } }, "source": [ - "This function takes a function `f` and its argument `args` as input and return the result of calling the function, while additionally printing a message on the standard output of the program. Let's try this out.\n", + "This function takes a function `f` and its argument `args` as input and returns the result of calling `f`, while additionally printing a message on the standard output of the program. Let's try this out.\n", "To do so, we define a new function `add_five`:" ] }, @@ -481,7 +570,7 @@ "tags": [] }, "source": [ - "### Referential transparency" + "## Referential transparency" ] }, { @@ -526,7 +615,7 @@ "source": [ "If `transparent_function` is referentially transparent, we can replace its value in the expression `y1 = transparent_function(1) + 5` and we obtain the same value. In other words, the value of `y` does not change if we execute it again, as shown in the code above: we have two expressions `y1` and `y2` that use the same code and because `x` is referentially transparent, their values are equal.\n", "\n", - "In this case, this is true because the value of `x()` is simply 5 and is invariant.\n", + "In this case, this is true because the value of `x` is simply 1 and is invariant.\n", "\n", "However, consider this other example:" ] @@ -573,7 +662,7 @@ "tags": [] }, "source": [ - "## Type system" + "## Type systems" ] }, { @@ -618,7 +707,7 @@ "def greet(name: str, age: int):\n", " print(f\"Hello {name}, you are {age} years old\")\n", " \n", - "greet(\"Simone\", 34)" + "greet(\"John Doe\", 34)" ] }, { @@ -634,7 +723,7 @@ "metadata": {}, "outputs": [], "source": [ - "greet(\"Simone\", \"age\")" + "greet(25, \"person\")" ] }, { @@ -645,8 +734,9 @@ "Python lacks this strictness. However, there are tools like [mypy](https://mypy-lang.org/) that can be used to check the type consistency of Python programs without running them.\n", "\n", "We can achieve a similar effect using execution-time type checks.\n", - "To facilitate our work, we first construct a `Person` class tha encapsulates the attributes of a person.\n", - "We will see more about classes in the object-oriented programming tutorial. For the moment, you can see a class as container of data and related functions." + "To facilitate our work, we first construct a `Person` class that encapsulates the attributes of a person.\n", + "We explain more about classes in the notebooks dedicated to object-oriented programming ([introductory](./05_object_oriented_programming.ipynb), [advanced](./13_object_oriented_programming_advanced.ipynb)).\n", + "If you are not familiar with this concept, for the moment, you can see a class as a container of data and related functions." ] }, { @@ -691,15 +781,10 @@ "metadata": {}, "outputs": [], "source": [ - "greet_better(Person(\"Simone\", 34))\n", + "greet_better(Person(\"John\", 34))\n", "\n", "try:\n", - " greet_better(Person(\"Simone\", \"d\"))\n", - "except ValueError as e:\n", - " print(f\"Ooops, it does not work: {e}\")\n", - " \n", - "try:\n", - " greet_better((\"Simone\", \"d\"))\n", + " greet_better(Person(\"Jane\", \"d\"))\n", "except ValueError as e:\n", " print(f\"Ooops, it does not work: {e}\")" ] @@ -717,7 +802,7 @@ "tags": [] }, "source": [ - "## Mapping / Iteration " + "# Mapping / Iteration " ] }, { @@ -737,14 +822,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Using map and filter\n", - "\n", "Python has the `map` built-in used to apply a given function to all the elements of any `iterable`.\n", "\n", "
\n", "

Note

\n", - " Reminder: an iterable is any object capable of returning its elements one at a time\n", - "
\n", + " Reminder: an iterable is any object capable of returning its elements one at a time\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mapping\n", "\n", "Let's see an example of a `map` using the `add_five` function:" ] @@ -838,10 +928,10 @@ "tags": [] }, "source": [ - "#### Filtering\n", + "## Filtering\n", "Another basic higher order function is `filter`.\n", "As the name says, this function is used to filter an `iterable` using a *predicate function*.\n", - "This is a function that takes a value and return `true` or `false`. With `true`, the current element is kept, with `false` it is discarded.\n", + "This is a function that takes a value and returns `true` or `false`. With `true`, the current element is kept, with `false` it is discarded.\n", "We now try to write a predicate function that only keeps even numbers.\n", "\n", "" @@ -894,7 +984,7 @@ "tags": [] }, "source": [ - "#### Reducing\n", + "## Reducing\n", "A third basic HoF is *reduction*.\n", "This is a function that takes a function `f(x, y)` of two arguments and an iterable `it` and applies the function to every element in the iterable *cumulatively* to produce one value.\n", "It works in the following way:\n", @@ -919,7 +1009,6 @@ "source": [ "import functools\n", "\n", - "\n", "def spy(x: int, y: int) -> int:\n", " val = x * y\n", " print(f\"x: {x}, y:{y}, result: {val}\")\n", @@ -941,6 +1030,72 @@ "The print statement in `spy` helps us see what goes on inside the function at each step in `reduce`." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Examples: Iteration and Mapping" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%reload_ext tutorial.tests.testsuite" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "

Question

\n", + " Modify the function solution_filter_even to filter only the even elements of the list input_data. For your solution, you cannot use explicit for loops.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%ipytest\n", + "\n", + "def solution_filter_even(input_data: list[int]) -> list[int]:\n", + " \"\"\"\n", + " Write your solution here\n", + " \"\"\"\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "

Question

\n", + " Modify the function solution_add_one to add the integer number 1 to each element of the list input_data. For your solution, you cannot use explicit for loops.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%ipytest\n", + "\n", + "def solution_add_one(input_data: list[int]) -> list[int]:\n", + " \"\"\"\n", + " Write your solution here\n", + " \"\"\"\n", + " pass" + ] + }, { "cell_type": "markdown", "metadata": { @@ -950,7 +1105,7 @@ "tags": [] }, "source": [ - "### List comprehensions\n", + "# List comprehensions\n", "Many of the operations in the previous section can be performed in a different (some would say more *pythonic*) way using [list comprehensions](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions).\n", "These look like mini for-loops inside square brackets and are very useful to write programs in a functional style while keeping code more readable than using higher order functions. \n", "\n", @@ -1114,29 +1269,30 @@ "[f\"The current pair is {i=}, {j=}\" for i in range(5) for j in range(5)]" ] }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ - "#### Exercises on iteration/mapping" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%reload_ext tutorial.tests.testsuite" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ - "1. Modify the function `solution_filter_even` to filter only the even elements of the list `input_data`. For your solution, you **cannot** use explicit `for` loops." + "### Example: Keeping only multiples of n\n", + "\n", + "
\n", + "

Question

\n", + " Given a list L of integers, write a function that only keeps the numbers that are multiples of a given constant k.\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + "

Hints

\n", + " The operator % (modulo) gives the remainder of division. a % k is 0 when a is a multiple of k. For more information, consult the chapter on basic datatypes. \n", + "
" ] }, { @@ -1145,20 +1301,7 @@ "metadata": {}, "outputs": [], "source": [ - "%%ipytest\n", - "\n", - "def solution_filter_even(input_data: list[int]) -> list[int]:\n", - " \"\"\"\n", - " Write your solution here\n", - " \"\"\"\n", - " pass" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "2. Modify the function `solution_add_one` to add the integer number `1` to each element of the list `input_data`. For your solution, you **cannot** use explicit `for` loops." + "%reload_ext tutorial.tests.testsuite" ] }, { @@ -1169,7 +1312,7 @@ "source": [ "%%ipytest\n", "\n", - "def solution_add_one(input_data: list[int]) -> list[int]:\n", + "def solution_multiples_of_n(l: \"list[int]\", k: int):\n", " \"\"\"\n", " Write your solution here\n", " \"\"\"\n", @@ -1184,7 +1327,7 @@ } }, "source": [ - "### Combinining and splitting iterators\n", + "# Combinining and splitting iterators\n", "Sometimes, we need to iterate over multiple lists in a special fashion, for example over two lists in parallel or we want to compute all the combinations of elements of two lists etc.\n", "We can solve many ot these problems with the use of list compherensions, but sometimes there are more elegant solutions by using the [`itertools`](https://docs.python.org/3/library/itertools.html) module, a part of Python's standard library.\n", "\n", @@ -1360,7 +1503,7 @@ "tags": [] }, "source": [ - "## Exercises" + "# Exercises" ] }, { @@ -1380,94 +1523,7 @@ } }, "source": [ - "### Exercise 1: Pure or impure functions? 🌶️\n", - "\n", - "For each of the functions below, determine whether they are pure or impure" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [], - "source": [ - "def fun1(a: \"list[str]\") -> None:\n", - " a.append(\"b\")\n", - "\n", - "\n", - "def fun2(a: int) -> int:\n", - " return a + 2\n", - "\n", - "\n", - "def fun3(a: \"dict[str, str]\") -> None:\n", - " a[\"test\"] = \"dest\"\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Exercise 2: Keeping only multiples of n 🌶️\n", - "\n", - "Given a list `L` of integers, write a function that only keeps the numbers that are multiples of a given constant `k`.\n", - "\n", - "- Example 1: given `nums = [1, 2, 3, 4, 5]`, and `k = 2`, the result must be `[2, 4]`\n", - "\n", - "- Example 2: given `nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ` and `k = 5`, the result is `[5, 10]`\n", - "\n", - "\n", - "\n", - "
\n", - "

Hints

\n", - "
    \n", - "
  • \n", - " The operator % (modulo) gives the remainder of division. a % k is 0 when a is a multiple of k. For more information, consult the chapter on basic datatypes. \n", - "
  • \n", - "
  • \n", - " Write your function in the cell below inside of the solution_exercise1 function. The function receives a list l as an input and should return another list\n", - "
  • \n", - "
\n", - "
\n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [], - "source": [ - "%%ipytest\n", - "\n", - "def solution_exercise2(l: \"list[int]\", k: int):\n", - " \"\"\"\n", - " Write your solution here\n", - " \"\"\"\n", - " pass" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Exercise 3: Transposing a Matrix 🌶️🌶️\n", + "### Exercise 1: Transposing a Matrix\n", "\n", "Consider a matrix `M` represented row-wise as a list of lists `[[1, 2, 3], [4, 5, 6], [7, 8, 8]]`.\n", "Write a function that returns the transpose of `M`, the matrix obtained by exchanging rows and columns\n", @@ -1481,12 +1537,10 @@ "

Hints

\n", "
    \n", "
  • \n", - " Write your function in solution_exercise3\n", - "
  • \n", " The zip function behaves similar to a transposition: zip([1,2], [3,4]) = [(1,3), (3,4)])\n", "
  • \n", "
  • \n", - " Write your function in the cell below inside of the solution_exercise3 function. The function receives a the \"matrix\" as an input m and should return another list\n", + " Write your function in the cell below inside of the solution_exercise1 function. The function receives a the \"matrix\" as an input m and should return another list\n", "
  • \n", "
\n", "
\n" @@ -1504,7 +1558,7 @@ "source": [ "%%ipytest\n", "\n", - "def solution_exercise3(m: \"list[list[int]]\") -> \"list[list[int]]\":\n", + "def solution_exercise1(m: \"list[list[int]]\") -> \"list[list[int]]\":\n", " \"\"\"\n", " Write your solution here\n", " \"\"\"\n", @@ -1519,13 +1573,10 @@ } }, "source": [ - "### Exercise 4: Flattening list of lists 🌶️🌶️\n", + "### Exercise 2: Flattening list of lists\n", "\n", "Imagine we receive a list of lists `l` like `[[1, 2], [3, 4]]`. Write a function that converts this list into a `flat` list like `[1, 2, 3, 4]`. \n", "\n", - "To write your function, just complete the code in `solution_exercise4`\n", - "\n", - "\n", "
\n", "

Hints

\n", "
    \n", @@ -1533,7 +1584,7 @@ " This is a good exercise to use functools.reduce \n", " \n", "
  • \n", - " Write your function in the cell below inside of the solution_exercise4 function. The function receives a list l as an input and should return another list\n", + " Write your function in the cell below inside of the solution_exercise2 function. The function receives a list l as an input and should return another list\n", "
  • \n", "
\n", "
" @@ -1551,7 +1602,7 @@ "source": [ "%%ipytest\n", "\n", - "def solution_exercise4(l: \"list[list[any]]\") -> \"list[any]\":\n", + "def solution_exercise2(l: \"list[list[any]]\") -> \"list[any]\":\n", " \"\"\"\n", " Write your solution here\n", " \"\"\"\n", @@ -1567,7 +1618,7 @@ } }, "source": [ - "### Exercise 5: Counting initials 🌶️🌶️🌶️\n", + "### Exercise 3: Counting initials\n", "\n", "Given a list `w` of English words, write a function that counts how many words begin with each letter of the English alphabet and returns the result as an **alphabetically sorted** list of tuples `(letter, count)`. We do not distinguish between uppercase and lowercase letters (\"A\" and \"a\" are considered the same). \n", "\n", @@ -1579,7 +1630,7 @@ " Consider the functions sorted and itertools.groupby from the Python standard library.\n", " \n", "
  • \n", - " Write your function in the cell below inside of the solution_exercise5 function. The function receives a list w as an input and should return another list\n", + " Write your function in the cell below inside of the solution_exercise3 function. The function receives a list w as an input and should return another list\n", "
  • \n", "
  • \n", " To ensure consistent capitalization you can use the lower() method of str\n", @@ -1601,7 +1652,7 @@ "source": [ "%%ipytest\n", "\n", - "def solution_exercise5(w: list[str]) -> list[(str, int)]:\n", + "def solution_exercise3(w: list[str]) -> list[(str, int)]:\n", " \"\"\"\n", " Write your solution here\n", " \"\"\"\n", @@ -1617,9 +1668,9 @@ } }, "source": [ - "### Exercise 6: Counting initials frequency 🌶️🌶️🌶️\n", + "### Exercise 4: Counting initials frequency\n", "If you could solve the previous exercise, you now have a list `l` of the form `[('a', 20), ('b', 30), ...]`.\n", - "If you cannot, do not worry: you will receive the correct input automatically as `l` inside the function `solution_exercise6`\n", + "If you cannot, do not worry: you will receive the correct input automatically as `l` inside the function `solution_exercise4`\n", "\n", "\n", "Write a function that computes the *relative frequency* of each letter in the list `l`. \n", @@ -1633,7 +1684,7 @@ " list over the total lenght of the list\n", "
  • \n", "
  • \n", - " Write your function in the cell below inside of the solution_exercise6 function. The function receives a list l as an input and should return another list\n", + " Write your function in the cell below inside of the solution_exercise4 function. The function receives a list l as an input and should return another list\n", "
  • \n", " \n", "
    " @@ -1651,7 +1702,7 @@ "source": [ "%%ipytest\n", "\n", - "def solution_exercise6(l: \"list[(str, int)]\") -> \"list[(str, float)]\":\n", + "def solution_exercise4(l: \"list[(str, int)]\") -> \"list[(str, float)]\":\n", " pass" ] }, @@ -1663,7 +1714,7 @@ } }, "source": [ - "### Exercise 7: Finding palindromes 🌶️🌶️🌶️\n", + "### Exercise 5: Finding palindromes\n", "Consider again the `words` list of strings. Write a function that returns the list of all *palindromes*. A *palindrome* is a word (any string longer than 1) that reads the same left-to-right and right-to-left.\n", "\n", "For example:\n", @@ -1682,7 +1733,7 @@ " A single character does not count as a palindrome.\n", " \n", "
  • \n", - " The words are available as the input words to solution_exercise7.\n", + " The words are available as the input words to solution_exercise5.\n", "
  • \n", " \n", "
    \n" @@ -1700,7 +1751,7 @@ "source": [ "%%ipytest\n", "\n", - "def solution_exercise7(words: \"list[str]\") -> \"list[str]\":\n", + "def solution_exercise5(words: \"list[str]\") -> \"list[str]\":\n", " pass" ] } diff --git a/tutorial/quiz/functional_programming.py b/tutorial/quiz/functional_programming.py index 1cba6f20..50d60525 100644 --- a/tutorial/quiz/functional_programming.py +++ b/tutorial/quiz/functional_programming.py @@ -4,7 +4,7 @@ class PureFunctions(Quiz): def __init__(self, title=""): q1 = Question( - question="""Is the following function pure:
    def f(x: int) -> int: \n    return x + 1
    """, + question="""Is the following function pure?
    def f(x: int) -> int: \n    return x + 1
    """, options={ "Yes": "Correct!", "No": "Why not? The function does not have any side effects.", @@ -14,7 +14,7 @@ def __init__(self, title=""): shuffle=True, ) q2 = Question( - question="""Is the following function pure:
    a = []\ndef f():\n    a.append(1)
    """, + question="""Is the following function pure?
    a = []\ndef f():\n    a.append(1)
    """, options={ "Yes": "Wrong! Notice that a is not an argument to the function", "No": "Correct", @@ -24,7 +24,7 @@ def __init__(self, title=""): shuffle=True, ) q3 = Question( - question="""Is the following function pure:
    def f(a: list[int]) -> list[int]:\n    return a+[1]
    """, + question="""Is the following function pure?
    def f(a: list[int]) -> list[int]:\n    return a+[1]
    """, options={ "Yes": "Correct!", "No": "Wrong! Notice that a is an argument to the function and we return a new list by concatenating a and [1].", @@ -33,5 +33,15 @@ def __init__(self, title=""): hint="Purity means that the function does not have any side effects, for example changing variables other than the inputs, opening files, etc.", shuffle=True, ) + q4 = Question( + question="""Is the following function pure?
    def f(a: dict[str, str]) -> None:\n    a["test"] = "dest"
    """, + options={ + "Yes": "Wrong! Notice that a is an argument to the function and we add a new key-value pair to this dictionary.", + "No": "Correct!", + }, + correct_answer="No", + hint="Purity means that the function does not have any side effects, for example changing variables other than the inputs, opening files, etc.", + shuffle=True, + ) - super().__init__(questions=[q1, q2, q3]) + super().__init__(questions=[q1, q2, q3, q4]) diff --git a/tutorial/tests/test_11_functional_programming.py b/tutorial/tests/test_11_functional_programming.py index 9cef97fb..46cbe408 100644 --- a/tutorial/tests/test_11_functional_programming.py +++ b/tutorial/tests/test_11_functional_programming.py @@ -11,8 +11,58 @@ from numpy.typing import NDArray -def reference_exercise2(my_list: List[int], k: int) -> List[int]: - return [i for i in my_list if i % k == 0] +# +# Example: Pure Function +# + + +def reference_pure_function(array, new_element): + array.append(new_element) + return array + + +@pytest.mark.parametrize( + "array, new_element", + [ + ([1, 2, 3], 4), + (["cat", "dog"], "bird"), + ], +) +def test_pure_function(array, new_element, function_to_test): + assert function_to_test(array, new_element) == reference_pure_function(array, new_element) + + +# +# Example: Composition +# + + +def reference_composition(x, y): + def square(x: int) -> int: + return x * x + + def sum(x: int, y: int) -> int: + return x + y + + eq = lambda x, y: sum(square(x), square(y)) + + return eq(x, y) + + +@pytest.mark.parametrize( + "x, y", + [ + (2, 4), + (5, 10), + ], +) +def test_composition(x, y, function_to_test): + assert function_to_test(x, y) == reference_composition(x, y) + + +# +# Example: Filter Even Numbers +# def check_for_loop_in_body(fun: Callable) -> bool: @@ -39,12 +89,17 @@ def reference_filter_even(my_list: List[int]) -> List[int]: def test_filter_even(function_to_test: Callable, my_list: List[int]): res = function_to_test(my_list) assert isinstance(res, list), "The function you wrote does not return a list" - assert res == reference_filter_even(my_list), ( - "The list you return is not equal to the expected solution" - ) assert not check_for_loop_in_body(function_to_test), ( "You are not allowed to use a for loop in this exercise" ) + assert res == reference_filter_even(my_list), ( + "The list you return is not equal to the expected solution" + ) + + +# +# Example: Add 1 to Each Element +# def reference_add_one(my_list: List[int]) -> List[int]: @@ -68,6 +123,15 @@ def test_add_one(function_to_test: Callable, my_list: List[int]): ) +# +# Example: Keeping only multiples of n +# + + +def reference_multiples_of_n(my_list: List[int], k: int) -> List[int]: + return [i for i in my_list if i % k == 0] + + @pytest.mark.parametrize( "my_list, k", [ @@ -75,15 +139,20 @@ def test_add_one(function_to_test: Callable, my_list: List[int]): (list(range(100)), 5), ], ) -def test_exercise2( +def test_multiples_of_n( function_to_test: Callable[[List[int]], int], my_list: List[int], k: int, ): - assert function_to_test(my_list, k) == reference_exercise2(my_list, k) + assert function_to_test(my_list, k) == reference_multiples_of_n(my_list, k) + + +# +# Exercise 1: Transposing a Matrix +# -def reference_exercise3(x: List[List[int]]) -> List[List[int]]: +def reference_exercise1(x: List[List[int]]) -> List[List[int]]: return [list(i) for i in zip(*x)] @@ -91,17 +160,22 @@ def reference_exercise3(x: List[List[int]]) -> List[List[int]]: "my_input", [(np.eye(3)), (np.random.randint(0, 100, size=(4, 4)))], ) -def test_exercise3( +def test_exercise1( function_to_test: Callable[[List[List[int]]], List[List[int]]], my_input: NDArray ): res = function_to_test(my_input.tolist()) assert ( - res == reference_exercise3(my_input.tolist()) + res == reference_exercise1(my_input.tolist()) and res == my_input.transpose().tolist() ) -def reference_exercise4(my_list: List[List[Any]]) -> List[Any]: +# +# Exercise 2: Flattening list of lists +# + + +def reference_exercise2(my_list: List[List[Any]]) -> List[Any]: return functools.reduce(lambda x, y: x + y, my_list) @@ -112,47 +186,62 @@ def reference_exercise4(my_list: List[List[Any]]) -> List[Any]: [["a", "b", "c"], ["d", "f", "e"], ["another"]], ], ) -def test_exercise4( +def test_exercise2( function_to_test: Callable[[List[List[Any]]], List[Any]], my_input: List[List[Any]], ): - assert function_to_test(my_input) == reference_exercise4(my_input) + assert function_to_test(my_input) == reference_exercise2(my_input) + + +# +# Exercise 3: Counting initials +# @functools.lru_cache -def get_data_exercise5() -> List[str]: +def get_data_exercise3() -> List[str]: words = requests.get("https://www.mit.edu/~ecprice/wordlist.10000").text return words.splitlines() -def reference_exercise5(w: List[str]) -> List[Tuple[str, int]]: +def reference_exercise3(w: List[str]) -> List[Tuple[str, int]]: return [ (k, len(list(v))) for k, v in itertools.groupby(sorted(w, key=lambda x: x[0]), key=lambda x: x[0]) ] -def test_exercise5(function_to_test: Callable[[List[str]], List[Tuple[str, int]]]): - data = get_data_exercise5() - assert function_to_test(data) == reference_exercise5(data) +def test_exercise3(function_to_test: Callable[[List[str]], List[Tuple[str, int]]]): + data = get_data_exercise3() + assert function_to_test(data) == reference_exercise3(data) -def reference_exercise6(my_list: List[Tuple[str, int]]) -> List[Tuple[str, float]]: +# +# Exercise 4: Counting initials frequency +# + + +def reference_exercise4(my_list: List[Tuple[str, int]]) -> List[Tuple[str, float]]: total = sum(map(lambda x: x[1], my_list)) # noqa: C417 return [(letter, freq / total) for letter, freq in my_list] -def test_exercise6( +def test_exercise4( function_to_test: Callable[[List[Tuple[str, int]]], List[Tuple[str, float]]], ): - input_data = reference_exercise5(get_data_exercise5()) - assert function_to_test(input_data) == reference_exercise6(input_data) + input_data = reference_exercise3(get_data_exercise3()) + assert function_to_test(input_data) == reference_exercise4(input_data) -def reference_function_exercise7(my_list: List[str]) -> List[str]: +# +# Exercise 5: Finding palindromes +# + + +def reference_exercise5(my_list: List[str]) -> List[str]: return list(filter(lambda x: x == x[::-1] and len(x) > 1, my_list)) -def test_exercise7(function_to_test: Callable[[List[str]], List[str]]): - data = get_data_exercise5() - assert function_to_test(data) == reference_function_exercise7(data) +def test_exercise5(function_to_test: Callable[[List[str]], List[str]]): + data = get_data_exercise3() + assert function_to_test(data) == reference_exercise5(data) From 093fcefbe5915c71b675e3017d2c9c936bab7b25 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 11:55:48 +0000 Subject: [PATCH 2/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tutorial/tests/test_11_functional_programming.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tutorial/tests/test_11_functional_programming.py b/tutorial/tests/test_11_functional_programming.py index 46cbe408..5638a03e 100644 --- a/tutorial/tests/test_11_functional_programming.py +++ b/tutorial/tests/test_11_functional_programming.py @@ -10,7 +10,6 @@ import requests from numpy.typing import NDArray - # # Example: Pure Function # @@ -29,7 +28,9 @@ def reference_pure_function(array, new_element): ], ) def test_pure_function(array, new_element, function_to_test): - assert function_to_test(array, new_element) == reference_pure_function(array, new_element) + assert function_to_test(array, new_element) == reference_pure_function( + array, new_element + ) # From 4ada45223d6b2eb42384b983da5398ec6821a30f Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Mon, 5 May 2025 15:39:23 +0200 Subject: [PATCH 3/9] add docstring --- 11_functional_programming.ipynb | 138 ++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 58 deletions(-) diff --git a/11_functional_programming.ipynb b/11_functional_programming.ipynb index ba974ff5..4bb502d9 100644 --- a/11_functional_programming.ipynb +++ b/11_functional_programming.ipynb @@ -635,7 +635,6 @@ "def non_transparent_function() -> int:\n", " return random.randint(0, 10)\n", "\n", - "\n", "# With a lamba we capture the expression x() + 5 in a function,\n", "# we get its value when we call it like a function\n", "y1 = lambda: non_transparent_function() + 5\n", @@ -1065,10 +1064,15 @@ "%%ipytest\n", "\n", "def solution_filter_even(input_data: list[int]) -> list[int]:\n", + " \"\"\"A function that filters only the even elements of a given list.\n", + "\n", + " Args:\n", + " input_data: the initial list\n", + " Returns:\n", + " - the filtered list with only even elements\n", " \"\"\"\n", - " Write your solution here\n", - " \"\"\"\n", - " pass" + "\n", + " return" ] }, { @@ -1090,10 +1094,15 @@ "%%ipytest\n", "\n", "def solution_add_one(input_data: list[int]) -> list[int]:\n", + " \"\"\"A function that adds the integer number 1 to each element of a given list.\n", + "\n", + " Args:\n", + " input_data: the initial list of integers\n", + " Returns:\n", + " - the edited list after adding 1 to each element\n", " \"\"\"\n", - " Write your solution here\n", - " \"\"\"\n", - " pass" + "\n", + " return" ] }, { @@ -1313,10 +1322,16 @@ "%%ipytest\n", "\n", "def solution_multiples_of_n(l: \"list[int]\", k: int):\n", + " \"\"\"A function that keeps only the multiples of k from a given list.\n", + "\n", + " Args:\n", + " l: the initial list\n", + " k: the integer number\n", + " Returns:\n", + " - the filtered list\n", " \"\"\"\n", - " Write your solution here\n", - " \"\"\"\n", - " pass" + "\n", + " return" ] }, { @@ -1526,7 +1541,7 @@ "### Exercise 1: Transposing a Matrix\n", "\n", "Consider a matrix `M` represented row-wise as a list of lists `[[1, 2, 3], [4, 5, 6], [7, 8, 8]]`.\n", - "Write a function that returns the transpose of `M`, the matrix obtained by exchanging rows and columns\n", + "Write a function that returns the transpose of `M`, the matrix obtained by exchanging rows and columns.\n", "\n", "- Example 1: given `M=[[1, 0], [0, 1]]`, the result must be `[[1, 0], [0, 1]]`\n", "- Example 2: given `M=[[1, 2, 3], [4, 5, 6], [7, 8, 8]]` the result must be `[1, 4, 7], [2, 5, 8], [3, 6, 8]]`\n", @@ -1535,14 +1550,7 @@ "\n", "
    \n", "

    Hints

    \n", - "
      \n", - "
    • \n", - " The zip function behaves similar to a transposition: zip([1,2], [3,4]) = [(1,3), (3,4)])\n", - "
    • \n", - "
    • \n", - " Write your function in the cell below inside of the solution_exercise1 function. The function receives a the \"matrix\" as an input m and should return another list\n", - "
    • \n", - "
    \n", + " The zip function behaves similar to a transposition: zip([1,2], [3,4]) = [(1,3), (3,4)])\n", "
    \n" ] }, @@ -1559,10 +1567,16 @@ "%%ipytest\n", "\n", "def solution_exercise1(m: \"list[list[int]]\") -> \"list[list[int]]\":\n", + " \"\"\"A function that returns the transpose of a given matrix, by using the zip function.\n", + " The transpose of a matrix is obtained by swapping the rows and columns of the matrix.\n", + "\n", + " Args:\n", + " m: the initial matrix\n", + " Returns:\n", + " - the transposed matrix\n", " \"\"\"\n", - " Write your solution here\n", - " \"\"\"\n", - " pass\n" + "\n", + " return\n" ] }, { @@ -1579,14 +1593,7 @@ "\n", "
    \n", "

    Hints

    \n", - "
      \n", - "
    • \n", - " This is a good exercise to use functools.reduce \n", - "
    • \n", - "
    • \n", - " Write your function in the cell below inside of the solution_exercise2 function. The function receives a list l as an input and should return another list\n", - "
    • \n", - "
    \n", + " This is a good exercise to use functools.reduce \n", "
    " ] }, @@ -1603,11 +1610,15 @@ "%%ipytest\n", "\n", "def solution_exercise2(l: \"list[list[any]]\") -> \"list[any]\":\n", + " \"\"\"A function that returns a flattened list from a given list of lists, by using funtools.reduce\n", + "\n", + " Args:\n", + " l: the initial list of lists\n", + " Returns:\n", + " - the flattened list\n", " \"\"\"\n", - " Write your solution here\n", - " \"\"\"\n", - " pass\n", - "\n" + "\n", + " return\n" ] }, { @@ -1630,9 +1641,6 @@ " Consider the functions sorted and itertools.groupby from the Python standard library.\n", " \n", "
  • \n", - " Write your function in the cell below inside of the solution_exercise3 function. The function receives a list w as an input and should return another list\n", - "
  • \n", - "
  • \n", " To ensure consistent capitalization you can use the lower() method of str\n", "
  • \n", " \n", @@ -1653,11 +1661,18 @@ "%%ipytest\n", "\n", "def solution_exercise3(w: list[str]) -> list[(str, int)]:\n", + " \"\"\"A function that counts the number of words from a given list that start with each letter of the alphabet.\n", + " The function should be case insensitive.\n", + " It should return a list of tuples, where each tuple contains a letter and the number of words that start with that letter.\n", + " This tuple should be sorted in alphabetical order.\n", + "\n", + " Args:\n", + " w: the initial list of words\n", + " Returns:\n", + " - the alphabetically sorted list of tuples\n", " \"\"\"\n", - " Write your solution here\n", - " \"\"\"\n", - " pass\n", - "\n" + "\n", + " return\n" ] }, { @@ -1673,20 +1688,12 @@ "If you cannot, do not worry: you will receive the correct input automatically as `l` inside the function `solution_exercise4`\n", "\n", "\n", - "Write a function that computes the *relative frequency* of each letter in the list `l`. \n", + "Write a function that computes the *relative frequency* of each letter in the list `l` and returns a list of tuples `(letter, frequency)`.\n", "\n", "\n", "
    \n", "

    Hints

    \n", - "
      \n", - "
    • \n", - " The relative frequence of a value a in a list is simply the number of time it appears in that\n", - " list over the total lenght of the list\n", - "
    • \n", - "
    • \n", - " Write your function in the cell below inside of the solution_exercise4 function. The function receives a list l as an input and should return another list\n", - "
    • \n", - "
    \n", + " The relative frequence of a value a in a list is simply the number of time it appears in that list over the total lenght of the list.\n", "
    " ] }, @@ -1703,7 +1710,16 @@ "%%ipytest\n", "\n", "def solution_exercise4(l: \"list[(str, int)]\") -> \"list[(str, float)]\":\n", - " pass" + " \"\"\"A function that computes the relative frequency of each letter from a given list of tuples.\n", + " The relative frequency is calculated as the number of occurrences of a letter in the list, divided by the length of the list.\n", + "\n", + " Args:\n", + " l: the initial list of tuples\n", + " Returns:\n", + " - the list of tuples with the relative frequency of each letter\n", + " \"\"\"\n", + "\n", + " return" ] }, { @@ -1715,7 +1731,7 @@ }, "source": [ "### Exercise 5: Finding palindromes\n", - "Consider again the `words` list of strings. Write a function that returns the list of all *palindromes*. A *palindrome* is a word (any string longer than 1) that reads the same left-to-right and right-to-left.\n", + "Consider again the `words` list of strings. Write a function that returns the list of all *palindromes*. A *palindrome* is a word (any string longer than 1) that reads the same left-to-right and right-to-left.\n", "\n", "For example:\n", "- rotator\n", @@ -1723,9 +1739,6 @@ "- noon\n", "- radar\n", "\n", - "\n", - "\n", - "\n", "
    \n", "

    Hints

    \n", "
      \n", @@ -1733,7 +1746,7 @@ " A single character does not count as a palindrome.\n", " \n", "
    • \n", - " The words are available as the input words to solution_exercise5.\n", + " The words are available as the input to solution_exercise3.\n", "
    • \n", "
    \n", "
    \n" @@ -1752,7 +1765,16 @@ "%%ipytest\n", "\n", "def solution_exercise5(words: \"list[str]\") -> \"list[str]\":\n", - " pass" + " \"\"\"A function that returns a list of words from a given list of words, that are palindromes.\n", + " A palindrome is a word that reads the same backwards as forward.\n", + "\n", + " Args:\n", + " words: the initial list of words\n", + " Returns:\n", + " - the list of palindromes\n", + " \"\"\"\n", + "\n", + " return" ] } ], From ebdcacc5675b398c3aeffe575a40d88ef40adb9f Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Mon, 5 May 2025 16:00:11 +0200 Subject: [PATCH 4/9] fix composition example --- 11_functional_programming.ipynb | 4 ++-- tutorial/tests/test_11_functional_programming.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/11_functional_programming.ipynb b/11_functional_programming.ipynb index 4bb502d9..6c8aa4e4 100644 --- a/11_functional_programming.ipynb +++ b/11_functional_programming.ipynb @@ -422,7 +422,7 @@ "\n", "
    \n", "

    Question

    \n", - " Inside the solution function below, write the solution of equation x^2 + y^2, using lambda expressions.\n", + " Inside the solution function below, write the solution of equation x^2 + y^2, using composition.\n", "
    " ] }, @@ -444,7 +444,7 @@ "%%ipytest\n", "\n", "def solution_composition(x: int, y: int):\n", - " \"\"\"A function that solves the equation x^2 + y^2, using lambda functions.\n", + " \"\"\"A function that solves the equation x^2 + y^2, using composition.\n", "\n", " Args:\n", " x: the first integer\n", diff --git a/tutorial/tests/test_11_functional_programming.py b/tutorial/tests/test_11_functional_programming.py index 5638a03e..8224306d 100644 --- a/tutorial/tests/test_11_functional_programming.py +++ b/tutorial/tests/test_11_functional_programming.py @@ -42,10 +42,11 @@ def reference_composition(x, y): def square(x: int) -> int: return x * x - def sum(x: int, y: int) -> int: + def add(x: int, y: int) -> int: return x + y - eq = lambda x, y: sum(square(x), square(y)) + def eq(x: int, y: int) -> int: + return add(square(x), square(y)) return eq(x, y) From d82a63863652c3315242a21e43a40ab045ad9d04 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Tue, 6 May 2025 17:40:20 +0200 Subject: [PATCH 5/9] Further improvements. --- 11_functional_programming.ipynb | 104 ++++++++++-------- .../tests/test_11_functional_programming.py | 9 +- 2 files changed, 65 insertions(+), 48 deletions(-) diff --git a/11_functional_programming.ipynb b/11_functional_programming.ipynb index 6c8aa4e4..ebe415fb 100644 --- a/11_functional_programming.ipynb +++ b/11_functional_programming.ipynb @@ -68,11 +68,11 @@ "\n", "- The [functools reference](https://docs.python.org/3/library/functools.html) from the Python standard library (text)\n", "- The [itertools reference](https://docs.python.org/3/library/itertools.html) from the Python standard library (text)\n", - "- [Functional programming howto](https://docs.python.org/3/howto/functional.html) from the Python documentation\n", + "- [Functional programming howto](https://docs.python.org/3/howto/functional.html) from the Python documentation (text)\n", "- A [good, but old introduction](https://www.youtube.com/watch?v=Ta1bAMOMFOI) of functional programming (video)\n", "- A [very high level](https://www.youtube.com/watch?v=Qa8IfEeBJqk) introduction of functional programming (video, advanced). Interesting, but not Python-specific as it refers to Haskell\n", "- A [general introduction on functional programming](https://www.youtube.com/watch?v=8z_bUIl_uPo). Very worth watching as it uses Python for the examples (video)\n", - "- [Principles of functional programming](https://dev.to/jamesrweb/principles-of-functional-programming-4b7c)\n", + "- [Principles of functional programming](https://dev.to/jamesrweb/principles-of-functional-programming-4b7c) (text)\n", "\n", "# Introduction\n", "Functional programming is an approach to programming where programs are built by composing and running functions that perform a series of transformations on data.\n", @@ -80,13 +80,16 @@ "Typically, within functional programming, great focus is placed on *composition*, *immutability* and *purity*.\n", "We are going to define these terms in more detail later.\n", "\n", - "# Why Functional Programming\n", - "Why do we choose functional programming? There are a series of advantages to this approach, namely:\n", + "# Why Functional Programming?\n", + "There are a series of advantages to this approach, namely:\n", "- **Debugging** and **testing** are easy: there are no surprises because every function only does one thing and does not affect any other piece of the program.\n", - "- **Parallelization** is trivial: functions are just small *boxes* that take one input and produce an output and do not depend on other parts of the code in an implicit manner through global variables or other shared pieces of state. Thus, it's easy to make several functions run in parallel.\n", + "- **Parallelization** is trivial: functions are just small *boxes* that take one input and produce an output and do not depend on other parts of the code in an implicit manner through global variables or other shared pieces of state.\n", + " Thus, it's easy to make several functions run in parallel.\n", "\n", "# The basic principles of functional programming\n", - "All modern programming languages have *functions* (or methods, procedures, subroutines, subprograms); these are groups of program statements that perform a certain computation. Functions are defined once for the whole program and can be reused at will throughout the program whenever we need to perform the specific computation they are defined for. Using functions, we can split our code in smaller units that are only responsible for a specific *functionality*; this helps us structuring our code in a clean and understandable form. \n", + "All modern programming languages have *functions* (or methods, procedures, subroutines, subprograms); these are groups of program statements that perform a certain computation.\n", + "Functions are defined once for the whole program and can be reused at will throughout the program whenever we need to perform the specific computation they are defined for.\n", + "Using functions, we can split our code in smaller units that are only responsible for a specific *functionality*; this helps us structuring our code in a clean and understandable form. \n", "\n", "The main principles we use in functional programming are:\n", "\n", @@ -119,9 +122,11 @@ "In functional programming, we try to strive for *purity*. We want to define and use functions that only depend on their input, always return the same output for the same inputs and do not have any *side effects*, that is they do not indirectly affect any other part of our program.\n", "You can think of these functions as mathematical functions. \n", "Some examples of side effects are:\n", - "- printing to the program output\n", - "- reading or writing files\n", - "- generating and using random numbers\n", + "\n", + "- Printing to the program output\n", + "- Reading or writing files\n", + "- Generating and using random numbers\n", + "\n", "To better understand this concept, let us look at the function `my_first_pure_function` we defined below:" ] }, @@ -228,14 +233,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As you see from the output, the function modified the list `x`. Therefore, this function is **not pure**. This leads us to the next principle, **immutability**." + "As you see from the output, the function modified the list `x`.\n", + "Therefore, this function is **not pure**.\n", + "This leads us to the next principle, **immutability**." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Example\n", + "### Exercise on pure functions\n", "\n", "
    \n", "

    Question

    \n", @@ -270,8 +277,8 @@ " Returns:\n", " - the appended list\n", " \"\"\"\n", - "\n", - " return" + " array.append(new_element)\n", + " return array" ] }, { @@ -370,7 +377,8 @@ "tags": [] }, "source": [ - "Another important aspect of functional programming is `composition`. This means that whenever we want to perform multiple operations, we avoid writing intermediate variables, especially so when these are in the global state of the program.\n", + "Another important aspect of functional programming is `composition`.\n", + "This means that whenever we want to perform multiple operations, we avoid writing intermediate variables, especially so when these are in the global state of the program.\n", "\n", "Instead, we design our functions in such a way that one function's output can be directly fed into the next function.\n", "\n", @@ -418,7 +426,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Example\n", + "### Exercise on composition\n", "\n", "
    \n", "

    Question

    \n", @@ -475,8 +483,7 @@ }, "source": [ "Another important principle of functional programming is that **functions are values**.\n", - "In programming languages (like Python) that support a functional style, we can manipulate functions with the language, we can pass them around in a variable and even use functions as parameters for another function.\n", - "\n", + "In programming languages that support a functional style (like Python), functions are treated as first-class objects: we can assign them to variables, pass them as arguments to other functions, and manipulate them just like any other value.\n", "As an example, consider the function `function_caller`:" ] }, @@ -507,7 +514,8 @@ } }, "source": [ - "This function takes a function `f` and its argument `args` as input and returns the result of calling `f`, while additionally printing a message on the standard output of the program. Let's try this out.\n", + "This function takes a function `f` and its argument `args` as input and returns the result of calling `f`, while additionally printing a message on the standard output of the program.\n", + "Let's try this out.\n", "To do so, we define a new function `add_five`:" ] }, @@ -558,10 +566,9 @@ }, "source": [ "This example was a bit convoluted, but it shows that in Python we can use functions as values and even pass them as arguments to other functions.\n", - "\n", "This is useful in many cases; a typical example is numerical optimization, where we want to find the parameters of a function that minimize a certain criterion.\n", - "\n", - "Other than these specific use cases, there are some common *higher-order functions*, or functions that take other functions as parameters, that are common in most programming languages.We will look at a few of them in a following section." + "Other than these specific use cases, there are some common *higher-order functions*, or functions that take other functions as parameters, that are common in most programming languages.\n", + "We will look at a few of them in a following section." ] }, { @@ -582,7 +589,8 @@ "tags": [] }, "source": [ - "Another principle of functional programming is called [**referential transparency**](https://en.wikipedia.org/wiki/Referential_transparency). This complex-sounding name simply means that we can replace any expression with its value without changing the behavior of the program.\n", + "Another principle of functional programming is called [**referential transparency**](https://en.wikipedia.org/wiki/Referential_transparency).\n", + "This complex-sounding name simply means that we can replace any expression with its value without changing the behavior of the program.\n", "\n", "Consider the following Python code:" ] @@ -613,7 +621,8 @@ } }, "source": [ - "If `transparent_function` is referentially transparent, we can replace its value in the expression `y1 = transparent_function(1) + 5` and we obtain the same value. In other words, the value of `y` does not change if we execute it again, as shown in the code above: we have two expressions `y1` and `y2` that use the same code and because `x` is referentially transparent, their values are equal.\n", + "If `transparent_function` is referentially transparent, we can replace its value in the expression `y1 = transparent_function(1) + 5` and we obtain the same value.\n", + "In other words, the value of `y` does not change if we execute it again, as shown in the code above: we have two expressions `y1` and `y2` that use the same code and because `x` is referentially transparent, their values are equal.\n", "\n", "In this case, this is true because the value of `x` is simply 1 and is invariant.\n", "\n", @@ -730,7 +739,8 @@ "metadata": {}, "source": [ "In a language with stricter typing discipline like Java or C++, we would not even be able to compile our program.\n", - "Python lacks this strictness. However, there are tools like [mypy](https://mypy-lang.org/) that can be used to check the type consistency of Python programs without running them.\n", + "Python lacks this strictness.\n", + "However, there are tools like [mypy](https://mypy-lang.org/) that can be used to check the type consistency of Python programs without running them.\n", "\n", "We can achieve a similar effect using execution-time type checks.\n", "To facilitate our work, we first construct a `Person` class that encapsulates the attributes of a person.\n", @@ -771,7 +781,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Because of the `isinstance` check in `__init_`, we aren't able to construct a `Person` if `age` is a string (type `str`). Likewise, we cannot call `greet_better` with a value other than a `Person`.\n" + "Because of the `isinstance` check in `__init_`, we aren't able to construct a `Person` if `age` is a string (type `str`).\n", + "Likewise, we cannot call `greet_better` with a value other than a `Person`.\n" ] }, { @@ -928,9 +939,10 @@ }, "source": [ "## Filtering\n", - "Another basic higher order function is `filter`.\n", + "Another basic higher order function (HoF) is `filter`.\n", "As the name says, this function is used to filter an `iterable` using a *predicate function*.\n", - "This is a function that takes a value and returns `true` or `false`. With `true`, the current element is kept, with `false` it is discarded.\n", + "This is a function that takes a value and returns `true` or `false`.\n", + "With `true`, the current element is kept, with `false` it is discarded.\n", "We now try to write a predicate function that only keeps even numbers.\n", "\n", "" @@ -958,7 +970,8 @@ } }, "source": [ - "Now we are ready to try `filter` using `is_even`. Because `filter` returns a `filter` object, we wrap it in `list` to directly see the result as a list:" + "Now we are ready to try `filter` using `is_even`.\n", + "Because `filter` returns a `filter` object, we wrap it in `list` to directly see the result as a list:" ] }, { @@ -984,14 +997,15 @@ }, "source": [ "## Reducing\n", - "A third basic HoF is *reduction*.\n", + "The third basic HoF is *reduction*.\n", "This is a function that takes a function `f(x, y)` of two arguments and an iterable `it` and applies the function to every element in the iterable *cumulatively* to produce one value.\n", "It works in the following way:\n", "\n", "- The first argument of `f`, `x`, is the **current value of the accumulation**. In the beginning, this corresponds to the first element of `it`.\n", "- The second argument of `f`, `y`, is the **current element of the iterable**. In the beginning, this corresponds to the second element of `it`.\n", "\n", - "Because of this behaviour, this function is handy to compute sums or similar aggregations over a list. In Python, this function is available in the [`functools` module](https://docs.python.org/3/library/functools.html), part of Python's standard library.\n", + "Because of this behaviour, this function is handy to compute sums or similar aggregations over a list.\n", + "In Python, this function is available in the [`functools` module](https://docs.python.org/3/library/functools.html), part of Python's standard library.\n", "\n", "As an example of using `reduce`, consider the following snippet:\n" ] @@ -1033,7 +1047,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Examples: Iteration and Mapping" + "### Exercises on Iteration and Mapping" ] }, { @@ -1282,7 +1296,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Example: Keeping only multiples of n\n", + "### Exercise: Keeping only multiples of `n`\n", "\n", "
    \n", "

    Question

    \n", @@ -1321,7 +1335,7 @@ "source": [ "%%ipytest\n", "\n", - "def solution_multiples_of_n(l: \"list[int]\", k: int):\n", + "def solution_multiples_of_n(my_list: \"list[int]\", k: int) -> \"list[int]\":\n", " \"\"\"A function that keeps only the multiples of k from a given list.\n", "\n", " Args:\n", @@ -1331,7 +1345,7 @@ " - the filtered list\n", " \"\"\"\n", "\n", - " return" + " return [a for a in my_list if a % k == 0]" ] }, { @@ -1348,9 +1362,11 @@ "\n", "Lets see some example of problems we can solve by using these tools.\n", "\n", - "- Iterating over two lists in parallel: \n", - " consider the two lists `numbers = [1, 2, 3, 4, 5]` and `words = [\"one\", \"two\", \"three\" , \"four\", \"five\"]`. We want to produce a list of strings consisting of the content of `numbers` and `words`, such that each number in `numbers` corresponds to the right word in `words`. We can do this by using the `zip` buil-in function:\n", - "`zip` takes a number of `iterables` as an input and returns a list of tuples. The i-th element of this list is a tuple with the i-th element of each iterable, proceeding until the shortes iterable is exhausted. Using `zip`, we solve our problem like this:" + "- Iterating over two lists in parallel: consider the two lists `numbers = [1, 2, 3, 4, 5]` and `words = [\"one\", \"two\", \"three\" , \"four\", \"five\"]`.\n", + " We want to produce a list of strings consisting of the content of `numbers` and `words`, such that each number in `numbers` corresponds to the right word in `words`.\n", + " We can do this by using the `zip` buil-in function: `zip` takes a number of `iterables` as an input and returns a list of tuples.\n", + " The `i-th` element of this list is a tuple with the `i-th` element of each iterable, proceeding until the shortes iterable is exhausted.\n", + " Using `zip`, we solve our problem like this:" ] }, { @@ -1376,10 +1392,10 @@ } }, "source": [ - "- Producing permutations of elements.\n", - " Suppose we have a list containing the letters `[\"A\", \"B\", \"C\", \"D\", \"E\"]` and we want to produce all four letter words from them.\n", - " We can use `itertools.permutations`.\n", - " Once again, we wrap the operation in `list` to obtain a list as output:" + "- Producing permutations of elements.\n", + " Suppose we have a list containing the letters `[\"A\", \"B\", \"C\", \"D\", \"E\"]` and we want to produce all four letter words from them.\n", + " We can use `itertools.permutations`.\n", + " Once again, we wrap the operation in `list` to obtain a list as output:" ] }, { @@ -1405,7 +1421,9 @@ } }, "source": [ - "Another useful trick to know when working with lists is [**unpacking**](https://docs.python.org/3/tutorial/controlflow.html#tut-unpacking-arguments). In Python, we can extract elements from a list using the assignment statement. For example, if we have a two-element list, we can write" + "- [**Unpacking**](https://docs.python.org/3/tutorial/controlflow.html#tut-unpacking-arguments) is another useful trick to know when working with lists. \n", + " In Python, we can extract elements from a list using the assignment statement.\n", + " For example, if we have a two-element list, we can write" ] }, { @@ -1795,7 +1813,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.3" + "version": "3.12.10" }, "vscode": { "interpreter": { diff --git a/tutorial/tests/test_11_functional_programming.py b/tutorial/tests/test_11_functional_programming.py index 8224306d..7a813e6b 100644 --- a/tutorial/tests/test_11_functional_programming.py +++ b/tutorial/tests/test_11_functional_programming.py @@ -16,8 +16,7 @@ def reference_pure_function(array, new_element): - array.append(new_element) - return array + return array + [new_element] @pytest.mark.parametrize( @@ -28,9 +27,9 @@ def reference_pure_function(array, new_element): ], ) def test_pure_function(array, new_element, function_to_test): - assert function_to_test(array, new_element) == reference_pure_function( - array, new_element - ) + output_array = function_to_test(array, new_element) + assert id(output_array) != id(array), "The arrays must be different objects." + assert output_array == reference_pure_function(array, new_element) # From 1ec11c3b8666f3e223546f586119b29e1cde7572 Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Wed, 7 May 2025 00:11:56 +0200 Subject: [PATCH 6/9] update TOC, fix details --- 11_functional_programming.ipynb | 54 ++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/11_functional_programming.ipynb b/11_functional_programming.ipynb index ebe415fb..58ce27dd 100644 --- a/11_functional_programming.ipynb +++ b/11_functional_programming.ipynb @@ -14,14 +14,14 @@ "## Table of Contents\n", " - [References](#References)\n", " - [Introduction](#Introduction)\n", - " - [Why Functional Programming](#Why-Functional-Programming)\n", + " - [Why Functional Programming?](#Why-Functional-Programming?)\n", " - [The basic principles of functional programming](#The-basic-principles-of-functional-programming)\n", " - [Pure Functions (Purity)](#Pure-Functions-(Purity))\n", - " - [Example](#Example)\n", + " - [Exercise on pure functions](#Exercise-on-pure-functions)\n", " - [Quiz on pure functions](#Quiz-on-pure-functions)\n", " - [Immutability](#Immutability)\n", " - [Composition](#Composition)\n", - " - [Example](#Example)\n", + " - [Exercise on composition](#Exercise-on-composition)\n", " - [Higher Order Functions / Functions as Values](#Higher-Order-Functions-/-Functions-as-Values)\n", " - [Referential transparency](#Referential-transparency)\n", " - [Type systems](#Type-systems)\n", @@ -29,9 +29,9 @@ " - [Mapping](#Mapping)\n", " - [Filtering](#Filtering)\n", " - [Reducing](#Reducing)\n", - " - [Examples: Iteration and Mapping](#Examples:-Iteration-and-Mapping)\n", + " - [Exercises on Iteration and Mapping](#Exercises-on-Iteration-and-Mapping)\n", " - [List comprehensions](#List-comprehensions)\n", - " - [Example: Keeping only multiples of n](#Example:-Keeping-only-multiples-of-n)\n", + " - [Exercise: Keeping only multiples of `n`](#Exercise:-Keeping-only-multiples-of-n)\n", " - [Combinining and splitting iterators](#Combinining-and-splitting-iterators)\n", " - [Exercises](#Exercises)\n", " - [Exercise 1: Transposing a Matrix](#Exercise-1:-Transposing-a-Matrix)\n", @@ -269,11 +269,11 @@ "%%ipytest\n", "\n", "def solution_pure_function(array: list, new_element: str):\n", - " \"\"\"A function that appends a given array with a given element.\n", + " \"\"\"A pure function that appends a given array with a given element.\n", "\n", " Args:\n", " array: the initial list\n", - " new_element: the element to be added to the initial list\n", + " new_element: the element to be added to the list\n", " Returns:\n", " - the appended list\n", " \"\"\"\n", @@ -311,6 +311,28 @@ "## Immutability" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Previously, we saw this example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x = [\"short\", \"list\"] \n", + "def do_something(y: str) -> None:\n", + " x.append(y)\n", + "\n", + "print(x)\n", + "do_something(\"a\")\n", + "print(x)" + ] + }, { "cell_type": "markdown", "metadata": { @@ -320,9 +342,9 @@ "tags": [] }, "source": [ - "When writing programs in functional style, we usually avoid functions like `do_something`.\n", - "Instead of modifying existing data (*mutation*), you write functions that transform your data and return new objects.\n", - "Another way to rewrite the function above could be:" + "But when writing programs in functional style, we prefer to avoid functions like `do_something`.\n", + "Instead of modifying existing data (*mutation*), we write functions that transform the data and return new objects.\n", + "So, another way to rewrite the function above could be:" ] }, { @@ -621,8 +643,10 @@ } }, "source": [ - "If `transparent_function` is referentially transparent, we can replace its value in the expression `y1 = transparent_function(1) + 5` and we obtain the same value.\n", - "In other words, the value of `y` does not change if we execute it again, as shown in the code above: we have two expressions `y1` and `y2` that use the same code and because `x` is referentially transparent, their values are equal.\n", + "If `transparent_function` is referentially transparent, we can replace the function call `transparent_function(1)` with its result without changing the meaning of the expression.\n", + "For example, in `y1 = transparent_function(1) + 5`, the result of `y1` will always be the same every time the code runs.\n", + "This is demonstrated by two expressions, `y1` and `y2`, that both use `transparent_function(1)`, and because the function is referentially transparent, `y1` and `y2` will have equal values.\n", + "\n", "\n", "In this case, this is true because the value of `x` is simply 1 and is invariant.\n", "\n", @@ -1300,7 +1324,7 @@ "\n", "
    \n", "

    Question

    \n", - " Given a list L of integers, write a function that only keeps the numbers that are multiples of a given constant k.\n", + " Given a list of integers, write a function that only keeps the numbers that are multiples of a given constant k.\n", "
      \n", "
    • \n", " Example 1: given nums = [1, 2, 3, 4, 5], and k = 2, the result must be [2, 4]\n", @@ -1339,13 +1363,13 @@ " \"\"\"A function that keeps only the multiples of k from a given list.\n", "\n", " Args:\n", - " l: the initial list\n", + " my_list: the initial list\n", " k: the integer number\n", " Returns:\n", " - the filtered list\n", " \"\"\"\n", "\n", - " return [a for a in my_list if a % k == 0]" + " return" ] }, { From 67d249ceb214d1e588b2f9813f993d6126ea8863 Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Wed, 7 May 2025 08:26:00 +0200 Subject: [PATCH 7/9] fix final exercises --- 11_functional_programming.ipynb | 57 ++++++++++--------- .../tests/test_11_functional_programming.py | 14 +++-- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/11_functional_programming.ipynb b/11_functional_programming.ipynb index 58ce27dd..e4bf67a5 100644 --- a/11_functional_programming.ipynb +++ b/11_functional_programming.ipynb @@ -1582,17 +1582,17 @@ "source": [ "### Exercise 1: Transposing a Matrix\n", "\n", - "Consider a matrix `M` represented row-wise as a list of lists `[[1, 2, 3], [4, 5, 6], [7, 8, 8]]`.\n", - "Write a function that returns the transpose of `M`, the matrix obtained by exchanging rows and columns.\n", + "Consider a matrix represented row-wise as a list of lists `[[1, 2, 3], [4, 5, 6], [7, 8, 8]]`.\n", + "Write a function that returns its transpose, the matrix obtained by exchanging rows and columns.\n", "\n", - "- Example 1: given `M=[[1, 0], [0, 1]]`, the result must be `[[1, 0], [0, 1]]`\n", - "- Example 2: given `M=[[1, 2, 3], [4, 5, 6], [7, 8, 8]]` the result must be `[1, 4, 7], [2, 5, 8], [3, 6, 8]]`\n", + "- Example 1: given `matrix=[[1, 0], [0, 1]]`, the result must be `[[1, 0], [0, 1]]`\n", + "- Example 2: given `matrix=[[1, 2, 3], [4, 5, 6], [7, 8, 8]]` the result must be `[1, 4, 7], [2, 5, 8], [3, 6, 8]]`\n", "\n", "\n", "\n", "
      \n", "

      Hints

      \n", - " The zip function behaves similar to a transposition: zip([1,2], [3,4]) = [(1,3), (3,4)])\n", + " The zip function behaves similarly to a transposition: zip([1,2], [3,4]) = [(1,3), (2,4)])\n", "
      \n" ] }, @@ -1608,12 +1608,12 @@ "source": [ "%%ipytest\n", "\n", - "def solution_exercise1(m: \"list[list[int]]\") -> \"list[list[int]]\":\n", + "def solution_exercise1(matrix: list[list[int]]) -> list[list[int]]:\n", " \"\"\"A function that returns the transpose of a given matrix, by using the zip function.\n", " The transpose of a matrix is obtained by swapping the rows and columns of the matrix.\n", "\n", " Args:\n", - " m: the initial matrix\n", + " matrix: the initial matrix\n", " Returns:\n", " - the transposed matrix\n", " \"\"\"\n", @@ -1631,7 +1631,7 @@ "source": [ "### Exercise 2: Flattening list of lists\n", "\n", - "Imagine we receive a list of lists `l` like `[[1, 2], [3, 4]]`. Write a function that converts this list into a `flat` list like `[1, 2, 3, 4]`. \n", + "Imagine we receive a list of lists like `[[1, 2], [3, 4]]`. Write a function that converts this list into a `flat` list like `[1, 2, 3, 4]`. \n", "\n", "
      \n", "

      Hints

      \n", @@ -1651,11 +1651,11 @@ "source": [ "%%ipytest\n", "\n", - "def solution_exercise2(l: \"list[list[any]]\") -> \"list[any]\":\n", + "def solution_exercise2(my_list: list[list[any]]) -> list[any]:\n", " \"\"\"A function that returns a flattened list from a given list of lists, by using funtools.reduce\n", "\n", " Args:\n", - " l: the initial list of lists\n", + " my_list: the initial list of lists\n", " Returns:\n", " - the flattened list\n", " \"\"\"\n", @@ -1673,7 +1673,7 @@ "source": [ "### Exercise 3: Counting initials\n", "\n", - "Given a list `w` of English words, write a function that counts how many words begin with each letter of the English alphabet and returns the result as an **alphabetically sorted** list of tuples `(letter, count)`. We do not distinguish between uppercase and lowercase letters (\"A\" and \"a\" are considered the same). \n", + "Given a list of English words, write a function that counts how many words begin with each letter of the English alphabet and returns the result as an **alphabetically sorted** list of tuples `(letter, count)`. We do not distinguish between uppercase and lowercase letters (\"A\" and \"a\" are considered the same). \n", "\n", "\n", "
      \n", @@ -1702,14 +1702,14 @@ "source": [ "%%ipytest\n", "\n", - "def solution_exercise3(w: list[str]) -> list[(str, int)]:\n", - " \"\"\"A function that counts the number of words from a given list that start with each letter of the alphabet.\n", - " The function should be case insensitive.\n", + "def solution_exercise3(words: list[str]) -> list[(str, int)]:\n", + " \"\"\"A function that counts the number of words from a given list that start with each letter of the alphabet, using sorted() and itertools.groupby.\n", + " The function should be case insensitive, using lower() to ensure consistet capitalization.\n", " It should return a list of tuples, where each tuple contains a letter and the number of words that start with that letter.\n", - " This tuple should be sorted in alphabetical order.\n", + " The list of tuples should be sorted in alphabetical order.\n", "\n", " Args:\n", - " w: the initial list of words\n", + " words: the initial list of words\n", " Returns:\n", " - the alphabetically sorted list of tuples\n", " \"\"\"\n", @@ -1726,16 +1726,16 @@ }, "source": [ "### Exercise 4: Counting initials frequency\n", - "If you could solve the previous exercise, you now have a list `l` of the form `[('a', 20), ('b', 30), ...]`.\n", - "If you cannot, do not worry: you will receive the correct input automatically as `l` inside the function `solution_exercise4`\n", + "If you could solve the previous exercise, you now have a list of the form `[('a', 20), ('b', 30), ...]`.\n", + "If you could not, do not worry: you will receive the correct input automatically as an argument inside the function `solution_exercise4`.\n", "\n", "\n", - "Write a function that computes the *relative frequency* of each letter in the list `l` and returns a list of tuples `(letter, frequency)`.\n", + "Write a function that computes the *relative frequency* of each letter in that list and returns a list of tuples `(letter, frequency)`.\n", "\n", "\n", "
      \n", "

      Hints

      \n", - " The relative frequence of a value a in a list is simply the number of time it appears in that list over the total lenght of the list.\n", + " The relative frequence of a value a in a list is simply the number of times it appears in that list, over the total length of the list.\n", "
      " ] }, @@ -1751,14 +1751,14 @@ "source": [ "%%ipytest\n", "\n", - "def solution_exercise4(l: \"list[(str, int)]\") -> \"list[(str, float)]\":\n", + "def solution_exercise4(my_list: list[(str, int)]) -> list[(str, float)]:\n", " \"\"\"A function that computes the relative frequency of each letter from a given list of tuples.\n", " The relative frequency is calculated as the number of occurrences of a letter in the list, divided by the length of the list.\n", "\n", " Args:\n", - " l: the initial list of tuples\n", + " my_list: the initial list of tuples (letter, count) which counts how many words start with each letter\n", " Returns:\n", - " - the list of tuples with the relative frequency of each letter\n", + " - the list of tuples (letter, frequency) with the relative frequency of each letter\n", " \"\"\"\n", "\n", " return" @@ -1806,14 +1806,15 @@ "source": [ "%%ipytest\n", "\n", - "def solution_exercise5(words: \"list[str]\") -> \"list[str]\":\n", - " \"\"\"A function that returns a list of words from a given list of words, that are palindromes.\n", - " A palindrome is a word that reads the same backwards as forward.\n", + "def solution_exercise5(words: list[str]) -> list[str]:\n", + " \"\"\"A function that returns a list of words that are palindromes, from a given list of words.\n", + " A palindrome is a word that reads the same forward and backward.\n", + " A word is any string longer than 1 character.\n", "\n", " Args:\n", " words: the initial list of words\n", " Returns:\n", - " - the list of palindromes\n", + " - the list of palindrome words\n", " \"\"\"\n", "\n", " return" @@ -1837,7 +1838,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.10" + "version": "3.10.13" }, "vscode": { "interpreter": { diff --git a/tutorial/tests/test_11_functional_programming.py b/tutorial/tests/test_11_functional_programming.py index 7a813e6b..892d3c12 100644 --- a/tutorial/tests/test_11_functional_programming.py +++ b/tutorial/tests/test_11_functional_programming.py @@ -153,8 +153,8 @@ def test_multiples_of_n( # -def reference_exercise1(x: List[List[int]]) -> List[List[int]]: - return [list(i) for i in zip(*x)] +def reference_exercise1(matrix: List[List[int]]) -> List[List[int]]: + return [list(i) for i in zip(*matrix)] @pytest.mark.parametrize( @@ -205,10 +205,12 @@ def get_data_exercise3() -> List[str]: return words.splitlines() -def reference_exercise3(w: List[str]) -> List[Tuple[str, int]]: +def reference_exercise3(words: List[str]) -> List[Tuple[str, int]]: return [ (k, len(list(v))) - for k, v in itertools.groupby(sorted(w, key=lambda x: x[0]), key=lambda x: x[0]) + for k, v in itertools.groupby( + sorted(words, key=lambda x: x[0]), key=lambda x: x[0] + ) ] @@ -239,8 +241,8 @@ def test_exercise4( # -def reference_exercise5(my_list: List[str]) -> List[str]: - return list(filter(lambda x: x == x[::-1] and len(x) > 1, my_list)) +def reference_exercise5(words: List[str]) -> List[str]: + return list(filter(lambda x: x == x[::-1] and len(x) > 1, words)) def test_exercise5(function_to_test: Callable[[List[str]], List[str]]): From bbcfa058275d6586323dd5bafcdab4c2aae8478a Mon Sep 17 00:00:00 2001 From: Despina Adamopoulou Date: Wed, 7 May 2025 09:34:10 +0200 Subject: [PATCH 8/9] fix final typos --- 11_functional_programming.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/11_functional_programming.ipynb b/11_functional_programming.ipynb index e4bf67a5..d405998b 100644 --- a/11_functional_programming.ipynb +++ b/11_functional_programming.ipynb @@ -1327,10 +1327,10 @@ " Given a list of integers, write a function that only keeps the numbers that are multiples of a given constant k.\n", "
        \n", "
      • \n", - " Example 1: given nums = [1, 2, 3, 4, 5], and k = 2, the result must be [2, 4]\n", + " Example 1: given my_list = [1, 2, 3, 4, 5], and k = 2, the result must be [2, 4]\n", "
      • \n", "
      • \n", - " Example 2: given nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] and k = 5, the result is [5, 10]\n", + " Example 2: given my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] and k = 5, the result is [5, 10]\n", "
      • \n", "
      \n", "
      \n", @@ -1359,7 +1359,7 @@ "source": [ "%%ipytest\n", "\n", - "def solution_multiples_of_n(my_list: \"list[int]\", k: int) -> \"list[int]\":\n", + "def solution_multiples_of_n(my_list: list[int], k: int) -> list[int]:\n", " \"\"\"A function that keeps only the multiples of k from a given list.\n", "\n", " Args:\n", @@ -1704,7 +1704,7 @@ "\n", "def solution_exercise3(words: list[str]) -> list[(str, int)]:\n", " \"\"\"A function that counts the number of words from a given list that start with each letter of the alphabet, using sorted() and itertools.groupby.\n", - " The function should be case insensitive, using lower() to ensure consistet capitalization.\n", + " The function should be case insensitive, using lower() to ensure consistent capitalization.\n", " It should return a list of tuples, where each tuple contains a letter and the number of words that start with that letter.\n", " The list of tuples should be sorted in alphabetical order.\n", "\n", From 93b2fa829066fa111897647e0ecb91e0cf7ef3e2 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 7 May 2025 14:28:29 +0200 Subject: [PATCH 9/9] Final pass. --- 11_functional_programming.ipynb | 213 ++++++++++++-------------------- 1 file changed, 82 insertions(+), 131 deletions(-) diff --git a/11_functional_programming.ipynb b/11_functional_programming.ipynb index d405998b..ca2ac2d0 100644 --- a/11_functional_programming.ipynb +++ b/11_functional_programming.ipynb @@ -22,7 +22,7 @@ " - [Immutability](#Immutability)\n", " - [Composition](#Composition)\n", " - [Exercise on composition](#Exercise-on-composition)\n", - " - [Higher Order Functions / Functions as Values](#Higher-Order-Functions-/-Functions-as-Values)\n", + " - [Higher-Order Functions / Functions as Values](#Higher-Order-Functions-(HoF)-/-Functions-as-Values)\n", " - [Referential transparency](#Referential-transparency)\n", " - [Type systems](#Type-systems)\n", " - [Mapping / Iteration ](#Mapping-/-Iteration)\n", @@ -54,38 +54,52 @@ }, { "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "skip" - } - }, + "metadata": {}, "source": [ - "\n", - "---\n", - "\n", "# References\n", "Here are some additional references to help you in self-learning. Next to each link, we write if this is a video, text or another type of material\n", "\n", - "- The [functools reference](https://docs.python.org/3/library/functools.html) from the Python standard library (text)\n", - "- The [itertools reference](https://docs.python.org/3/library/itertools.html) from the Python standard library (text)\n", - "- [Functional programming howto](https://docs.python.org/3/howto/functional.html) from the Python documentation (text)\n", - "- A [good, but old introduction](https://www.youtube.com/watch?v=Ta1bAMOMFOI) of functional programming (video)\n", - "- A [very high level](https://www.youtube.com/watch?v=Qa8IfEeBJqk) introduction of functional programming (video, advanced). Interesting, but not Python-specific as it refers to Haskell\n", - "- A [general introduction on functional programming](https://www.youtube.com/watch?v=8z_bUIl_uPo). Very worth watching as it uses Python for the examples (video)\n", - "- [Principles of functional programming](https://dev.to/jamesrweb/principles-of-functional-programming-4b7c) (text)\n", - "\n", + "- The [functools reference](https://docs.python.org/3/library/functools.html) from the Python standard library (text).\n", + "- The [itertools reference](https://docs.python.org/3/library/itertools.html) from the Python standard library (text).\n", + "- [Functional programming howto](https://docs.python.org/3/howto/functional.html) from the Python documentation (text).\n", + "- A [good, but old introduction](https://www.youtube.com/watch?v=Ta1bAMOMFOI) of functional programming (video).\n", + "- A [very high level](https://www.youtube.com/watch?v=Qa8IfEeBJqk) introduction of functional programming (video, advanced).\n", + " Interesting, but not Python-specific as it refers to Haskell.\n", + "- A [general introduction on functional programming](https://www.youtube.com/watch?v=8z_bUIl_uPo).\n", + " Very worth watching as it uses Python for the examples (video).\n", + "- [Principles of functional programming](https://dev.to/jamesrweb/principles-of-functional-programming-4b7c) (text)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "# Introduction\n", "Functional programming is an approach to programming where programs are built by composing and running functions that perform a series of transformations on data.\n", "This contrasts with the approach of *imperative programming*, where programs are written as a series of statements which modify the *state* of the computation environment.\n", "Typically, within functional programming, great focus is placed on *composition*, *immutability* and *purity*.\n", - "We are going to define these terms in more detail later.\n", - "\n", + "We are going to define these terms in more detail later." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "# Why Functional Programming?\n", "There are a series of advantages to this approach, namely:\n", "- **Debugging** and **testing** are easy: there are no surprises because every function only does one thing and does not affect any other piece of the program.\n", "- **Parallelization** is trivial: functions are just small *boxes* that take one input and produce an output and do not depend on other parts of the code in an implicit manner through global variables or other shared pieces of state.\n", - " Thus, it's easy to make several functions run in parallel.\n", - "\n", + " Thus, it's easy to make several functions run in parallel." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ "# The basic principles of functional programming\n", "All modern programming languages have *functions* (or methods, procedures, subroutines, subprograms); these are groups of program statements that perform a certain computation.\n", "Functions are defined once for the whole program and can be reused at will throughout the program whenever we need to perform the specific computation they are defined for.\n", @@ -96,30 +110,20 @@ "- *Purity*\n", "- *Immutability of data*\n", "- *Composition*\n", - "- *Functions as values* (*higher order functions*)\n", + "- *Functions as values* (*higher-order functions*)\n", "- *Referential transparency*\n", "- *Type systems*\n", "\n", "In the next few sections, we are going to briefly explore these concepts with some examples." ] }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "notes" - }, - "tags": [] - }, - "source": [ - "## Pure Functions (Purity)" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In functional programming, we try to strive for *purity*. We want to define and use functions that only depend on their input, always return the same output for the same inputs and do not have any *side effects*, that is they do not indirectly affect any other part of our program.\n", + "## Pure Functions (Purity)\n", + "In functional programming, we try to strive for *purity*.\n", + "We want to define and use functions that only depend on their input, always return the same output for the same inputs and do not have any *side effects*, that is they do not indirectly affect any other part of our program.\n", "You can think of these functions as mathematical functions. \n", "Some examples of side effects are:\n", "\n", @@ -196,7 +200,6 @@ }, "outputs": [], "source": [ - "x = [\"short\", \"list\"] \n", "def do_something(y: str) -> None:\n", " x.append(y)" ] @@ -224,6 +227,7 @@ }, "outputs": [], "source": [ + "x = [\"short\", \"list\"]\n", "print(x)\n", "do_something(\"a\")\n", "print(x)" @@ -308,13 +312,8 @@ "tags": [] }, "source": [ - "## Immutability" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "## Immutability\n", + "\n", "Previously, we saw this example:" ] }, @@ -387,18 +386,8 @@ "tags": [] }, "source": [ - "## Composition" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "notes" - }, - "tags": [] - }, - "source": [ + "## Composition\n", + "\n", "Another important aspect of functional programming is `composition`.\n", "This means that whenever we want to perform multiple operations, we avoid writing intermediate variables, especially so when these are in the global state of the program.\n", "\n", @@ -412,8 +401,7 @@ "\n", "where we used $\\circ$ to express the composition of functions.\n", "\n", - "In Python, we can do this using a [`lambda` expression](https://docs.python.org/3/tutorial/controlflow.html?#lambda-expressions):\n", - "\n" + "In Python, we can do this using a [`lambda` expression](https://docs.python.org/3/tutorial/controlflow.html?#lambda-expressions):" ] }, { @@ -440,8 +428,8 @@ "In this way, we can break down a complex calculation in a series of simpler ones.\n", "This is useful for many things:\n", "\n", - "- It is easier to find problems in a smaller function\n", - "- We do our work only once: every time we need the same action, we just call the same function" + "- It is easier to find problems in a smaller function.\n", + "- We do our work only once: every time we need the same action, we just call the same function." ] }, { @@ -492,18 +480,8 @@ "tags": [] }, "source": [ - "## Higher Order Functions / Functions as Values" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "notes" - }, - "tags": [] - }, - "source": [ + "## Higher-Order Functions (HoF) / Functions as Values\n", + "\n", "Another important principle of functional programming is that **functions are values**.\n", "In programming languages that support a functional style (like Python), functions are treated as first-class objects: we can assign them to variables, pass them as arguments to other functions, and manipulate them just like any other value.\n", "As an example, consider the function `function_caller`:" @@ -589,7 +567,7 @@ "source": [ "This example was a bit convoluted, but it shows that in Python we can use functions as values and even pass them as arguments to other functions.\n", "This is useful in many cases; a typical example is numerical optimization, where we want to find the parameters of a function that minimize a certain criterion.\n", - "Other than these specific use cases, there are some common *higher-order functions*, or functions that take other functions as parameters, that are common in most programming languages.\n", + "Other than these specific use cases, there are some common *higher-order functions* (HoF), or functions that take other functions as parameters, that are common in most programming languages.\n", "We will look at a few of them in a following section." ] }, @@ -599,18 +577,8 @@ "tags": [] }, "source": [ - "## Referential transparency" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "notes" - }, - "tags": [] - }, - "source": [ + "## Referential transparency\n", + "\n", "Another principle of functional programming is called [**referential transparency**](https://en.wikipedia.org/wiki/Referential_transparency).\n", "This complex-sounding name simply means that we can replace any expression with its value without changing the behavior of the program.\n", "\n", @@ -694,15 +662,8 @@ "tags": [] }, "source": [ - "## Type systems" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "tags": [] - }, - "source": [ + "## Type systems\n", + "\n", "A typical trait of (modern) functional programming is the heavy use of the *type system*.\n", "This provides a set of rules to classify expressions and values in the language into classes called *types*.\n", "You encountered some basic types in the introduction, for example, `int`, `str` or `float`.\n", @@ -806,7 +767,7 @@ "metadata": {}, "source": [ "Because of the `isinstance` check in `__init_`, we aren't able to construct a `Person` if `age` is a string (type `str`).\n", - "Likewise, we cannot call `greet_better` with a value other than a `Person`.\n" + "Likewise, we cannot call `greet_better` with a value other than a `Person`." ] }, { @@ -836,26 +797,11 @@ "tags": [] }, "source": [ - "# Mapping / Iteration " - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "notes" - }, - "tags": [] - }, - "source": [ + "# Mapping / Iteration\n", + "\n", "In this section, we will look at the application of some of the principles stated above in Python.\n", - "An important application of functional programming in Python is the manipulation of iterables and lists." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "An important application of functional programming in Python is the manipulation of iterables and lists.\n", + "\n", "Python has the `map` built-in used to apply a given function to all the elements of any `iterable`.\n", "\n", "
      \n", @@ -894,7 +840,8 @@ } }, "source": [ - "The output is a bit confusing! The reason is that `map` does not return a list but a [map](https://docs.python.org/3/library/functions.html#map) object.\n", + "The output is a bit confusing!\n", + "The reason is that `map` does not return a list but a [map](https://docs.python.org/3/library/functions.html#map) object.\n", "This object is **lazy**, that means that the values are only generated when we access them, for example by iterating over the `map` object. \n", "Fortunately, we can easily convert this into a list by calling the `list` function:" ] @@ -920,7 +867,7 @@ } }, "source": [ - "This is equivalent to using a for loop:\n" + "This is equivalent to using a for loop:" ] }, { @@ -963,13 +910,12 @@ }, "source": [ "## Filtering\n", - "Another basic higher order function (HoF) is `filter`.\n", + "\n", + "Another basic higher-order function (HoF) is `filter`.\n", "As the name says, this function is used to filter an `iterable` using a *predicate function*.\n", "This is a function that takes a value and returns `true` or `false`.\n", "With `true`, the current element is kept, with `false` it is discarded.\n", - "We now try to write a predicate function that only keeps even numbers.\n", - "\n", - "" + "We now try to write a predicate function that only keeps even numbers." ] }, { @@ -1021,6 +967,7 @@ }, "source": [ "## Reducing\n", + "\n", "The third basic HoF is *reduction*.\n", "This is a function that takes a function `f(x, y)` of two arguments and an iterable `it` and applies the function to every element in the iterable *cumulatively* to produce one value.\n", "It works in the following way:\n", @@ -1031,7 +978,7 @@ "Because of this behaviour, this function is handy to compute sums or similar aggregations over a list.\n", "In Python, this function is available in the [`functools` module](https://docs.python.org/3/library/functools.html), part of Python's standard library.\n", "\n", - "As an example of using `reduce`, consider the following snippet:\n" + "As an example of using `reduce`, consider the following snippet:" ] }, { @@ -1119,7 +1066,8 @@ "source": [ "
      \n", "

      Question

      \n", - " Modify the function solution_add_one to add the integer number 1 to each element of the list input_data. For your solution, you cannot use explicit for loops.\n", + " Modify the function solution_add_one to add the integer number 1 to each element of the list input_data.\n", + " For your solution, you cannot use explicit for loops.\n", "
      " ] }, @@ -1153,8 +1101,9 @@ }, "source": [ "# List comprehensions\n", + "\n", "Many of the operations in the previous section can be performed in a different (some would say more *pythonic*) way using [list comprehensions](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions).\n", - "These look like mini for-loops inside square brackets and are very useful to write programs in a functional style while keeping code more readable than using higher order functions. \n", + "These look like mini for-loops inside square brackets and are very useful to write programs in a functional style while keeping code more readable than using higher order functions.\n", "\n", "For example, if we want to double all integers between 1 and 10 and store the result in a list, we can proceed in the classical imperative way:" ] @@ -1202,7 +1151,7 @@ "source": [ "doubles = map(lambda x: x * 2, range(10))\n", "\n", - "print(list(doubles))\n" + "print(list(doubles))" ] }, { @@ -1381,6 +1330,7 @@ }, "source": [ "# Combinining and splitting iterators\n", + "\n", "Sometimes, we need to iterate over multiple lists in a special fashion, for example over two lists in parallel or we want to compute all the combinations of elements of two lists etc.\n", "We can solve many ot these problems with the use of list compherensions, but sometimes there are more elegant solutions by using the [`itertools`](https://docs.python.org/3/library/itertools.html) module, a part of Python's standard library.\n", "\n", @@ -1618,7 +1568,7 @@ " - the transposed matrix\n", " \"\"\"\n", "\n", - " return\n" + " return" ] }, { @@ -1660,7 +1610,7 @@ " - the flattened list\n", " \"\"\"\n", "\n", - " return\n" + " return" ] }, { @@ -1673,7 +1623,8 @@ "source": [ "### Exercise 3: Counting initials\n", "\n", - "Given a list of English words, write a function that counts how many words begin with each letter of the English alphabet and returns the result as an **alphabetically sorted** list of tuples `(letter, count)`. We do not distinguish between uppercase and lowercase letters (\"A\" and \"a\" are considered the same). \n", + "Given a list of English words, write a function that counts how many words begin with each letter of the English alphabet and returns the result as an **alphabetically sorted** list of tuples `(letter, count)`.\n", + "We do not distinguish between uppercase and lowercase letters (\"A\" and \"a\" are considered the same). \n", "\n", "\n", "
      \n", @@ -1714,7 +1665,7 @@ " - the alphabetically sorted list of tuples\n", " \"\"\"\n", "\n", - " return\n" + " return" ] }, { @@ -1726,13 +1677,12 @@ }, "source": [ "### Exercise 4: Counting initials frequency\n", + "\n", "If you could solve the previous exercise, you now have a list of the form `[('a', 20), ('b', 30), ...]`.\n", "If you could not, do not worry: you will receive the correct input automatically as an argument inside the function `solution_exercise4`.\n", "\n", - "\n", "Write a function that computes the *relative frequency* of each letter in that list and returns a list of tuples `(letter, frequency)`.\n", "\n", - "\n", "
      \n", "

      Hints

      \n", " The relative frequence of a value a in a list is simply the number of times it appears in that list, over the total length of the list.\n", @@ -1773,6 +1723,7 @@ }, "source": [ "### Exercise 5: Finding palindromes\n", + "\n", "Consider again the `words` list of strings. Write a function that returns the list of all *palindromes*. A *palindrome* is a word (any string longer than 1) that reads the same left-to-right and right-to-left.\n", "\n", "For example:\n", @@ -1791,7 +1742,7 @@ " The words are available as the input to solution_exercise3.\n", "
    • \n", "
    \n", - "
    \n" + "
    " ] }, { @@ -1838,7 +1789,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.12.10" }, "vscode": { "interpreter": {