diff --git a/01-data-structures-complexity.ipynb b/01-data-structures-complexity.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..e1b8d697b1aecdfea66bc0e70aba06d5ce3b158d --- /dev/null +++ b/01-data-structures-complexity.ipynb @@ -0,0 +1,2203 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b50844ff", + "metadata": { + "slideshow": { + "slide_type": "slide" + }, + "tags": [ + "definition" + ] + }, + "source": [ + "# UE5 Fundamentals of Algorithms\n", + "## Lecture 1: Introduction\n", + "### Ecole Centrale de Lyon, Bachelor of Science in Data Science for Responsible Business\n", + "#### Romain Vuillemot\n", + "<center><img src=\"figures/Logo_ECL.png\" style=\"width:300px\"></center>" + ] + }, + { + "cell_type": "markdown", + "id": "2ada5ceb", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Outline\n", + "- Definition and examples of algorithms\n", + "- Algorithms properties\n", + "- Complexity analysis\n", + "- Data structures\n", + "- Empirical complexity analysis" + ] + }, + { + "cell_type": "markdown", + "id": "2f278159", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "f828e797", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## What is an algorithm?\n", + "\n", + "### Definition\n", + "\n", + "> An algorithm is a **set of unambiguous instructions** designed to solve a problem.\n", + "\n", + "\n", + "### History\n", + "\n", + "The earliest algorithms, originating from the name **Mūsā al-Khwārizmī**, a Persian mathematician from the 9th century. For more information, visit https://mathematical-tours.github.io/algorithms/.\n", + "\n", + "Back to ancient civilizations, such as the Egyptians and Babylonians, developed algorithms for **basic arithmetic operations**, like addition and multiplication. Euclid's algorithm, developed around 300 BCE, is **one of the earliest known algorithms** and is used to find the greatest common divisor (GCD) of two numbers.\n" + ] + }, + { + "cell_type": "markdown", + "id": "a3b03927", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Question\n", + "\n", + "- Are you aware of any algorithm?" + ] + }, + { + "cell_type": "markdown", + "id": "0e1b605e", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "- Do you know how they work?\n", + "- Do you think they work perfectly? \n", + "- Can they be biased or make non-optimal decisions?" + ] + }, + { + "cell_type": "markdown", + "id": "598f0585", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Notes\n", + "\n", + "- The representation (or sometimes translation) into a programming language is not reciprocal: **not every program is an algorithm.**\n", + "\n", + "- For example, reactive programs (handling input/output) or those containing animations do not terminate because they are always waiting for input. They do not constitute algorithms in the strict sense.\n", + "\n", + "- Algorithms are language-agnostic; they describe the logic and steps needed to solve a problem, but not the specific coding details." + ] + }, + { + "cell_type": "markdown", + "id": "13950423", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Example: Euclid's algorithm\n", + "\n", + "One of the earliest algorithm: Euclid's algorithm to compute the greatest common divisor of two integers a and b: " + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "1ab6e76d", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def gcd(a, b):\n", + " while b != 0:\n", + " t = b\n", + " b = a % b\n", + " a = t\n", + " return a\n", + "\n", + "gcd(10, 20) # 10" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "e594e658", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "assert gcd(12, 18) == 6 # GCD of 12 and 18 is 6\n", + "assert gcd(1071, 462) == 21 # GCD of 1071 and 462 is 21\n", + "assert gcd(0, 8) == 8 # GCD of 0 and 8 is 8\n", + "assert gcd(25, 0) == 25 # GCD of 25 and 0 is 25\n", + "assert gcd(-12, 18) == 6 # GCD of -12 and 18 is 6" + ] + }, + { + "cell_type": "markdown", + "id": "d06be14a", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### How do you check an algorithm is correct?\n", + "\n", + "- **Mathematical Proof:** a formal and rigorous method of demonstrating that an algorithm is correct.\n", + "- **Code Review:** a collaborative process where one or more peers review the code implementation of an algorithm.\n", + "- **Test Cases:** sets of inputs and expected outputs used to validate that an algorithm produces correct results.\n", + "\n", + "For **test cases:**, the ```assert``` statement is used to check whether a given condition evaluates to ```True```, then the program continues to execute normally. If the condition is ```False```, an ```AssertionError``` exception is raised, and the program stops executing.\n" + ] + }, + { + "cell_type": "markdown", + "id": "b4756712", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### How do you check an algorithm is correct? (cont.)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "c978e5c9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "All test cases passed!\n" + ] + } + ], + "source": [ + "def add(a, b): # function to test\n", + " return a + b\n", + "\n", + "assert add(2, 3) == 5, \"Test Case 1 Failed\" \n", + "assert add(-1, 1) == 0, \"Test Case 2 Failed\" \n", + "assert add(0, 0) == 0, \"Test Case 3 Failed\" \n", + "assert add(10, -5) == 5, \"Test Case 4 Failed\"\n", + "\n", + "print(\"All test cases passed!\")" + ] + }, + { + "cell_type": "markdown", + "id": "b3440a3c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: x power n\n", + "\n", + "An algorithm (and tests) that calculates $x^n$:" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "0eec5ad4", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def puissance(x, n):\n", + " if n == 0:\n", + " return 1\n", + " elif n % 2 == 0:\n", + " temp = puissance(x, n // 2)\n", + " return temp * temp\n", + " elif n < 0:\n", + " temp = puissance(x, -(n + 1) // 2)\n", + " return 1 / (temp * temp * x)\n", + " else:\n", + " temp = puissance(x, (n - 1) // 2)\n", + " return temp * temp * x" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "265c8dbf", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "assert puissance(2, 3) == 8\n", + "assert puissance(5, 0) == 1\n", + "assert puissance(3, -2) == 1/9\n", + "assert puissance(2, 10) == 1024\n", + "assert puissance(2, -3) == 1/8\n", + "assert puissance(2, 1) == 2" + ] + }, + { + "cell_type": "markdown", + "id": "42bd90e0", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: The sum of the first n integers\n", + "\n", + "An algorithm (and tests) that calculates $\\sum_{i=1}^{n} x_i$:" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "c97cd26b", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def sum_n(n):\n", + " return n*(n+1)/2\n", + "\n", + "assert sum_n(1) == 1 # 1\n", + "assert sum_n(2) == 3 # 1 + 2\n", + "assert sum_n(3) == 6 # 1 + 2 + 3\n", + "assert sum_n(4) == 10 # 1 + 2 + 3 + 4\n", + "assert sum_n(5) == 15 # 1 + 2 + 3 + 4 + 5\n", + "assert sum_n(1000) == 500500 # .." + ] + }, + { + "cell_type": "markdown", + "id": "f4b2a737", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: Leap year\n", + "\n", + "Write a function `is_leap_year` that takes a year as input and returns `True` if it's a leap year and `False`\n", + " otherwise. The function follows the rules for leap year determination:\n", + "\n", + "- A year that is divisible by 4 is a leap year.\n", + "- However, a year that is divisible by 100 is not a leap year, unless...\n", + "- The year is also divisible by 400, in which case it is a leap year.\n", + "\n", + "E.g 2000 is a leap year, 2020 is a leap year." + ] + }, + { + "cell_type": "markdown", + "id": "6086ec57", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: Leap year (cont.)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "8630973c", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2000 is a leap year.\n", + "2020 is a leap year.\n", + "2100 is not a leap year.\n", + "2400 is a leap year.\n" + ] + } + ], + "source": [ + "def is_leap_year(year):\n", + " if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):\n", + " return True\n", + " else:\n", + " return False\n", + "\n", + "test_years = [2020, 2100, 2400]\n", + "\n", + "for year in test_years:\n", + " if is_leap_year(year):\n", + " print(f\"{year} is a leap year.\")\n", + " else:\n", + " print(f\"{year} is not a leap year.\")" + ] + }, + { + "cell_type": "markdown", + "id": "eb56b548", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "Another possible test: compare to the Python [isLeap](https://github.com/python/cpython/blob/607f18c89456cdc9064e27f86a7505e011209757/Lib/calendar.py#L141) from the `calendar` module." + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "c3a28d0e", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "import calendar\n", + "\n", + "def is_leap_year(year):\n", + " return calendar.isleap(year)" + ] + }, + { + "cell_type": "markdown", + "id": "68fa27fe", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: Find a number in a list\n", + "\n", + "Given a list of integer, return a specific number provided as parameter" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "8b2ba103", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def search_element_in_list(element, list):\n", + "\n", + " for i in list:\n", + " if i == element:\n", + " return True\n", + " return False\n", + "\n", + "element_list = [1, 2, 3, 4, 5]\n", + "element_to_find = 3\n", + "result = search_element_in_list(element_to_find, element_list)\n", + "assert result == True, f\"Expected True, but got {result}\"" + ] + }, + { + "cell_type": "markdown", + "id": "6dbe39e2", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "Another type of test is to compare with a built-in Python function:" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "40147ce1", + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "def search_element_in_list_python(element, lst):\n", + " return element in lst\n", + "\n", + "assert search_element_in_list(element_to_find, element_list) == search_element_in_list(element_to_find, element_list)" + ] + }, + { + "cell_type": "markdown", + "id": "15b9f515", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Algorithms properties" + ] + }, + { + "cell_type": "markdown", + "id": "b78724b1", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Properties\n", + "\n", + "An algorithm possesses the following properties (among others):\n", + "\n", + "- Communicable\n", + "- Efficient\n", + "- Complete, terminates, and correct\n", + "- Deterministic" + ] + }, + { + "cell_type": "markdown", + "id": "8bccda63", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Communicate algorithms\n", + "\n", + "There are different ways to write algorithms. There is no optimal one, it depends on the context. Examples of contexts are:\n", + "\n", + "- Plain language (pseudo-code)\n", + "- Formalization such as an equation\n", + "- A software specification\n", + "- Implementation in a programming language" + ] + }, + { + "cell_type": "markdown", + "id": "60a6156f", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Plain language (pseudo-code)\n", + "\n", + "The pseudocode is a way to write algorithms in a human-readable way. It is not a programming language, but it is close to it. It is a way to communicate algorithms. E.g. for Euclid's algorithm:\n", + "\n", + "- Divide a by b, and you get the remainder r.\n", + "- Replace a with b.\n", + "- Replace b with r.\n", + "- Continue as long as it's possible; otherwise, you get the GCD (Greatest Common Divisor).\n", + "\n", + "or\n", + " \n", + "```\n", + "function gcd(a, b)\n", + " while b ≠ 0\n", + " t := b; \n", + " b := a mod b; \n", + " a := t; \n", + " return a;\n", + " ````" + ] + }, + { + "cell_type": "markdown", + "id": "9d16ff11", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Equation\n", + "\n", + "You can use mathematical equations and notations to describe certain aspects of the algorithm's behavior or to express mathematical relationships within the algorithm. \n", + "\n", + "- $\\sum_{i=1}^{n} x_i$\n", + "\n", + "- $Fn = Fn-1 + Fn-2$\n", + "\n", + "- μ = (Σx) / N\n", + "\n", + "- $PR_{t+1}(P_i) = \\sum_{P_j} \\frac{PR_t(P_j)}{C(P_j)}$" + ] + }, + { + "cell_type": "markdown", + "id": "b2f99051", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Graphics\n", + "\n", + "Graphical representations of algorithms are visual ways to illustrate the flow, logic, and structure of an algorithm. They are often used to aid in understanding, designing, and communicating algorithms, especially in algorithm design and computer science education. There are various types of graphical representations, and the choice depends on the complexity and purpose of the algorithm. \n", + "\n", + "<img src=\"figures/flowchart.png\" width=150></img>\n", + "\n", + "source: https://commons.wikimedia.org/wiki/File:Euclid_flowchart.svg\n" + ] + }, + { + "cell_type": "markdown", + "id": "861e2f0b", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Code (Python)\n", + "\n", + "Code (Python, Java, ..); example in Python:\n", + "\n", + "```python\n", + "def gcd(a, b):\n", + " while b != 0:\n", + " t = b\n", + " b = a % b\n", + " a = t\n", + " return a\n", + "```\n", + "\n", + "In Java:\n", + "\n", + "```java\n", + "public class GCD {\n", + " public static int gcd(int a, int b) {\n", + " while (b != 0) {\n", + " int t = b;\n", + " b = a % b;\n", + " a = t;\n", + " }\n", + " return a;\n", + " }\n", + "}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "55662526", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Discussion on the type of representation\n", + "\n", + "There are different ways to express an algorithm, depending on the context and the level of formalization required.\n", + "\n", + "- **Graphical representation** is more accessible and provides an overview, allowing for the detection of errors, patterns, etc. Humans have better perception abilities in the visual space than in text.\n", + "\n", + "- **Pseudo-language** has the characteristic of being flexible, close to both human and computer languages, and independent of a programming language. However, it is often defined ambiguously and requires additional effort for implementation.\n", + "\n", + "- Finally, **implementation (e.g., Python)** has the advantage of being immediately testable. However, it can be very strict (must be correct) and sometimes challenging to read if one is not familiar with the language. This also depends on the programmer." + ] + }, + { + "cell_type": "markdown", + "id": "39e29af0", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Efficiency\n", + "\n", + "> An algorithm is considered **efficient** if it minimizes the consumption of resources required to perform it.\n", + "\n", + "\n", + "Efficiency is relative to various criteria (values we want to measure) that need to be calculated (theoretically) or measured (empirically) in order to understand what is happening. Note that it is necessary to use large values of $n$ to obtain a representative behavior. Among these criteria:\n", + "\n", + "- Execution time\n", + "\n", + "- Required memory space\n", + "\n", + "- Disk storage space\n", + "\n", + "- Etc.\n", + "\n", + "We will see later that the concept of **Complexity** is based on one of these criteria and allows independence from the technology used (language, computer, compiler, etc.).\n" + ] + }, + { + "cell_type": "markdown", + "id": "a9849a9c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Example:\n", + "\n", + "In genomics, it is common to compare two sequences (of genes) of lengths $N$ and $M$ (e.g., $\\texttt{TAG CAC}$ and $\\texttt{TGC TTG}$).\n", + "\n", + "- The number of comparisons is $N \\times M$.\n", + "\n", + "- If the size of the sequences doubles, then the number of comparisons... quadruples!\n", + "\n", + "- $(2 \\times N) \\times (2 \\times M) = 4 \\times (N \\times M)$.\n", + "\n", + "- Now, if we want to align 3 sequences, it becomes $N^{3}$.\n", + "\n", + "In practice, it becomes challenging to find a solution quickly (especially when comparing more than 2 sequences).\n", + "\n", + "\n", + "$\\rightarrow$ The same applies to long sequences.\n", + "\n", + "$\\rightarrow$ Therefore, it is necessary to have an efficient algorithm (in the case of sequence comparison, consider the [BLAST algorithm](https://en.wikipedia.org/wiki/BLAST) (Basic Local Alignment Search Tool)).\n" + ] + }, + { + "cell_type": "markdown", + "id": "b48e5cc5", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Other properties\n", + "\n", + "Other qualities of an algorithm (beyond being simple and understandable):\n", + "\n", + "---\n", + "\n", + "> **Completeness**: An algorithm must be complete, meaning that for a given problem, it provides a solution for each of the inputs.\n", + "\n", + "---\n", + "\n", + "> **Termination**: An algorithm must terminate within a finite time.\n", + "\n", + "---\n", + "\n", + "> **Correctness**: An algorithm must be correct and terminate by providing a result that is the solution to the problem it is supposed to solve.\n", + "\n", + "---\n", + "\n", + "$\\rightarrow$ All of this is very difficult to prove (formal proof, etc.)!\n" + ] + }, + { + "cell_type": "markdown", + "id": "0af6998c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Algorithms patterns\n", + "\n", + "An algorithm has a **pattern**, which is a way to classify algorithms based on their properties.\n", + "\n", + "- There are several ways to design algorithms, either based on performance constraints or based on the structural style.\n", + "\n", + "- There is not a single unique algorithm for a given problem.\n", + "\n", + "Examples of patterns (main ones):\n", + "\n", + "- By purpose\n", + "- By implementation (e.g., **recursion**, functional, etc.)\n", + "- By **design paradigm** (Divide and Conquer, etc.)\n", + "- By **complexity**\n" + ] + }, + { + "cell_type": "markdown", + "id": "3d5ba2a2", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Complexity" + ] + }, + { + "cell_type": "markdown", + "id": "e260b392", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### What is complexity?\n", + "\n", + "> The **complexity of an algorithm** is the formal estimation of the amount of resources required to execute an algorithm. These resources can include time, memory space, storage, etc. \n", + "\n", + "There are different types of complexity:\n", + "\n", + "- **Best Case:** The _smallest_ number of operations the algorithm will have to execute on a dataset of a fixed size.\n", + "\n", + "- **Worst Case:** This is the _largest_ number of operations the algorithm will have to execute on a dataset of a fixed size.\n", + "\n", + "- **Average Case:** This is the _average_ of the algorithm's complexities on datasets of a fixed size.\n", + "\n", + "\n", + "Note: It is often the worst-case analysis that is chosen (provides an upper performance limit). The complexity in terms of the number of operations is typically the most studied.\n" + ] + }, + { + "cell_type": "markdown", + "id": "41ff1b6e", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "<img src=\"figures/big-o-chart.png\" width=75%>" + ] + }, + { + "cell_type": "markdown", + "id": "37fee118", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: find the complexity\n", + "\n", + "```python\n", + " def maximum(L):\n", + " m=L[0]\n", + " for i in range(1,len(L)):\n", + " if L[i]>m:\n", + " m=L[i]\n", + " return m\n", + "````\n" + ] + }, + { + "cell_type": "markdown", + "id": "7420338d", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + " $\\mathcal{O}(n)$\n", + " \n", + " (goes through the whole list in the worst case scenario)" + ] + }, + { + "cell_type": "markdown", + "id": "1c68b012", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Intuition behind the complexity calculation\n", + "\n", + "| Notation | Complexity | Intuition |\n", + "| -------------------- | ---------------- | ------------------------------------------------ |\n", + "| $\\mathcal{O} 1$ | Constant | First or nth element of a list, ... |\n", + "| $\\mathcal{O} log n$ | Logarithmic | Divide in half and repeat, ... |\n", + "| $\\mathcal{O} n$ | Linear | Traverse data, ... |\n", + "| $\\mathcal{O} nlog n$ | Quasi-Linear | Divide in half and combine, ... |\n", + "| $\\mathcal{O}n^{2}$ | Quadratic | Traverse data with 2 loops, ... |\n", + "| $\\mathcal{O}2^{n}$ | Exponential | Test all combinations, ... |\n", + "| $\\mathcal{O}n^k$, k >2 | Polynomial | Traverse data with k loops, ... |\n", + "| $\\mathcal{O}n!$ | Factorial | Test all paths (graph), ... |" + ] + }, + { + "cell_type": "markdown", + "id": "354c08c0", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: find the complexity" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "65be2326", + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "def nocc(x,L):\n", + " n=0\n", + " for y in L:\n", + " if x==y:\n", + " n=n+1\n", + " return n" + ] + }, + { + "cell_type": "markdown", + "id": "c2853423", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "$\\mathcal{O}(n)$\n", + " \n", + "(goes through the whole list in the worst case scenario)" + ] + }, + { + "cell_type": "markdown", + "id": "a667f037", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: find the complexity" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "41fee19f", + "metadata": {}, + "outputs": [], + "source": [ + "def maj(L):\n", + " xmaj=L[0]\n", + " nmaj=nocc(xmaj,L)\n", + " for i in range(1,len(L)):\n", + " if nocc(L[i],L)>nmaj:\n", + " xmaj=L[i]\n", + " nmaj=nocc(L[i],L)\n", + " return xmaj" + ] + }, + { + "cell_type": "markdown", + "id": "ad350594", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "$\\mathcal{O}(n^{2})$" + ] + }, + { + "cell_type": "markdown", + "id": "20906f5f", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: find the complexity\n", + "\n", + "The complexity of an `is_even(n)`algorithm that takes an integer `n` as input and returns `True` if n is an even number and `False`` otherwise." + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "id": "f6306bf2", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def is_even(n):\n", + " return n % 2 == 0" + ] + }, + { + "cell_type": "markdown", + "id": "41b3bb3c", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "$\\mathcal{O}(1)$" + ] + }, + { + "cell_type": "markdown", + "id": "872695b1", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: find the complexity" + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "id": "206fae84", + "metadata": {}, + "outputs": [], + "source": [ + "def somcubes(n):\n", + " s = 0\n", + " while n>0:\n", + " s = s+(n%10)**3\n", + " n = n//10\n", + " return s\n", + "\n", + "\n", + "def eq_somcubes(N):\n", + " L = []\n", + " for n in range(0, N+1):\n", + " if n==somcubes(n):\n", + " L.append(n)\n", + " return L" + ] + }, + { + "cell_type": "markdown", + "id": "83fa5d5d", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "$\\mathcal{O}(nlog(n))$ (we seek numbers that are equal to the sum of the cubes of their digits)." + ] + }, + { + "cell_type": "markdown", + "id": "6a617a3b", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: find the complexity\n", + "\n", + "You have two sorted lists, `[1, 3, 8, 10]` and `[2, 3, 9]``, and you want to obtain a new merged list from these two lists (without using sorting functions like sort or sorted). What is the complexity?" + ] + }, + { + "cell_type": "markdown", + "id": "01f5db81", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "We iterate through all the data once: $O(n)$." + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "id": "ae749b90", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 2, 3, 3, 8, 9, 10]\n" + ] + } + ], + "source": [ + "def merge_sorted_lists(list1, list2):\n", + " merged_list = []\n", + " i = j = 0\n", + "\n", + " while i < len(list1) and j < len(list2):\n", + " if list1[i] < list2[j]:\n", + " merged_list.append(list1[i])\n", + " i += 1\n", + " else:\n", + " merged_list.append(list2[j])\n", + " j += 1\n", + "\n", + " while i < len(list1):\n", + " merged_list.append(list1[i])\n", + " i += 1\n", + "\n", + " while j < len(list2):\n", + " merged_list.append(list2[j])\n", + " j += 1\n", + "\n", + " return merged_list\n", + "\n", + "# Example usage:\n", + "list1 = [1, 3, 8, 10]\n", + "list2 = [2, 3, 9]\n", + "result = merge_sorted_lists(list1, list2)\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "id": "85357f95", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Example: Selection sort\n", + "\n", + "Implement the selection sort which is described as pseudo-code below:\n", + " \n", + "- Start with an unsorted list of elements.\n", + "- Find the smallest element in the unsorted portion of the list.\n", + "- Swap this smallest element with the first element in the unsorted portion.\n", + "- Now, consider the remaining unsorted portion (excluding the element that was just swapped).\n", + "- Repeat steps 2 to 4 until the entire list is sorted.\n", + "- The result is a sorted list in ascending order.\n", + "- The key idea is to repeatedly select the smallest element from the unsorted part of the list and move it to the beginning of the sorted part of the list. This process continues until the entire list is sorted.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "ef34203d", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Example: Selection sort (cont.)" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "id": "311b2a95", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[17, 20, 26, 31, 44, 54, 55, 77, 93]\n" + ] + } + ], + "source": [ + "def selectionSort(l):\n", + " for i in range(0, len(l)):\n", + " min = i\n", + " for j in range(i+1, len(l)):\n", + " if(l[j] < l[min]):\n", + " min = j\n", + " tmp = l[i]\n", + " l[i] = l[min]\n", + " l[min] = tmp\n", + " return l \n", + "\n", + "if __name__==\"__main__\": \n", + " liste = [54,26,93,17,77,31,44,55,20]\n", + " selectionSort(liste)\n", + " print(liste) # [17, 20, 26, 31, 44, 54, 55, 77, 93]\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "75b4a494", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "Complexity is on the order of $\\mathcal{O}(n^{2})$." + ] + }, + { + "cell_type": "markdown", + "id": "c1afcc1d", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Complexity Calculation\n", + "\n", + "There isn't just one but several methods to calculate the complexity of an algorithm, depending on its properties (and the desired precision of the complexity). Here are the main approaches:\n", + "\n", + "- **Reduction of the code to a known case** and combination of complexities. For example, two loops ($O(\\log N)$) result in an overall complexity of $O(n^{2} \\log(n))$.\n", + "\n", + "- **Reduction to a family of known functions** and calculation of the relative growth rate (limit).\n", + "\n", + "- **Empirical calculation by displaying execution times** as a function of the problem size. It's worth noting that this is independent of the power of the machine.\n" + ] + }, + { + "cell_type": "markdown", + "id": "204152e1", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Data structures\n" + ] + }, + { + "cell_type": "markdown", + "id": "282ed691", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Standard data structures\n", + "\n", + "Included in Python ([documentation](https://docs.python.org/3/tutorial/datastructures.html))\n", + "\n", + "\n", + "- `int`: Integer, typically 4 bytes in size.\n", + "- `long`: Long integer, can be 4 or 8 bytes in size.\n", + "- `float`: Real number.\n", + "- `str`: String, a sequence of characters (with Unicode conversion).\n", + "- `bool`: Boolean, representing True or False.\n", + "- `tuple`: Tuple, an ordered collection of elements, e.g., `(1, 2, \"ECL\", 3.14)`.\n", + "- `list`: List, an ordered and mutable collection of elements.\n", + "- `set`: Set, an unordered collection of unique elements.\n", + "- `dict`: Dictionary, a collection of key-value pairs, e.g., `{'small': 1, 'large': 2}`.\n", + "\n", + "You can check the data type of a variable or object \n", + "\n", + "```python\n", + "print(int)\n", + "print(type(int))\n", + "assert isinstance(3, int)\n", + "```\n" + ] + }, + { + "cell_type": "markdown", + "id": "b0dfbe40", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Standard data structures (cont.)\n", + "\n", + "- `range`: A range, representing a sequence of values to generate (in Python 2, `xrange()`).\n", + "- `complex`: Complex number, e.g., `1j` is one of the square roots of -1.\n", + "- `file`: File, for handling file input/output.\n", + "- `None`: Represents the absence of a value (equivalent to `void` in some contexts).\n", + "- `exception`: Exception, for handling errors and exceptional conditions.\n", + "- `function`: Function, a reusable block of code.\n", + "- `module`: Module, a file containing Python code and definitions.\n", + "- `object`: Object, a generic data type representing any Python object.\n" + ] + }, + { + "cell_type": "markdown", + "id": "01b8a90c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Advanced data structures\n", + "\n", + "Not included in Python, often achieved using standard structure and object-oriented programming:\n", + "\n", + "- **Linked Lists**: A data structure where elements are linked together with pointers, allowing for efficient insertions and deletions but not direct access to elements by index.\n", + "\n", + "- **Stacks**: A linear data structure that follows the Last-In-First-Out (LIFO) principle, commonly used for managing function calls, undo operations, and parsing expressions.\n", + "\n", + "- **Queues**: A linear data structure that follows the First-In-First-Out (FIFO) principle, used for tasks such as managing tasks in a print queue or breadth-first search in graphs.\n", + "\n", + "- **Priority Queue**: A data structure that stores elements with associated priorities and allows for efficient retrieval of the element with the highest (or lowest) priority." + ] + }, + { + "cell_type": "markdown", + "id": "ce212ed1", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Advanced data structures (cont.)\n", + "- **Heaps**: A specialized tree-based data structure that is often used to implement priority queues. It ensures that the highest (or lowest) priority element can be efficiently accessed.\n", + "\n", + "- **Deques (Double-Ended Queues)**: A linear data structure that allows elements to be added or removed from both ends with constant-time complexity, useful for certain algorithms and data management.\n", + "\n", + "- **Trees**: A hierarchical data structure with a root node and child nodes, commonly used for various purposes such as binary search trees, AVL trees, and decision trees.\n", + "\n", + "- **Graphs**: A non-linear data structure consisting of nodes and edges, used for modeling relationships between objects or entities. Python provides libraries like NetworkX for graph manipulation.\n", + "\n", + "- **Hash Tables (Dictionaries)**: A data structure that allows efficient key-value mapping and retrieval. Python's built-in `dict` type is an example.\n" + ] + }, + { + "cell_type": "markdown", + "id": "8982a34a", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Data structures complexity\n", + "\n", + "\n", + "- **List:** Lists in Python offer dynamic resizing and allow for constant-time access to elements by index. However, they may have linear time complexity for operations like insertion or deletion in the middle of the list due to shifting elements.\n", + "\n", + "- **Dictionary:** Python dictionaries, implemented as hash tables, provide constant-time average-case complexity for key-based operations such as insertion, retrieval, and deletion. However, the worst-case scenario can lead to linear time complexity.\n", + "\n", + "- **Set:** Sets in Python have efficient average-case time complexity for set operations like union, intersection, and difference, which is often close to constant time. However, in rare cases, these operations may exhibit linear time complexity.\n", + "\n", + "Understanding the complexities of these built-in data structures is essential for selecting the right one for specific programming tasks and optimizing the performance of Python programs." + ] + }, + { + "cell_type": "markdown", + "id": "87538ea2", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Dictionnaries\n", + "\n", + "\n", + "A **dictionary** in Python is an unordered collection of key-value pairs. It is a versatile data structure that allows you to store and retrieve values based on unique keys. Unlike lists or arrays, which use integer indices, dictionaries use keys to access their elements.\n", + "\n", + "- **Keys** in a dictionary must be unique and immutable, meaning you can use strings, numbers, or tuples as keys, but not lists or other dictionaries.\n", + "- **Values** can be of any data type, including strings, numbers, lists, other dictionaries, or even functions.\n", + "\n", + "Dictionaries are useful for a wide range of applications, such as:\n", + "\n", + "- Storing and retrieving configuration settings.\n", + "- Counting the frequency of elements in a dataset.\n", + "- Representing data in a structured way, such as JSON.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "9036ba27", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Example: Creating a Dictionary in Python\n", + "\n", + "\n", + "```python\n", + ">>> phonebook = {'bob': 7387, 'alice': 3719, 'jack': 7052}\n", + ">>> phonebook['alice']\n", + "3719\n", + "```\n", + "\n", + "- Implemented as a Python dictionary.\n", + "- Raises a `KeyError: 'missing'` exception if accessing an undefined key.\n", + "- A good practice is to use `.get(\"attr\", \"\")` to return a default value if the key doesn't exist.\n", + "- We will see that they are widely used for memoization to avoid recomputing certain calculations (e.g., dynamic programming).\n" + ] + }, + { + "cell_type": "markdown", + "id": "192c9168", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Example: Creating a Dictionary in Python\n", + "\n", + "Here's an example of how to create a dictionary in Python:\n", + "\n", + "```python\n", + "# Create a dictionary to store information about a person\n", + "person = {\n", + " \"name\": \"John Doe\",\n", + " \"age\": 30,\n", + " \"city\": \"New York\"\n", + "}\n", + "\n", + "# Access values using keys\n", + "print(\"Name:\", person[\"name\"])\n", + "print(\"Age:\", person[\"age\"])\n", + "print(\"City:\", person[\"city\"])\n", + "```\n", + "\n", + "In this example, we've created a dictionary named `person` that contains information about an individual. We access the values stored in the dictionary using their respective keys.\n", + "\n", + "Output:\n", + "```\n", + "Name: John Doe\n", + "Age: 30\n", + "City: New York\n", + "```\n", + "." + ] + }, + { + "cell_type": "markdown", + "id": "3cc3715e", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Question: Count words in a list (using a dictionnary)\n", + "\n", + "Write an algorithm that takes two parameters:\n", + "- `stri`: A list of words.\n", + "- `n`: An integer.\n", + "\n", + "And returns how many words in the list appear exactly `n` times, and return that count.\n" + ] + }, + { + "cell_type": "markdown", + "id": "db5cc56c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Question: Count words in a list (using a dictionnary)" + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "id": "d5b559aa", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n" + ] + } + ], + "source": [ + "def countWords(stri, n): \n", + " \n", + " m = dict() \n", + " for w in stri: # m {'hate': 2, 'love': 4, 'peace': 4}\n", + " m[w] = m.get(w, 0) + 1\n", + "\n", + " res = 0\n", + " for i in m.values(): \n", + " if i == n: \n", + " res += 1\n", + "\n", + " return res \n", + "\n", + "if __name__==\"__main__\": \n", + " # Driver code \n", + " s = [ \"hate\", \"love\", \"peace\", \"love\", \n", + " \"peace\", \"hate\", \"love\", \"peace\", \"love\", \"peace\" ] \n", + "\n", + " print(countWords(s, 4)) # 2" + ] + }, + { + "cell_type": "markdown", + "id": "35a76856", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: detect duplicates from a list (using dicts)\n", + "\n", + "Write an algorithm validates the following:\n", + "\n", + "```python\n", + "assert duplicatas([1,2]) == False\n", + "assert duplicatas([1,2,1]) == True\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "67336f28", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def duplicatas(L):\n", + " d = {}\n", + " for x in L:\n", + " if x in d:\n", + " return True\n", + " d[x] = True\n", + " return False\n", + "\n", + "assert duplicatas([1,2]) == False\n", + "assert duplicatas([1,2,1]) == True" + ] + }, + { + "cell_type": "markdown", + "id": "3fb60306", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: algorithm optimization (using dicts)\n", + "\n", + "Optimize this algorithm all integers such that $A^2 + B^2 = C^2 + D^2$ with A, B, C, D ranging from 1 to 1000.\n", + "\n", + "```python\n", + "n = 1000\n", + "for a in range(1, n+1):\n", + " for b in range(1, n+1):\n", + " for c in range(1, n+1):\n", + " for d in range(1, n+1):\n", + " if a**2 + b**2 == c**2 + d**2:\n", + " print(a, b, c, d)\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "247972d9", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: algorithm optimization (using dicts) (cont.)\n", + "\n", + "```python\n", + "n = 1000\n", + "result_map = {}\n", + "\n", + "for c in range(1, n+1):\n", + " for d in range(1, n+1):\n", + " result = c**2 + d**2\n", + " if result in result_map:\n", + " result_map[result].append((c, d))\n", + " else:\n", + " result_map[result] = [(c, d)]\n", + "\n", + "for a in range(1, n+1):\n", + " for b in range(1, n+1):\n", + " result = a**2 + b**2\n", + " if result in result_map:\n", + " matching_pairs = result_map[result]\n", + " for pair in matching_pairs:\n", + " print(a, b, pair)\n", + "\n", + "```\n", + "\n", + "- A first loop uses a dictionary `result_map` to store pairs $(c, d)$ that yield the same result $c^2 + d^2$.\n", + "- A second loop iterates through $a^2 + b^2$ values and checks if there are matching pairs in `result_map`.\n" + ] + }, + { + "cell_type": "markdown", + "id": "c82d4d90", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Sets\n", + "\n", + "A **set** in Python is an unordered collection of unique elements. It is similar to a mathematical set and has several important characteristics:\n", + "\n", + "1. **Uniqueness**: Sets do not allow duplicate elements. If you try to add a duplicate element to a set, it will be ignored.\n", + "\n", + "2. **Unordered**: Unlike lists or tuples, sets do not have a specific order. The elements are not stored in any particular sequence, and you cannot access them by index.\n", + "\n", + "3. **Mutable**: Sets are mutable, which means you can add or remove elements after creating a set.\n", + "\n", + "4. **No Indexing**: Since sets are unordered, you cannot access elements by their index. Instead, you typically perform operations on sets as a whole.\n", + "\n", + "5. **Common Set Operations**: Sets support various set operations such as union, intersection, difference, and more, making them useful for mathematical and data manipulation tasks.\n" + ] + }, + { + "cell_type": "markdown", + "id": "bca7c805", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Sets (cont.)\n", + "\n", + "```python\n", + "# Creating a set\n", + "my_set = {1, 2, 3, 4, 5}\n", + "\n", + "# Creating an empty set\n", + "empty_set = set()\n", + "```\n", + "\n", + "Common set operations include:\n", + "\n", + "- **Adding Elements**: You can add elements to a set using the `add()` method.\n", + "\n", + "- **Removing Elements**: Elements can be removed from a set using the `remove()` or `discard()` method.\n", + "\n", + "- **Set Operations**: You can perform operations like union (`|`), intersection (`&`), difference (`-`), and more between sets.\n", + "\n", + "- **Checking Membership**: You can check if an element is in a set using the `in` operator.\n", + "\n", + "- **Iterating**: You can iterate through the elements of a set using a `for` loop.\n", + "\n", + "Sets are commonly used for tasks where uniqueness and set operations are essential." + ] + }, + { + "cell_type": "markdown", + "id": "cf4fcdf0", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Set Operations in Python\n", + "\n", + "| Method | Description |\n", + "|-----------------------|--------------------------------|\n", + "| `add()` | Adds an element to the set. |\n", + "| `clear()` | Removes all elements from the set. |\n", + "| `copy()` | Returns a copy of the set. |\n", + "| `difference()` | Returns the difference of two sets. |\n", + "| `intersection()` | Returns the intersection of two sets. |\n", + "| `pop()` | Removes and returns a random element from the set. |\n", + "| `union()` | Returns the union of two sets. |\n", + "| `isdisjoint()` | Returns `True` if the sets have no elements in common. |\n", + "| `issubset()` | Returns `True` if the set is a subset of another set. |\n", + "| `issuperset()` | Returns `True` if the set contains another set. |\n", + "\n", + "There are many other set operations available in Python, and `frozenset` can be used to create an immutable set.\n", + "\n", + "For more details, refer to the [Python documentation](https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset).\n" + ] + }, + { + "cell_type": "markdown", + "id": "e3fce712", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: remove duplicatas from a list (using sets)\n", + "\n", + "Write an algorithm validates the following:\n", + "\n", + "```python\n", + "assert duplicatas_sets([1,2]) == False\n", + "assert duplicatas_sets([1,2,1]) == True\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 135, + "id": "19e2c4ab", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def duplicatas_sets(L):\n", + "\ts = set()\n", + "\tfor x in L:\n", + "\t\tif x in s:\n", + "\t\t\treturn True\n", + "\t\ts.add(x)\n", + "\treturn False" + ] + }, + { + "cell_type": "code", + "execution_count": 141, + "id": "aec4421f", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def duplicatas_sets2(nums):\n", + " return True if len(set(nums)) < len(nums) else False\n", + " \n", + "assert duplicatas_sets2([1,2]) == False\n", + "assert duplicatas_sets2([1,2,1]) == True" + ] + }, + { + "cell_type": "markdown", + "id": "61b23935", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: find pairs duplicates (using sets) \n", + "\n", + "In a list, return the values that occure exactly 2 times." + ] + }, + { + "cell_type": "code", + "execution_count": 137, + "id": "632fd6b4", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[(2, 2), (3, 3), (5, 5)]\n" + ] + } + ], + "source": [ + "def find_duplicate_pairs_optimized(lst):\n", + " seen = set()\n", + " duplicate_pairs = []\n", + "\n", + " for num in lst:\n", + " if num in seen:\n", + " duplicate_pairs.append((num, num))\n", + " seen.add(num)\n", + "\n", + " return duplicate_pairs\n", + "\n", + "# Example usage:\n", + "input_list = [2, 3, 5, 2, 7, 3, 8, 5]\n", + "result = find_duplicate_pairs_optimized(input_list)\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "id": "7c958cd9", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercice: find words typed with a single row on a keyboard (using sets)\n", + "\n", + "You can determine words that can be typed with a single row of letters on a keyboard using sets in Python.\n", + "\n", + "```python\n", + "words = ['Velo', 'Ecole', 'Informatique', 'Etroit']\n", + "check_keyboard(words) == ['Etroit'] # for a French keyboard\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 151, + "id": "e3686496", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['Etroit']\n" + ] + } + ], + "source": [ + "def check_keyboard(words):\n", + " result = []\n", + " for w in words:\n", + " ws = set([c.lower() for c in w])\n", + " if not ws.difference(\"azertyuiop\") \\\n", + " or not ws.difference(\"qsdfghjklm\") \\\n", + " or not ws.difference(\"wxcvbn\"):\n", + " result.append(w)\n", + " return result\n", + "\n", + "typed_with_single_row = solution(words)\n", + "print(typed_with_single_row)" + ] + }, + { + "cell_type": "markdown", + "id": "8865e8a6", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "<img src=\"figures/complexite-arrays.png\" width=75%>" + ] + }, + { + "cell_type": "markdown", + "id": "995c354c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "<img src=\"figures/complexite-data-structures.png\" width=75%>" + ] + }, + { + "cell_type": "markdown", + "id": "b6e68dc5", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Empirical complexity analysis" + ] + }, + { + "cell_type": "markdown", + "id": "0a04e8e2", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Empirical complexity analysis\n", + "\n", + "A practical way to estimate complexity\n", + "\n", + "1. **Gather data** on the execution time of algorithms or operations for various input sizes. This data is typically collected through various random measurements.\n", + "\n", + "2. **Plot the time measures** for the various measurements, for each algorithm to assess performance scales.\n", + "\n", + "3. **Analyzing trends** to draw conclusions about the algorithm's time complexity by observing curves in the plotted data.\n", + "\n", + "Using the matplotlib library (to be imported as a module):" + ] + }, + { + "cell_type": "code", + "execution_count": 146, + "id": "273e0647", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "id": "80c00461", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Example: constant time" + ] + }, + { + "cell_type": "code", + "execution_count": 145, + "id": "76f6f476", + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[<matplotlib.lines.Line2D at 0x11774cd30>]" + ] + }, + "execution_count": 145, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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rtnYRY8++qjGjRun2267TREREbrnnns0ffp0SVJYWJgkqampST179tQLL7yg1NRUTZ48WY888kjAy06fl5OTo9raWv+toqLiSh4KAADoAIIKlri4OIWFhTW7mlJdXd3sqstFUVFRysvL07lz53Tq1CmVl5erX79+io6OVlxcnCSpV69e+trXvuYPGOmz98VUVVXp/Pnzlzyv1+tVTExMwA0AAHROQQVL165dlZqaqoKCgoDxgoICpaenX/bYiIgI9enTR2FhYdqwYYPuuusudeny2a8fOXKkTp48qaamJv/8EydOqFevXuratWswSwQAAJ1Q0C8JZWdn68UXX1ReXp6OHTumefPmqby8XFlZWZI+e6lm6tSp/vknTpzQK6+8ovfee09FRUWaPHmyDh8+rKVLl/rnPPjgg/rwww81Z84cnThxQn/605+0dOlSzZw5MwQPEQAAdHRBfw/LpEmT9OGHH2rx4sWqrKzUoEGDlJ+fr759+0qSKisrVV5e7p/f2NioZ555RsePH1dERITGjBmjffv2qV+/fv45SUlJ2r59u+bNm6fBgwerd+/emjNnjubPn3/1jxAAAHR4QX8Pi1V8DwsAAB1Pm3wPCwAAQHsgWAAAgHkECwAAMI9gAQAA5hEsAADAPIIFAACYR7AAAADzCBYAAGAewQIAAMwjWAAAgHkECwAAMI9gAQAA5hEsAADAPIIFAACYR7AAAADzCBYAAGAewQIAAMwjWAAAgHkECwAAMI9gAQAA5hEsAADAPIIFAACYR7AAAADzCBYAAGAewQIAAMwjWAAAgHkECwAAMI9gAQAA5hEsAADAPIIFAACYR7AAAADzCBYAAGAewQIAAMwjWAAAgHkECwAAMI9gAQAA5hEsAADAPIIFAACYR7AAAADzCBYAAGAewQIAAMwjWAAAgHnh7b2AUHHOSZJ8Pl87rwQAALTWxefti8/jLek0wVJXVydJSkpKaueVAACAYNXV1Sk2NrbF+z3ui5Kmg2hqatIHH3yg6OhoeTyekJ3X5/MpKSlJFRUViomJCdl5cWns97XFfl9b7Pe1xX5fW1e638451dXVKTExUV26tPxOlU5zhaVLly7q06dPm50/JiaGv/DXEPt9bbHf1xb7fW2x39fWlez35a6sXMSbbgEAgHkECwAAMI9g+QJer1ePPfaYvF5vey/lfwL7fW2x39cW+31tsd/XVlvvd6d50y0AAOi8uMICAADMI1gAAIB5BAsAADCPYAEAAOYRLF9g1apVSklJUWRkpFJTU7Vnz572XlKHl5ubq1tvvVXR0dHq2bOnJk6cqOPHjwfMcc7p8ccfV2JioqKiovTNb35TR44caacVdy65ubnyeDyaO3euf4z9Dq0zZ87ogQceUI8ePXTdddfplltuUUlJif9+9jt0Lly4oEWLFiklJUVRUVHq37+/Fi9erKamJv8c9vvq7N69W5mZmUpMTJTH49HWrVsD7m/N/tbX1+uhhx5SXFycrr/+et1999365z//GdxCHFq0YcMGFxER4dauXeuOHj3q5syZ466//np3+vTp9l5ahzZ27Fj30ksvucOHD7vS0lI3YcIEl5yc7M6ePeuf8+STT7ro6Gi3adMmd+jQITdp0iTXq1cv5/P52nHlHV9RUZHr16+fGzx4sJszZ45/nP0OnX//+9+ub9++bvr06e7dd991ZWVlbseOHe7kyZP+Oex36PzqV79yPXr0cG+++aYrKytzf/jDH9yXvvQlt3z5cv8c9vvq5Ofnu0ceecRt2rTJSXJbtmwJuL81+5uVleV69+7tCgoK3P79+92YMWPckCFD3IULF1q9DoLlMr7xjW+4rKysgLEBAwa4BQsWtNOKOqfq6monye3atcs551xTU5NLSEhwTz75pH/Op59+6mJjY92aNWvaa5kdXl1dnfvqV7/qCgoK3B133OEPFvY7tObPn+9GjRrV4v3sd2hNmDDB/ehHPwoY+/73v+8eeOAB5xz7HWqfD5bW7O/HH3/sIiIi3IYNG/xzzpw547p06eLeeuutVv9uXhJqwfnz51VSUqKMjIyA8YyMDO3bt6+dVtU51dbWSpK6d+8uSSorK1NVVVXA3nu9Xt1xxx3s/VWYOXOmJkyYoDvvvDNgnP0OrW3btiktLU0/+MEP1LNnTw0dOlRr1671389+h9aoUaP09ttv68SJE5KkgwcPau/evRo/frwk9ruttWZ/S0pK1NDQEDAnMTFRgwYNCurPoNP844ehVlNTo8bGRsXHxweMx8fHq6qqqp1W1fk455Sdna1Ro0Zp0KBBkuTf30vt/enTp6/5GjuDDRs2aP/+/frb3/7W7D72O7Tef/99rV69WtnZ2Vq4cKGKioo0e/Zseb1eTZ06lf0Osfnz56u2tlYDBgxQWFiYGhsbtWTJEt13332S+Pvd1lqzv1VVVeratau6devWbE4wz6cEyxfweDwBPzvnmo3hys2aNUt///vftXfv3mb3sfehUVFRoTlz5mj79u2KjIxscR77HRpNTU1KS0vT0qVLJUlDhw7VkSNHtHr1ak2dOtU/j/0OjY0bN+qVV17R73//e339619XaWmp5s6dq8TERE2bNs0/j/1uW1eyv8H+GfCSUAvi4uIUFhbWrP6qq6ublSSuzEMPPaRt27bpnXfeUZ8+ffzjCQkJksTeh0hJSYmqq6uVmpqq8PBwhYeHa9euXXruuecUHh7u31P2OzR69eqlm266KWBs4MCBKi8vl8Tf71D72c9+pgULFmjy5Mm6+eabNWXKFM2bN0+5ubmS2O+21pr9TUhI0Pnz5/XRRx+1OKc1CJYWdO3aVampqSooKAgYLygoUHp6ejutqnNwzmnWrFnavHmz/vKXvyglJSXg/pSUFCUkJATs/fnz57Vr1y72/gp8+9vf1qFDh1RaWuq/paWl6f7771dpaan69+/PfofQyJEjm31M/8SJE+rbt68k/n6H2rlz59SlS+BTWVhYmP9jzex322rN/qampioiIiJgTmVlpQ4fPhzcn8EVv1X4f8DFjzWvW7fOHT161M2dO9ddf/317tSpU+29tA7twQcfdLGxsW7nzp2usrLSfzt37px/zpNPPuliY2Pd5s2b3aFDh9x9993HxxBD6L8/JeQc+x1KRUVFLjw83C1ZssS999577tVXX3XXXXede+WVV/xz2O/QmTZtmuvdu7f/Y82bN292cXFx7uc//7l/Dvt9derq6tyBAwfcgQMHnCT37LPPugMHDvi/4qM1+5uVleX69OnjduzY4fbv3+++9a1v8bHmUFu5cqXr27ev69q1qxs2bJj/o7e4cpIueXvppZf8c5qamtxjjz3mEhISnNfrdbfffrs7dOhQ+y26k/l8sLDfofXGG2+4QYMGOa/X6wYMGOBeeOGFgPvZ79Dx+Xxuzpw5Ljk52UVGRrr+/fu7Rx55xNXX1/vnsN9X55133rnk/7OnTZvmnGvd/v7nP/9xs2bNct27d3dRUVHurrvucuXl5UGtw+Occ1d1PQgAAKCN8R4WAABgHsECAADMI1gAAIB5BAsAADCPYAEAAOYRLAAAwDyCBQAAmEewAAAA8wgWAABgHsECAADMI1gAAIB5BAsAADDv/wG639Rz3b7QDwAAAABJRU5ErkJggg==", + "text/plain": [ + "<Figure size 640x480 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "steps = []\n", + "def constant(n):\n", + " return 1\n", + " \n", + "for i in range(1, 100):\n", + " steps.append(constant(i))\n", + "plt.plot(steps)" + ] + }, + { + "cell_type": "markdown", + "id": "fe0a6c66", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Example: linear time" + ] + }, + { + "cell_type": "code", + "execution_count": 150, + "id": "6e6c4334", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'Steps')" + ] + }, + "execution_count": 150, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "<Figure size 640x480 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "steps = []\n", + "def linear(n):\n", + " return n\n", + " \n", + "for i in range(1, 100):\n", + " steps.append(linear(i))\n", + " \n", + "plt.plot(steps)\n", + "plt.xlabel('Inputs')\n", + "plt.ylabel('Steps')" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "54dcf1fa", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "import time\n", + "import random\n", + "import numpy as np\n", + "#%matplotlib inline\n", + "\n", + "nvalues = [100, 500, 1000, 1500, 2000, 2500, 3000]\n", + "timesAlgo = []\n", + "\n", + "for i in nvalues:\n", + "\n", + " random.seed()\n", + " p = 12**2 # magnitude of values\n", + " liste = []\n", + " \n", + " for x in range(i): liste.append(random.randint(0, p))\n", + "\n", + " a=time.perf_counter()\n", + " e1 = []\n", + " for n in liste:\n", + " e1.append(n)\n", + " b = time.perf_counter()\n", + " timesAlgo.append(b-a)" + ] + }, + { + "cell_type": "code", + "execution_count": 128, + "id": "340dc177", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "data": { + "image/png": "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LU2mTp0qK1eulLS0NElJSZF77rlHmjVrJj/99FOF896uXTu5//775auvvpL//e9/cubMGfNnGzduNJcPCAiQ66+/Xj7++GNJS0uT5ORkmTp1qkUZW/9WbrjhBlm8eLGkpqbKU089JQDkww8/rPR3QlQZJkdE1yA3N1cAWFzgqnLgwAEBIE899ZTF+oyMDIsLt4j6Bx+A7Nixw7zuzJkz4uTkJM2aNbNIhHbv3i0A5L///a95nSk5mjx5skVdn376qQCQTz75xKKu8snRZ599JgAkOTnZYtvt27cLAFm4cKF5XV5enrRv31769Okju3btEldXV3nooYcstrsyORIRee211wSA5OTkVDhP4eHhcvPNN1use/LJJ8Xd3V3Onz9vUbezs7NFnKYL8fLly6WoqEguXrwo33//vVx//fXi5OQke/bskePHj4uzs7NMmDDBoo7z58+Lj4+PREdHm9fFxsYKAFm6dGmFOE3nuPyFujJdunSRLl26yKVLlyot069fP2nTpo3FMRYXF0u3bt2kffv2UlpaKiJlydGV8d9zzz0CQN58802L9TfffLMEBweb35t+H76+vhbx5OfnS+vWreWOO+6oNMbi4mIpLCyUG264weK7ZTrvAwYMqLDNlclRXl6eAJC33nqr0npq8reSkZFhUTYoKEgiIyMrrYOoMnysRlSPNm7cCEA9qimvT58+uOmmmyo80mjbti1CQkLM71u3bo02bdrg5ptvhq+vr3n9TTfdBMDykZHJgw8+aPE+Ojoazs7O5lis+d///oeWLVvizjvvRHFxsfl18803w8fHx+LxiKenJ5YvX45du3ahf//+6NixIxYvXlz1ibiKSZMmYffu3diyZQsAID8/Hx9//DFiY2PRvHlzc7kvv/wSer0eUVFRFfYxcuRI6HQ6uLq6YsCAASgpKcHKlSvRo0cPrFu3DsXFxXj44Yctjs/FxQXh4eFWe1Xdd999NT6en3/+GUeOHMGjjz4KFxcXq2UuXLiAjIwM3H///RbH6OTkhJiYGJw8eRIHDx602GbEiBEW703fg+HDh1dYb+27ce+991rE06JFC9x55534/vvvUVJSAgAoLi7Gyy+/jKCgIOj1ejg7O0Ov1+PQoUNWe/9V5zy1bt0aXbp0wWuvvYY333wTmZmZFR5/2fq34uPjgz59+lis69Gjh9XjJroaJkdE18DLywuurq7IycmpVvkzZ84AUEnPlXx9fc2fm7Ru3bpCOb1eX2G9Xq8HAFy+fLlCeR8fH4v3zs7O8PT0rFBXeb///jvOnTsHvV4PnU5n8crNzUVeXp5F+b59+6Jr1664fPkynnzySbi5uVW67+q4++670alTJyxYsACAamNz4cIFPP300xblVq5ciaFDh8LV1bXCPl555RVs374du3btwvHjx3H06FHcc8895uMDgFtuuaXC8S1fvrzC8bm6usLd3b3Gx2Nqp9W+fftKy5w9exYiUul3A8BVvx+m74G19dX5bpjWFRYW4q+//gIATJkyBc899xzuuecerF69GhkZGdi+fTt69uyJS5cuVdjeWvxXcnBwwHfffYfIyEi8+uqrCA4OxnXXXYeJEyfi/PnzFsda3b8VT0/PCuUMBoPVGImuhr3ViK6Bk5MTBg8ejG+++QYnT56s8uIHlP0DfurUqQplf/vtN3h5edV6jLm5uRZd14uLi3HmzBmrFxMTLy8veHp6VtrLrkWLFhbvZ8+ejaysLISEhOD555/HiBEj0Llz5xrH7OjoiKeffhozZszAG2+8gYULF2Lw4MEICAgwlzEajfjuu+8sxk4qr3Pnzujdu7fVz0zneeXKlfDz87tqPA4ODrYfRDnXXXcdAFRoSFxeq1at4OjoiFOnTlX47LfffgOAWv9+5ObmWl2n1+vNd68++eQTPPzww3j55ZctyuXl5VkdgqG658rPzw+JiYkA1J21zz//HHPmzEFhYSEWL16syd8KkQnvHBFdo+nTp0NE8Pjjj6OwsLDC50VFRebeXIMGDQKgLjjlbd++HQcOHMDgwYNrPb5PP/3U4v3nn3+O4uLiKgd9HDFiBM6cOYOSkhL07t27wqt8kpKamop58+Zh1qxZSE1NhYeHB0aOHGn1XJRn6tlX2f/sH3vsMej1ejz44IM4ePAgxo8fb/H56tWr4eDgUOHRUnVERkbC2dkZR44csXp8lSVVNXXjjTeiS5cuWLp0KQoKCqyWcXNzQ9++fbFq1SqLc1JaWopPPvkE7du3x4033lirca1atcrijtL58+exevVqhIWFwcnJCYBKdq7shblmzRr8+uuvtRbHjTfeiFmzZqF79+7YtWsXAG3+VohMeOeI6BqFhoZi0aJFeOqppxASEoInn3wSXbt2RVFRETIzM/Hee++hW7duuPPOOxEQEIAnnngC77zzDhwdHTF06FAcO3YMzz33HDp06IDJkyfXenyrVq2Cs7MzhgwZgv379+O5555Dz549ER0dXek2o0aNwqeffophw4Zh0qRJ6NOnD3Q6HU6ePImNGzfi7rvvxj/+8Q+cOnUKDz30EMLDwzF79mw4Ojpi+fLlGDBgAKZNm4a33nqr0jq6d+8OAHj77bcRGxsLnU6HgIAA812pli1b4uGHH8aiRYvg5+eHO++802L7lStXYsiQIRXuYlVHp06d8MILL2DmzJk4evQooqKi0KpVK/z+++/48ccf4ebmhrlz59q836osWLAAd955J/r164fJkyejY8eOOH78ONatW2dOYOfNm4chQ4bg9ttvxzPPPAO9Xo+FCxdi3759+Oyzz675DtaVnJycMGTIEEyZMgWlpaV45ZVXkJ+fb3HsI0aMwAcffIDAwED06NEDO3fuxGuvvXbVu6RV2bt3L8aPH48HHngAN9xwA/R6PTZs2IC9e/ciPj4eADT5WyEy07pFOFFjsXv3bomNjZWOHTuKXq83d5l//vnn5fTp0+ZyJSUl8sorr8iNN94oOp1OvLy85KGHHqrQDTw8PFy6du1aoR4/Pz8ZPnx4hfUA5Omnnza/N/Wk2rlzp9x5553SvHlzadGihYwePVp+//33CnWV760mIlJUVCSvv/669OzZU1xcXKR58+YSGBgoY8eOlUOHDklxcbGEh4eLt7e3nDp1ymJbU0+0lJQUEbHeW01EZPr06eLr6yuOjo4VunqLqK7tACQhIcFi/V9//SUuLi4V9idSeVd+a7744gu5/fbbxd3dXQwGg/j5+cn9998v3377rblMbGysuLm5Wd3elt5qIiLp6ekydOhQ8fDwEIPBIF26dKnQm3Dz5s0yaNAgcXNzk2bNmkm/fv1k9erVFmVMvdW2b99erXiuPAbT7+OVV16RuXPnSvv27UWv10uvXr1k3bp1FtuePXtWHn30UWnTpo24urrKbbfdJps3b67wnanqvF/ZW+3333+XRx55RAIDA8XNzU2aN28uPXr0kPnz50txcbF5u2v9W4mNjRU/P78K64muxkFERJOsjIjq1Jw5czB37lz88ccfdts+Y+rUqVi0aBFOnDhh0Ubq888/x4MPPojff//daqN1qtqxY8fg7++P1157Dc8884zW4RA1OGxzREQNzrZt2/DRRx9h4cKFeOKJJyo0Ho+OjkZRURETIyKqE2xzREQNTmhoKFxdXTFixAi8+OKLWodDRE0MH6sRERERlcPHakRERETlMDkiIiIiKofJEREREVE5bJBto9LSUvz2229o0aJFrQ/IRkRERHVDRHD+/Hn4+vrC0bHqe0NMjmz022+/oUOHDlqHQURERDVw4sSJq47wzuTIRqapCk6cOHFNs3QTERFR/cnPz0eHDh2qNeUQkyMbmR6lubu7MzkiIiKyM9VpEsMG2URERETlMDkiIiIiKofJEREREVE5TI6IiIiIymFyRERERFROjZKjhQsXwt/fHy4uLggJCcHmzZurLJ+WloaQkBC4uLigc+fOWLx4cYUyycnJCAoKgsFgQFBQEFJSUmyud9WqVYiMjISXlxccHBywe/fuCvvIzc1FTEwMfHx84ObmhuDgYKxcudK2E0BERESNls3J0fLlyxEXF4eZM2ciMzMTYWFhGDp0KI4fP261fE5ODoYNG4awsDBkZmZixowZmDhxIpKTk81l0tPTMXLkSMTExGDPnj2IiYlBdHQ0MjIybKr3woULuPXWW5GQkFBp/DExMTh48CC++uorZGVl4d5778XIkSORmZlp66kgIiKixkhs1KdPHxk3bpzFusDAQImPj7daftq0aRIYGGixbuzYsdKvXz/z++joaImKirIoExkZKaNGjapRvTk5OQJAMjMzK3zm5uYmH330kcW61q1by/vvv281/isZjUYBIEajsVrliYiISHu2XL9tunNUWFiInTt3IiIiwmJ9REQEtm7danWb9PT0CuUjIyOxY8cOFBUVVVnGtM+a1FuZ2267DcuXL8eff/6J0tJSJCUloaCgAAMHDrRavqCgAPn5+RYvIiIiarxsSo7y8vJQUlICb29vi/Xe3t7Izc21uk1ubq7V8sXFxcjLy6uyjGmfNam3MsuXL0dxcTE8PT1hMBgwduxYpKSkoEuXLlbLz5s3Dx4eHuYX51UjIiJq3GrUIPvKobdFpMrhuK2Vv3J9dfZpa73WzJo1C2fPnsW3336LHTt2YMqUKXjggQeQlZVltfz06dNhNBrNrxMnTthUHxEREdkXm+ZW8/LygpOTU4W7NadPn65wV8fEx8fHanlnZ2d4enpWWca0z5rUa82RI0fw7rvvYt++fejatSsAoGfPnti8eTMWLFhgtRedwWCAwWCodh1ERERk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", + "text/plain": [ + "<Figure size 640x480 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(nvalues, timesAlgo, \"r-\", label=\"Algo 1\")\n", + "plt.title(\"Complexity/Perf comparison\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8593238b", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "import time\n", + "import random\n", + "\n", + "def measure_sorting_time(sorting_function, lst):\n", + " a = time.perf_counter()\n", + " sorting_function(lst)\n", + " b = time.perf_counter()\n", + " return b - a\n", + "\n", + "nvalues = [100, 500, 1000, 1500, 2000, 2500, 3000]\n", + "timesAlgo = []\n", + "\n", + "for i in nvalues:\n", + " random.seed()\n", + " p = 12**2 # Magnitude of values\n", + " lst = [random.randint(0, p) for x in range(i)]\n", + "\n", + " time_python_sort = measure_sorting_time(sorted, lst.copy())\n", + " time_selection_sort = measure_sorting_time(selectionSort, lst.copy())\n", + " # add more sorting algorithms\n", + " \n", + " timesAlgo.append((time_python_sort, time_selection_sort))\n", + "\n", + "python_sort_times = [t[0] for t in timesAlgo]\n", + "selection_sort_times = [t[1] for t in timesAlgo]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 148, + "id": "fb711c57", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "<Figure size 640x480 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Plot the results\n", + "plt.plot(nvalues, python_sort_times, marker='o', linestyle='-', color='b', label='Python Built-in Sort')\n", + "plt.plot(nvalues, selection_sort_times, marker='o', linestyle='-', color='g', label='Selection Sort (Custom)')\n", + "plt.xlabel('Input Size (n)')\n", + "plt.ylabel('Time (seconds)')\n", + "plt.title('Comparison of Sorting Algorithms')\n", + "plt.legend()\n", + "plt.grid()\n", + "plt.show()" + ] + } + ], + "metadata": { + "celltoolbar": "Slideshow", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/03-lists-search-sort.ipynb b/03-lists-search-sort.ipynb index b1084b9cd440f98ae9fc14c507e8fcc85612c262..cc8c8cd05f260bebfab488daa464b7188909ab52 100644 --- a/03-lists-search-sort.ipynb +++ b/03-lists-search-sort.ipynb @@ -597,25 +597,6 @@ "order_by_alphabetical_order(\"cherry\", \"banana\")" ] }, - { - "cell_type": "code", - "execution_count": 53, - "id": "a0c076fa", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "66" - ] - }, - "execution_count": 53, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [] - }, { "cell_type": "markdown", "id": "ee60b8ff", @@ -764,9 +745,17 @@ " print(item)" ] }, + { + "cell_type": "markdown", + "id": "c5bf0281", + "metadata": {}, + "source": [ + "Fibonacci with generators" + ] + }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 15, "id": "ff445529", "metadata": {}, "outputs": [], @@ -832,7 +821,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "id": "53534019", "metadata": { "slideshow": { @@ -849,10 +838,8 @@ } ], "source": [ - "# Initialize an empty linked list\n", "linked_list = None\n", "\n", - "# Function to append data to the linked list\n", "def append(data):\n", " global linked_list\n", " if linked_list is None:\n", @@ -863,7 +850,6 @@ " current = current[\"next\"]\n", " current[\"next\"] = {\"data\": data, \"next\": None}\n", "\n", - "# Function to traverse and print the linked list\n", "def traverse():\n", " current = linked_list\n", " while current:\n", @@ -871,13 +857,11 @@ " current = current[\"next\"]\n", " print(\"None\")\n", "\n", - "# Append some data to the linked list\n", "append(1)\n", "append(2)\n", "append(3)\n", "\n", - "# Print the linked list\n", - "traverse()\n" + "traverse()" ] }, { @@ -973,14 +957,6 @@ "list(map(lambda x : x * x, x))" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "fb30b1ce", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": 15, @@ -1075,19 +1051,19 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 17, "id": "601e1691", "metadata": {}, "outputs": [ { - "ename": "ValueError", - "evalue": "10 is not in list", + "ename": "NameError", + "evalue": "name 'L' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[34], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mL\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindex\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m10\u001b[39;49m\u001b[43m)\u001b[49m\n", - "\u001b[0;31mValueError\u001b[0m: 10 is not in list" + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[17], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mL\u001b[49m\u001b[38;5;241m.\u001b[39mindex(\u001b[38;5;241m10\u001b[39m)\n", + "\u001b[0;31mNameError\u001b[0m: name 'L' is not defined" ] } ], diff --git a/04-05-06-programming-strategies.ipynb b/04-05-06-programming-strategies.ipynb index f1d734f362c05b95f6906995c1c4616a6889b547..4c0ca2272abf686e33a3b8b3fe4a706f78f5cd6f 100644 --- a/04-05-06-programming-strategies.ipynb +++ b/04-05-06-programming-strategies.ipynb @@ -665,7 +665,7 @@ "- Fibonacci Sequence\n", "- Rod Cutting\n", "- Sequence Alignment, Longest Subsequence Finding\n", - "- Shortest Path Findin" + "- Shortest Path Finding" ] }, { @@ -810,7 +810,7 @@ "source": [ "## Rod cutting \n", "\n", - "_Given a list of cuts and prices, identify the optimal cuts. Given the example below, what is the best cutting strategy for a rod of size 2?_\n", + "_Given a list of cuts and prices, identify the optimal cuts. Given the example below, what is the best cutting strategy for a rod of size `4`?_\n", "\n", "<img src=\"figures/rod-cutting.png\" style=\"width:500px\">" ] @@ -828,9 +828,13 @@ { "cell_type": "markdown", "id": "47119bfb", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, "source": [ - "Solution: $2$ with $5 + 5 = 10$." + "Solution: For a rod of size `4` optimal solution is 2 cuts of size 2 so $5 + 5 = 10$." ] }, { @@ -1037,7 +1041,7 @@ "\n", "Now, we can calculate `V_3` using the values of `V_2` and `V_1`:\n", "\n", - "$$V_3 = \\max(p_1 + V_2, p_2 + V_1, p_3 + V_0) = \\max(2 + 5, 5 + 2, 9 + 0) = \\max(7, 7, 9) = 9$$" + "$$V_3 = \\max(p_1 + V_2, p_2 + V_1, p_3 + V_0) = \\max(1 + 5, 5 + 2, 9 + 0) = \\max(6, 7, 8) = 8$$" ] }, { @@ -1093,6 +1097,36 @@ " print(\"Max size cut \" + str(cutRod(arr, size)), len(arr) ) " ] }, + { + "cell_type": "markdown", + "id": "c42683cc", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Change-making problem (dynamic programming)\n", + "\n", + "\n", + "$Q_{opt}(S,M) = min \\ \\sum_{i=1}^n x_i$.\n", + " \n", + "$S$: all the available coins\n", + " \n", + "$M$: amount\n", + " \n", + "$\n", + " Q_{opt}(i,m) = min\n", + "\\begin{cases}\n", + " 1 + Q_{opt}(i, m - v_i) \\quad si \\ (m - v_i) \\geq 0 \\qquad \\text{we use a coin of type $i$ of value $v_i$}\\\\\n", + "Q_{opt}(i-1, m) \\qquad \\quad si \\ i \\geq 1 \\qquad \\qquad \\quad \\text{we do not use coin of type $i$, \n", + "we use $i-1$}\n", + "\\end{cases}\n", + "$\n", + "\n", + "<img src=\"figures/coins-changing.png\" style=\"width:500px\">\n" + ] + }, { "cell_type": "markdown", "id": "fd3e3af7", diff --git a/07-stacks-queues.ipynb b/07-stacks-queues.ipynb index e41de9135de4815f4639c2a95a4bb5861bfb8cf5..740b4b6eae83683ab8aa69dd02070d31405d62b2 100644 --- a/07-stacks-queues.ipynb +++ b/07-stacks-queues.ipynb @@ -13,9 +13,18 @@ "## Lecture 7: Stacks and queues\n", "### Ecole Centrale de Lyon, Bachelor of Science in Data Science for Responsible Business\n", "#### Romain Vuillemot\n", - "<center><img src=\"figures/Logo_ECL.png\" style=\"width:300px\"></center>\n", - "\n", - "\n", + "<center><img src=\"figures/Logo_ECL.png\" style=\"width:300px\"></center>" + ] + }, + { + "cell_type": "markdown", + "id": "4dfe56e7", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ "---" ] }, @@ -192,14 +201,20 @@ } }, "source": [ - "### Stacks (using OOP)" + "### Stacks (using OOP)\n", + "\n", + "_Internally, will be based on an `Array` structure._" ] }, { "cell_type": "code", "execution_count": 17, "id": "8ae9a611", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, "outputs": [], "source": [ "class Stack():\n", diff --git a/exercises/01-data-structures-complexity-exercises.ipynb b/exercises/01-data-structures-complexity-exercises.ipynb index 0be737d823b8270c6f727b80d3d3dfc798baae8e..50f1d8fdd67cb0271978a336e741d5ad527d2efd 100644 --- a/exercises/01-data-structures-complexity-exercises.ipynb +++ b/exercises/01-data-structures-complexity-exercises.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "59aef681", + "id": "fa46e55d", "metadata": {}, "source": [ "# UE5 Fundamentals of Algorithms\n", @@ -23,7 +23,7 @@ { "cell_type": "code", "execution_count": null, - "id": "88952ac5", + "id": "4c1206d1", "metadata": {}, "outputs": [], "source": [ @@ -33,7 +33,7 @@ }, { "cell_type": "markdown", - "id": "b779c274", + "id": "6157338f", "metadata": {}, "source": [ "---" diff --git a/exercises/02-recursion-exercises.ipynb b/exercises/02-recursion-exercises.ipynb index 5f854e39a6f1e92ed01267baa4bfea467c61cfb1..5d727a2d641bef5dc48ae5be44efd40f8253ab28 100644 --- a/exercises/02-recursion-exercises.ipynb +++ b/exercises/02-recursion-exercises.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "a14c054e", + "id": "01d26f8d", "metadata": {}, "source": [ "# UE5 Fundamentals of Algorithms\n", @@ -23,7 +23,7 @@ { "cell_type": "code", "execution_count": null, - "id": "b13780a7", + "id": "a6f5bcf0", "metadata": {}, "outputs": [], "source": [ @@ -33,7 +33,7 @@ }, { "cell_type": "markdown", - "id": "67ead887", + "id": "51dc6634", "metadata": {}, "source": [ "---" diff --git a/exercises/03-lists-search-sort-exercises.ipynb b/exercises/03-lists-search-sort-exercises.ipynb index 2ff574110dcfc0a1b629bd8cddc1b1781ed34e55..d64010d6f3467b5100f3b6b1aad2839c4abb411f 100644 --- a/exercises/03-lists-search-sort-exercises.ipynb +++ b/exercises/03-lists-search-sort-exercises.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "f8151910", + "id": "7989517f", "metadata": {}, "source": [ "# UE5 Fundamentals of Algorithms\n", @@ -23,7 +23,7 @@ { "cell_type": "code", "execution_count": null, - "id": "94c8f6d3", + "id": "c659f857", "metadata": {}, "outputs": [], "source": [ @@ -33,7 +33,7 @@ }, { "cell_type": "markdown", - "id": "9e5bf33f", + "id": "a58b61ff", "metadata": {}, "source": [ "---" diff --git a/exercises/04-05-06-programming-strategies-exercises.ipynb b/exercises/04-05-06-programming-strategies-exercises.ipynb index e79716bbd73ef9d85a0250452eb7e90e62b6d225..1a37afee2497791d8d64044d5eed09612ce1f6ff 100644 --- a/exercises/04-05-06-programming-strategies-exercises.ipynb +++ b/exercises/04-05-06-programming-strategies-exercises.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "8fa9ea2e", + "id": "c271b1aa", "metadata": {}, "source": [ "# UE5 Fundamentals of Algorithms\n", @@ -23,7 +23,7 @@ { "cell_type": "code", "execution_count": null, - "id": "6017bb23", + "id": "757d4143", "metadata": {}, "outputs": [], "source": [ @@ -33,7 +33,7 @@ }, { "cell_type": "markdown", - "id": "7dd0e72c", + "id": "451d6862", "metadata": {}, "source": [ "---" @@ -599,7 +599,7 @@ }, "outputs": [], "source": [ - "intervals = [(1, 3), (2, 4), (3, 5), (5, 7), (6, 8)]" + "interval_scheduling([(0, 2), (2, 4), (1, 3)])" ] }, { @@ -611,7 +611,7 @@ "editable": false, "nbgrader": { "cell_type": "code", - "checksum": "a1c5ab0cb1fbb052b5567e17549a9723", + "checksum": "d8a22346b92125746d141cee329408f1", "grade": true, "grade_id": "correct_interval_scheduling", "locked": true, @@ -624,7 +624,19 @@ }, "outputs": [], "source": [ - "assert interval_scheduling(intervals) == [(1, 3), (3, 5), (5, 7)]" + "assert interval_scheduling([(0, 2), (2, 4), (1, 3)]) == [(0, 2), (2, 4)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de2c9857-9925-4477-96e4-341fa5bc4ec8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert interval_scheduling([(0, 2), (2, 4), (1,3)]) == [(0, 2), (2, 4)]" ] }, { @@ -741,7 +753,7 @@ "deletable": false, "nbgrader": { "cell_type": "code", - "checksum": "4d258685229c335d689928bcd6c973ec", + "checksum": "959fdc68a051b0090f4ead159f3356df", "grade": false, "grade_id": "cell-0bf75b2bfe8e2e4c", "locked": false, @@ -753,7 +765,7 @@ }, "outputs": [], "source": [ - "def greedy_knapsack(W, wt):\n", + "def greedy_knapsack(W, w):\n", " # YOUR CODE HERE\n", " raise NotImplementedError()" ] @@ -849,6 +861,52 @@ "source": [ "assert dynamic_knapsack(max_weight, weights) == (5, [3, 2])" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef953a29-7632-4475-8230-54a8a110d19a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7dd8b57-0d0d-43d0-b228-b9c994043f81", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a066e026-8a09-46ca-a919-53bc90c8a308", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80c70549-29c1-49ff-a7de-f47dbce004a9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "greedy_intevals([(0, 2), (2, 4), (1,3)])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41e37d95-4e71-42f0-81dd-fd6122fc9023", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/exercises/07-stacks-queues-exercises.ipynb b/exercises/07-stacks-queues-exercises.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..cfbe2107bfebf771dbefd59816f4083bf69beb28 --- /dev/null +++ b/exercises/07-stacks-queues-exercises.ipynb @@ -0,0 +1,444 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "49d0b44c", + "metadata": {}, + "source": [ + "# UE5 Fundamentals of Algorithms\n", + "## Exercices\n", + "### Ecole Centrale de Lyon, Bachelor of Science in Data Science for Responsible Business\n", + "#### [Romain Vuillemot](https://romain.vuillemot.net/)\n", + "\n", + "Before you turn this problem in:\n", + "- make sure everything runs as expected. \n", + " - first, **restart the kernel** (in the menubar, select Kernel$\\rightarrow$Restart) \n", + " - then **run all cells** (in the menubar, select Cell$\\rightarrow$Run All).\n", + "- make sure you fill in any place that says `YOUR CODE HERE` or \"YOUR ANSWER HERE\"\n", + "- remove `raise NotImplementedError()` to get started with your answer\n", + "- bonus points (at the end of this notebook) are optionals\n", + "- write your name (and collaborators as a list if any) below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a884873", + "metadata": {}, + "outputs": [], + "source": [ + "ID = \"\"\n", + "COLLABORATORS_ID = []" + ] + }, + { + "cell_type": "markdown", + "id": "c7232915", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "2f1f2dcd-96a9-45ef-90a6-4ad488635679", + "metadata": {}, + "source": [ + "# Stacks and queues" + ] + }, + { + "cell_type": "markdown", + "id": "b9bd540c-dd15-49ac-bfbd-f2e758688a85", + "metadata": { + "tags": [] + }, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "03a0653e-65c2-4e79-9e83-31765cf19098", + "metadata": {}, + "source": [ + "## Exercise 1: Reverse a string using a Stack\n", + "\n", + "_Use the `Stack` below to reverse a string given as input._" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4932473d-2734-4e81-b777-ca10decfd9e8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "class Stack:\n", + " def __init__(self):\n", + " self.items = []\n", + "\n", + " def push(self, item):\n", + " self.items.append(item)\n", + "\n", + " def pop(self):\n", + " if not self.is_empty():\n", + " return self.items.pop()\n", + "\n", + " def peek(self):\n", + " if not self.is_empty():\n", + " return self.items[-1]\n", + "\n", + " def is_empty(self):\n", + " return len(self.items) == 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b77ae34-ef7c-4664-94e0-8928156f2224", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "128a37273e0e5da052abe4bf08bb1c27", + "grade": false, + "grade_id": "cell-5b0828e97507162e", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def reverse_string(s):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63719c8e-f60c-4544-8e41-cb6380ae4bcf", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "reverse_string(\"Hello\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81e93620-0664-4a9d-ba5f-894937c9769e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert reverse_string(\"Hello\") == \"olleH\" " + ] + }, + { + "cell_type": "markdown", + "id": "81df9b1e-cfe5-4b69-96a5-c8065259cc7d", + "metadata": {}, + "source": [ + "## Exercise 2: Check if a word is a palindrom (using a Stack)\n", + "_A palindrome is a sequence of characters that reads the same forward and backward._" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf6fbdd5-53c5-45c2-a0c5-a5ed845c4f81", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "048d788477e620bf78240329c0dd8771", + "grade": false, + "grade_id": "is_palindrome", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def is_palindrome(s):\n", + " # YOUR CODE HERE\n", + " raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "586bafba-2fbb-4833-b2e3-609db9b28fbf", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "is_palindrome(\"ABA\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0005a10-9152-4aa5-a94b-fcbff1bd2281", + "metadata": { + "deletable": false, + "editable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "4165b33ba9b75546b0edd15216e61e4f", + "grade": true, + "grade_id": "correct_is_palindrome", + "locked": true, + "points": 0, + "schema_version": 3, + "solution": false, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "assert is_palindrome(\"ABA\")" + ] + }, + { + "cell_type": "markdown", + "id": "f767bf25-9f4f-4a0d-8cb9-b729bbec5c27", + "metadata": {}, + "source": [ + "## Exercise 3: Implement a min-heap\n", + "\n", + "Use a `PriorityQueue` to return the smallest element when using `pop` of a stack (or a queue). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ddcccaf6-d235-4327-826f-7a62a4c23f28", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from queue import PriorityQueue" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2da2db1e-f55d-43b4-877f-96ef944818e8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# how to use the modue\n", + "priority_queue = PriorityQueue()\n", + "priority_queue.put((3, 'apple'))\n", + "priority_queue.put((1, 'banana'))\n", + "priority_queue.put((2, 'cherry'))\n", + "element = priority_queue.get()\n", + "print(element)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "804ea32d-5bf8-42b9-ae52-6318b26f4065", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "7f6b90fc037aa2a24fa9ce3b4dfca6dd", + "grade": false, + "grade_id": "cell-4b9a5ecdee87514e", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# YOUR CODE HERE\n", + "raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b2d28c4-277b-44fa-b7e8-590aa00f8f70", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "min_heap = MinHeap()\n", + "min_heap.insert(5)\n", + "min_heap.insert(3)\n", + "min_heap.insert(8)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed61bced-f000-41c6-8ecd-d669b4edb700", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert min_heap.pop() == 3\n", + "assert min_heap.peek() == 5\n", + "assert min_heap.peek() == 5" + ] + }, + { + "cell_type": "markdown", + "id": "a445d290-b04f-49b5-a8e7-2c6e259daf58", + "metadata": { + "tags": [] + }, + "source": [ + "## Exercise 4: Evaluate a postfix expression\n", + "\n", + "_Write a code that given the following expression, provides the following evaluation (using arthmetic operations over numerical values)._\n", + "\n", + "Expression: `\"3 4 +\"`\n", + "Evaluation: `3 + 4 = 7`\n", + "\n", + "First step: write a function `apply_operator` that applies an operation (ie + - * /) over two elements." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4cc7f805-0887-4422-b6b7-3d591d0df1fb", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "eb88296bc1de3c5dd7c68059e0a071e8", + "grade": false, + "grade_id": "cell-8c5106f02f243455", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def apply_operator(op, b, a):\n", + "# YOUR CODE HERE\n", + "raise NotImplementedError()" + ] + }, + { + "cell_type": "markdown", + "id": "e68bdf7c-ca08-4553-9874-8bd9038fd4b5", + "metadata": {}, + "source": [ + "Solution in pseudo-code:\n", + "- Split the input expression in to a list of tokens\n", + "- If not an operator\n", + " - Add the value to the stack\n", + "- If an operator \n", + " - Make sure there is enough parameters `a` and `b`\n", + " - Pop `a` and `b`\n", + " - Apply `apply_operator` on `a` and `b`\n", + " - Store the result in the stack" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e792c90d-1b38-47f5-9879-399debc934b9", + "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "73960d3c6b85c2efc0ad8e298e2649b7", + "grade": false, + "grade_id": "cell-e9236618b265b34f", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def evaluate_postfix(expression):\n", + "# YOUR CODE HERE\n", + "raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ea6e4840-1b7e-4265-b37d-e8c45ea6b3ed", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "postfix_expression = \"3 4 + 2 *\"\n", + "result = evaluate_postfix(postfix_expression)\n", + "print(\"Result:\", result)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0dc4dff8-089b-46a6-a08d-f53ee2fe72c3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert evaluate_postfix(\"3 4 + 2 *\") == 14\n", + "assert evaluate_postfix(\"4 2 3 5 * + *\") == 68 # (4 * (2 + (3 * 5))\n", + "assert evaluate_postfix(\"8 4 / 6 2 * +\") == 14 # ((8 / 4) + (6 * 2))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/solutions/04-05-06-programming-strategies-exercises.ipynb b/solutions/04-05-06-programming-strategies-exercises.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..5bee7faa259005d9bec99b1e6b4b7ba18a89307c --- /dev/null +++ b/solutions/04-05-06-programming-strategies-exercises.ipynb @@ -0,0 +1,1106 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e58599e3-9ab7-4d43-bb22-aeccade424ce", + "metadata": {}, + "source": [ + "# Programming strategies" + ] + }, + { + "cell_type": "markdown", + "id": "691b3c38-0e83-4bb2-ac90-ef76d2dd9a7a", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "9d813205-b709-42ab-b414-6f3fc947022a", + "metadata": {}, + "source": [ + "## Exercise 1: tribonacci\n", + "\n", + "Here is an implementation of the Tribonacci sequence (similar to the Fibonacci) defined as:\n", + "\n", + "$T(n) = T(n-1) + T(n-2) + T(n-3)$\n", + "\n", + "_Explain the role of the `tab` variable; write some tests._" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "f3b233ec-7077-479d-9c04-f1a4c35f3111", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def tribonacci(n: int) -> int:\n", + " \n", + " if(n==0):\n", + " return 0\n", + " if(n==1):\n", + " return 1\n", + " if(n==2):\n", + " return 1\n", + " \n", + " tab=[0 for i in range(n+1)]\n", + " tab[0]=0\n", + " tab[1]=1\n", + " tab[2]=1\n", + " \n", + " for i in range(3, n+1):\n", + " tab[i]=tab[i-1]+tab[i-2]+tab[i-3]\n", + " \n", + " return tab[n]" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "f280eeea-4812-4a30-80b5-3fe1cafa9283", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "274" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tribonacci(11)" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "423d2637-8bd6-4e8f-95ff-2765dae5bce7", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9e7356aa23ccfb4c", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "### BEGIN SOLUTION\n", + "assert tribonacci(0) == 0\n", + "assert tribonacci(1) == 1\n", + "assert tribonacci(2) == 1\n", + "assert tribonacci(11) == 274\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "markdown", + "id": "b2b31dca-a19a-46cf-b0de-4feec6afa083", + "metadata": {}, + "source": [ + "## Exercice 2: find two numbers with a given sum\n", + "\n", + "_Find two numbers in an array `nums` that add up to a specific target value `target`. Return a list of the values index. Tip: make sure you do not use twice the same array element._" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "f5a964df-958e-4758-8ca2-1139c59a7585", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "two_sum", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def two_sum(nums, target: int):\n", + " ### BEGIN SOLUTION\n", + " for i in range(len(nums)):\n", + " for j in range(i+1,len(nums)):\n", + " if(nums[i]+nums[j]==target):\n", + " return([i,j])\n", + " return -1\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "1cec37b0-5d27-4973-b53a-053a46992c0c", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, 2]" + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "two_sum([1, 2, 3, 4, 5], 4)" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "395cb69f-b99a-4c60-88f5-11216a5ec857", + "metadata": { + "nbgrader": { + "grade": true, + "grade_id": "correct_two_sum", + "locked": true, + "points": 1, + "schema_version": 3, + "solution": false, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "assert two_sum([2, 7, 11, 15], 9) == [0, 1] # 2 + 7 = 9\n", + "assert two_sum([3, 2, 4], 6) == [1, 2] # 2 + 4 = 6\n", + "assert two_sum([3, 3], 6) == [0, 1] # 3 + 3 = 6\n", + "assert two_sum([3, 3], 123) == -1 # not possible" + ] + }, + { + "cell_type": "markdown", + "id": "dc4f4361-5dee-4e19-b3fd-d18ea40d341e", + "metadata": { + "tags": [] + }, + "source": [ + "## Exercice 3: find the minimum distance between two points\n", + "\n", + "_Given a list of points find the minimum distance between all the pairs of points._\n", + "\n", + "- write a `dist`function using \n", + "- define a `closest_point_naive`function using `math.inf`as initial value\n", + "\n", + "Tip: make sure you do not use the same point twice!" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "f73f28a4-d39c-497d-9cbb-1c4edd1c628f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import math" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "0be305c3-c054-4092-b041-bd7e705e2178", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8738237c4c6682e4", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def dist(point1, point2):\n", + " ### BEGIN SOLUTION\n", + " return math.sqrt((point1[0] - point2[0])**2 + (point1[1] - point2[1])**2)\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "c0e25948-eefd-4cb0-a893-c3d5e34ff0ee", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9ca280911ed42034", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def closest_point_naive(P):\n", + " ### BEGIN SOLUTION\n", + " min_dist = math.inf\n", + " n = len(P)\n", + " for i in range(n):\n", + " for j in range(i + 1, n):\n", + " if dist(P[i], P[j]) < min_dist:\n", + " min_dist = dist(P[i], P[j])\n", + " return min_dist\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "965d8274-470e-4a3e-8b88-60d458de74e2", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1.4142135623730951" + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "points = [(1, 2), (4, 6), (7, 8), (3, 5)]\n", + "closest_point_naive(points)" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "5d875175-45c3-4594-a434-23e15cfb88f7", + "metadata": { + "nbgrader": { + "grade": true, + "grade_id": "cell-618f956021833284", + "locked": true, + "points": 1, + "schema_version": 3, + "solution": false, + "task": false + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "points1 = [(1, 2), (4, 6), (0, 0), (0, 0)]\n", + "closest_point_naive(points1)" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "755116cb-456e-48ad-8dfe-bd72617488d9", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert math.isclose(closest_point_naive(points1), 0.0, rel_tol=1e-9)" + ] + }, + { + "cell_type": "markdown", + "id": "4d521622-f4b7-4859-b501-2583b943d0e7", + "metadata": {}, + "source": [ + "## Exercice 4: display the minimum distance\n", + "\n", + "_Update the previous function to take a single list of points `P`as input and return the closest points and draw the line that connects them._" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "a5902565-9c98-482a-8e63-9cc37214beb2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt\n", + "import random\n", + "\n", + "points_count = 10\n", + "x = [random.gauss(0, 1) for _ in range(points_count)]\n", + "y = [random.gauss(0, 1) for _ in range(points_count)]\n", + "\n", + "def draw_points(x, y):\n", + " color = \"blue\"\n", + " plt.figure(figsize=(10, 7))\n", + " _ = plt.plot(x, y, '.', markersize=14, color=color)" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "e35557a8-54cb-4324-b280-4479835685db", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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WOKudEDFuasQAFANQ2mDAS6PBjh9R+8qBAAwWjmVDgAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAABg2PT0RBw/mX8cSYQQAAJy29vaIlSsj6uoipkzJv65cmY+PBcIIAAA4LRs3RjQ2RmzYEFEu52Plcv68sTF/fbQTRgAAwClrb49obo44ciSiu7vva93d+Xhz8+g/ciSMAACAU7ZuXUSh0P+cQiGfN5oJIwAA4JT09OSnyR1/pOh43d35vCwbmXWdCmEEAACckkOHjl1TNJByOZ8/WgkjAADglEyaFFEsDm5usZjPH62EEQAAcEpqaiKamiJqa/ufV1ubzxvoWqRqEkYAAMApW7164GuHsiyfN5oJIwAA4JQtWBCxfn3EuHEnHjmqrc3H16/P541mwggAADgtTU0Rra0RN9987JqjYjF/3tqavz7aFbJsNN80b+i6urqiVCpFZ2dn1NfXV3s5AACQlJ6e/O5zdXXVv6ZoKG0wwGVSAAAAg1dTEzF5crVXMXROpQMAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgORVPIwefvjhmDt3bkycODEaGxvjxRdf/Mi527Zti0KhcMLjjTfeqPQyAQCAhFU0jJ566qlYvXp13HvvvdHW1hbLli2Lq666Knbv3t3v+958883Yu3dv7+MTn/hEJZcJAAAkrpBlWVapP/ySSy6Jiy66KB555JHesQsuuCCuu+66WLt27Qnzt23bFp/5zGfi/fffj7POOmtQ36NcLke5XO593tXVFbNnz47Ozs6or68/7Z8BAAAYm7q6uqJUKg2qDSp2xOjw4cPR2toay5cv7zO+fPny2LFjR7/vXbhwYcycOTMuv/zy2Lp1a79z165dG6VSqfcxe/bs0147AACQloqF0f79++PIkSMxY8aMPuMzZsyIjo6Ok75n5syZ8dhjj8WmTZvi6aefjnnz5sXll18eL7zwwkd+nzVr1kRnZ2fvY8+ePcP6cwAAAGe+2kp/g0Kh0Od5lmUnjB01b968mDdvXu/zJUuWxJ49e+J73/teXHrppSd9T7FYjGKxOHwLBgAAklOxI0bTpk2LcePGnXB0aN++fSccRerP4sWL46233hru5QEAAPSqWBhNmDAhGhsbY8uWLX3Gt2zZEkuXLh30n9PW1hYzZ84c7uUBAAD0quipdHfeeWc0NzfHokWLYsmSJfHYY4/F7t27Y9WqVRGRXx/0zjvvxE9+8pOIiFi3bl2cc8458clPfjIOHz4cGzZsiE2bNsWmTZsquUwAACBxFQ2jG264Id5777341re+FXv37o358+fHs88+G3PmzImIiL179/b5TKPDhw/HXXfdFe+8805MmjQpPvnJT8YzzzwTV199dSWXCQAAJK6in2NUDUO5VzkAAHDmGhWfYwQAADBWCCMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAGBV6eiIOHsy/wkgTRgAAVFV7e8TKlRF1dRFTpuRfV67Mx2GkCCMAAKpm48aIxsaIDRsiyuV8rFzOnzc25q/DSBBGAABURXt7RHNzxJEjEd3dfV/r7s7Hm5sdOWJkCCMAAKpi3bqIQqH/OYVCPg8qTRgBADDienry0+SOP1J0vO7ufF6Wjcy6SJcwAgBgxB06dOyaooGUy/l8qCRhBADAiJs0KaJYHNzcYjGfD5UkjAAAGHE1NRFNTRG1tf3Pq63N5w10LRKcLmEEAEBVrF498LVDWZbPg0oTRgAAVMWCBRHr10eMG3fikaPa2nx8/fp8HlSaMAIAoGqamiJaWyNuvvnYNUfFYv68tTV/HUZCIcvOrJsfdnV1RalUis7Ozqivr6/2cgAAGKSenvzuc3V1rilieAylDQa43A0AAEZGTU3E5MnVXgWpciodAACQvIqH0cMPPxxz586NiRMnRmNjY7z44ov9zt++fXs0NjbGxIkT49xzz41HH3200ksEAAASV9Eweuqpp2L16tVx7733RltbWyxbtiyuuuqq2L1790nn79q1K66++upYtmxZtLW1xT333BO33357bNq0qZLLBAAAElfRmy9ccsklcdFFF8UjjzzSO3bBBRfEddddF2vXrj1h/te//vX42c9+Fq+//nrv2KpVq6K9vT1aWloG9T3dfAEAAIgYWhtU7IjR4cOHo7W1NZYvX95nfPny5bFjx46TvqelpeWE+VdccUW8/PLL8eGHH570PeVyObq6uvo8AAAAhqJiYbR///44cuRIzJgxo8/4jBkzoqOj46Tv6ejoOOn87u7u2L9//0nfs3bt2iiVSr2P2bNnD88PAAAAJKPiN18oHHcT+izLThgbaP7Jxo9as2ZNdHZ29j727NlzmisGAABSU7HPMZo2bVqMGzfuhKND+/btO+Go0FENDQ0nnV9bWxtnn332Sd9TLBajePRjkgEAAE5BxY4YTZgwIRobG2PLli19xrds2RJLly496XuWLFlywvznn38+Fi1aFOPHj6/UUgEAgMRV9FS6O++8M/71X/81fvSjH8Xrr78ed9xxR+zevTtWrVoVEflpcH/7t3/bO3/VqlXx9ttvx5133hmvv/56/OhHP4rHH3887rrrrkouEwAASFzFTqWLiLjhhhvivffei29961uxd+/emD9/fjz77LMxZ86ciIjYu3dvn880mjt3bjz77LNxxx13xA9/+MOYNWtW/OAHP4gvfvGLlVwmAACQuIp+jlE1+BwjAAAgYpR8jhEAAMBYIYwAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACABglenoiDh7MvwIjSxgBAFRZe3vEypURdXURU6bkX1euzMeBkSGMAACqaOPGiMbGiA0bIsrlfKxczp83NuavA5UnjAAAqqS9PaK5OeLIkYju7r6vdXfn483NjhzBSBBGAABVsm5dRKHQ/5xCIZ8HVJYwAgCogp6e/DS5448UHa+7O5+XZSOzLkiVMAIAqIJDh45dUzSQcjmfD1SOMAIAqIJJkyKKxcHNLRbz+UDlCCMAgCqoqYloaoqore1/Xm1tPm+ga5GA0yOMAACqZPXqga8dyrJ8HlBZwggAoEoWLIhYvz5i3LgTjxzV1ubj69fn84DKEkYAAFXU1BTR2hpx883HrjkqFvPnra3560DlFbLszLr5Y1dXV5RKpejs7Iz6+vpqLwcAYNB6evK7z9XVuaYIhsNQ2mCAy/0AABgpNTURkydXexWQJqfSAQAAyRNGAABA8oQRAACQPGEEAAAkTxgBAADJE0YAAEDyhBEAAJA8YQQAACRPGAEAAMkTRgAAQPKEEQAAkDxhBAAAJE8YAQAAyRNGAABA8oQRAACQPGEEAAAkTxgBAADJE0YAAEDyhBEAAJA8YQQAACRPGAEAAMkTRgAAQPKEEQAAkDxhBAAAJE8YAQAAyRNGAABA8oQRAACQPGEEAAAkTxgBAADJE0YAAEDyKhZG77//fjQ3N0epVIpSqRTNzc3xxz/+sd/33HLLLVEoFPo8Fi9eXKklAgAAREREbaX+4Jtuuin+53/+J5577rmIiPi7v/u7aG5ujp///Of9vu/KK6+MJ554ovf5hAkTKrVEAACAiKhQGL3++uvx3HPPxUsvvRSXXHJJRET8y7/8SyxZsiTefPPNmDdv3ke+t1gsRkNDw6C/V7lcjnK53Pu8q6vr1BcOAAAkqSKn0rW0tESpVOqNooiIxYsXR6lUih07dvT73m3btsX06dPjvPPOi1tvvTX27dvX7/y1a9f2nq5XKpVi9uzZw/IzAAAA6ahIGHV0dMT06dNPGJ8+fXp0dHR85PuuuuqqePLJJ+MXv/hFfP/734+dO3fGZz/72T5HhI63Zs2a6Ozs7H3s2bNnWH4GAAAgHUM6le6+++6L+++/v985O3fujIiIQqFwwmtZlp10/Kgbbrih93/Pnz8/Fi1aFHPmzIlnnnkmrr/++pO+p1gsRrFYHMzyAQAATmpIYXTbbbfFjTfe2O+cc845J37961/H73//+xNe+8Mf/hAzZswY9PebOXNmzJkzJ956662hLBMAAGBIhhRG06ZNi2nTpg04b8mSJdHZ2Rm/+tWv4uKLL46IiF/+8pfR2dkZS5cuHfT3e++992LPnj0xc+bMoSwTAABgSCpyjdEFF1wQV155Zdx6663x0ksvxUsvvRS33nprfP7zn+9zR7rzzz8/Nm/eHBERH3zwQdx1113R0tISv/vd72Lbtm1xzTXXxLRp0+ILX/hCJZYJAAAQERX8gNcnn3wyLrzwwli+fHksX748PvWpT8X69ev7zHnzzTejs7MzIiLGjRsXr776alx77bVx3nnnxYoVK+K8886LlpaWmDp1aqWWCQAAEIUsy7JqL2I4dXV1RalUis7Ozqivr6/2cgAAgCoZShtU7IgRAADAWCGMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAEtfTE3HwYP4VAFIljAAS1d4esXJlRF1dxJQp+deVK/NxAEiNMAJI0MaNEY2NERs2RJTL+Vi5nD9vbMxfB4CUCCOAxLS3RzQ3Rxw5EtHd3fe17u58vLnZkSMA0iKMABKzbl1EodD/nEIhnwcAqRBGAAnp6clPkzv+SNHxurvzeVk2MusCgGoTRgAJOXTo2DVFAymX8/kAkAJhBJCQSZMiisXBzS0W8/kAkAJhBJCQmpqIpqaI2tr+59XW5vMGuhYJAM4UwgggMatXD3ztUJbl8wAgFcIIIDELFkSsXx8xbtyJR45qa/Px9evzeQCQCmEEkKCmpojW1oibbz52zVGxmD9vbc1fB4CUFLLszLoZa1dXV5RKpejs7Iz6+vpqLwdg1Ovpye8+V1fnmiIAzixDaYMBLr8F4ExXUxMxeXK1VwEA1eVUOgAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJInjAAAgORVLIy+/e1vx9KlS6Ouri7OOuusQb0ny7K47777YtasWTFp0qT49Kc/Hb/97W8rtUQAAICIqGAYHT58OL70pS/FV7/61UG/57vf/W48+OCD8dBDD8XOnTujoaEhPve5z8WBAwcqtUwAAIDKhdH9998fd9xxR1x44YWDmp9lWaxbty7uvffeuP7662P+/Pnx4x//OP70pz/Fv/3bv1VqmQAAAKPnGqNdu3ZFR0dHLF++vHesWCzGZZddFjt27PjI95XL5ejq6urzAAAAGIpRE0YdHR0RETFjxow+4zNmzOh97WTWrl0bpVKp9zF79uyKrhMAADjzDCmM7rvvvigUCv0+Xn755dNaUKFQ6PM8y7ITxv6vNWvWRGdnZ+9jz549p/X9AQCA9NQOZfJtt90WN954Y79zzjnnnFNaSENDQ0TkR45mzpzZO75v374TjiL9X8ViMYrF4il9TwAAgIghhtG0adNi2rRpFVnI3Llzo6GhIbZs2RILFy6MiPzOdtu3b48HHnigIt8TAAAgooLXGO3evTteeeWV2L17dxw5ciReeeWVeOWVV+KDDz7onXP++efH5s2bIyI/hW716tXxT//0T7F58+b4zW9+E7fcckvU1dXFTTfdVKllAgAADO2I0VB84xvfiB//+Me9z48eBdq6dWt8+tOfjoiIN998Mzo7O3vnfO1rX4tDhw7F3//938f7778fl1xySTz//PMxderUSi0TAAAgClmWZdVexHDq6uqKUqkUnZ2dUV9fX+3lAAAAVTKUNhg1t+sGAACoFmEEAAAkTxglqKcn4uDB/CsAACCMktLeHrFyZURdXcSUKfnXlSvzcQAASJkwSsTGjRGNjREbNkSUy/lYuZw/b2zMXwcAgFQJowS0t0c0N0ccORLR3d33te7ufLy52ZEjAADSJYwSsG5dRKHQ/5xCIZ8HAAApEkZnuJ6e/DS5448UHa+7O593Zn2qFQAADI4wOsMdOnTsmqKBlMv5fAAASI0wOsNNmhRRLA5ubrGYzwcAgNQIozNcTU1EU1NEbW3/82pr83kDXYsEAABnImGUgNWrB752KMvyeQAAkCJhlIAFCyLWr48YN+7EI0e1tfn4+vX5PAAASJEwSkRTU0Rra8TNNx+75qhYzJ+3tuavAwBAqgpZdmbdoLmrqytKpVJ0dnZGfX19tZczKvX05Hefq6tzTREAAGeuobTBAJfkcyaqqYmYPLnaqwAAgNHDqXQAAEDyhBEAAJA8YQQAACRPGAEAAMkTRgAAQPKEEQAAkDxhBAAAJE8YAQAAyRNGAABA8oQRAACQPGEEAAAkTxgBAADJE0YAAEDyhBEAAJA8YQQAACRPGAEAAMkTRgAAQPKEEQAAkDxhBAAAJE8YAQAAyRNGAABA8oQRAACQPGEEAAAkTxgBAADJE0YAAEDyhBEAAJA8YQQAACRPGAEAAMkTRgAAQPKEEQAAkDxhBAAAJE8YAQAAyRNGAABA8oQRAACQPGEEAAAkTxgBAADJE0YV1tMTcfBg/hUAABidhFGFtLdHrFwZUVcXMWVK/nXlynwcAAAYXYRRBWzcGNHYGLFhQ0S5nI+Vy/nzxsb8dQAAYPQQRsOsvT2iuTniyJGI7u6+r3V35+PNzY4cAQDAaCKMhtm6dRGFQv9zCoV8HgAAMDoIo2HU05OfJnf8kaLjdXfn87JsZNYFAAD0TxgNo0OHjl1TNJByOZ8PAABUnzAaRpMmRRSLg5tbLObzAQCA6hNGw6imJqKpKaK2tv95tbX5vIGuRQIAAEaGMBpmq1cPfO1QluXzAACA0UEYDbMFCyLWr48YN+7EI0e1tfn4+vX5PAAAYHQQRhXQ1BTR2hpx883HrjkqFvPnra356wAAwOhRyLIz66bRXV1dUSqVorOzM+rr66u9nOjpye8+V1fnmiIAABhJQ2mDAW4TwOmqqYmYPLnaqwAAAPrjVDoAACB5wggAAEieMAIAAJInjAAAgOQJIwAAIHnCCAAASJ4wAgAAkieMAACA5AkjAAAgecIIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMAIAAJJXW+0FDLcsyyIioqurq8orAQAAquloExxthP6ccWF04MCBiIiYPXt2lVcCAACMBgcOHIhSqdTvnEI2mHwaQ3p6euLdd9+NqVOnRqFQqPZyBtTV1RWzZ8+OPXv2RH19fbWXwwBsr7HF9hp7bLOxxfYaW2yvscX2Gh5ZlsWBAwdi1qxZUVPT/1VEZ9wRo5qamvjYxz5W7WUMWX19vf/TjyG219hie409ttnYYnuNLbbX2GJ7nb6BjhQd5eYLAABA8oQRAACQPGFUZcViMb75zW9GsVis9lIYBNtrbLG9xh7bbGyxvcYW22tssb1G3hl38wUAAIChcsQIAABInjACAACSJ4wAAIDkCSMAACB5wggAAEieMBph3/72t2Pp0qVRV1cXZ5111qDec8stt0ShUOjzWLx4cWUXSq9T2WZZlsV9990Xs2bNikmTJsWnP/3p+O1vf1vZhRIREe+//340NzdHqVSKUqkUzc3N8cc//rHf99jHRs7DDz8cc+fOjYkTJ0ZjY2O8+OKL/c7fvn17NDY2xsSJE+Pcc8+NRx99dIRWylFD2Wbbtm07YV8qFArxxhtvjOCK0/XCCy/ENddcE7NmzYpCoRA//elPB3yPfax6hrq97F+VJ4xG2OHDh+NLX/pSfPWrXx3S+6688srYu3dv7+PZZ5+t0Ao53qlss+9+97vx4IMPxkMPPRQ7d+6MhoaG+NznPhcHDhyo4EqJiLjpppvilVdeieeeey6ee+65eOWVV6K5uXnA99nHKu+pp56K1atXx7333httbW2xbNmyuOqqq2L37t0nnb9r1664+uqrY9myZdHW1hb33HNP3H777bFp06YRXnm6hrrNjnrzzTf77E+f+MQnRmjFaTt48GAsWLAgHnrooUHNt49V11C311H2rwrKqIonnngiK5VKg5q7YsWK7Nprr63oehjYYLdZT09P1tDQkH3nO9/pHfvf//3frFQqZY8++mgFV8hrr72WRUT20ksv9Y61tLRkEZG98cYbH/k++9jIuPjii7NVq1b1GTv//POzu++++6Tzv/a1r2Xnn39+n7GvfOUr2eLFiyu2Rvoa6jbbunVrFhHZ+++/PwKroz8RkW3evLnfOfax0WMw28v+VXmOGI0R27Zti+nTp8d5550Xt956a+zbt6/aS+Ij7Nq1Kzo6OmL58uW9Y8ViMS677LLYsWNHFVd25mtpaYlSqRSXXHJJ79jixYujVCoN+HdvH6usw4cPR2tra5/9IiJi+fLlH7ltWlpaTph/xRVXxMsvvxwffvhhxdZK7lS22VELFy6MmTNnxuWXXx5bt26t5DI5Dfaxscn+VTnCaAy46qqr4sknn4xf/OIX8f3vfz927twZn/3sZ6NcLld7aZxER0dHRETMmDGjz/iMGTN6X6MyOjo6Yvr06SeMT58+vd+/e/tY5e3fvz+OHDkypP2io6PjpPO7u7tj//79FVsruVPZZjNnzozHHnssNm3aFE8//XTMmzcvLr/88njhhRdGYskMkX1sbLF/VV5ttRdwJrjvvvvi/vvv73fOzp07Y9GiRaf0599www29/3v+/PmxaNGimDNnTjzzzDNx/fXXn9KfmbpKb7OIiEKh0Od5lmUnjDE4g91eESf+vUcM/HdvHxs5Q90vTjb/ZONUzlC22bx582LevHm9z5csWRJ79uyJ733ve3HppZdWdJ2cGvvY2GH/qjxhNAxuu+22uPHGG/udc8455wzb95s5c2bMmTMn3nrrrWH7M1NTyW3W0NAQEflv4mbOnNk7vm/fvhN+M8fgDHZ7/frXv47f//73J7z2hz/8YUh/9/ax4Tdt2rQYN27cCUca+tsvGhoaTjq/trY2zj777IqtldypbLOTWbx4cWzYsGG4l8cwsI+Nffav4SWMhsG0adNi2rRpI/b93nvvvdizZ0+ff3QzNJXcZnPnzo2GhobYsmVLLFy4MCLyc/W3b98eDzzwQEW+55lusNtryZIl0dnZGb/61a/i4osvjoiIX/7yl9HZ2RlLly4d9Pezjw2/CRMmRGNjY2zZsiW+8IUv9I5v2bIlrr322pO+Z8mSJfHzn/+8z9jzzz8fixYtivHjx1d0vZzaNjuZtrY2+9IoZR8b++xfw6yad35I0dtvv521tbVl999/fzZlypSsra0ta2tryw4cONA7Z968ednTTz+dZVmWHThwIPuHf/iHbMeOHdmuXbuyrVu3ZkuWLMn+8i//Muvq6qrWj5GUoW6zLMuy73znO1mpVMqefvrp7NVXX82ampqymTNn2mYj4Morr8w+9alPZS0tLVlLS0t24YUXZp///Of7zLGPVce///u/Z+PHj88ef/zx7LXXXstWr16dTZ48Ofvd736XZVmW3X333Vlzc3Pv/P/+7//O6urqsjvuuCN77bXXsscffzwbP3589h//8R/V+hGSM9Rt9s///M/Z5s2bs//8z//MfvOb32R33313FhHZpk2bqvUjJOXAgQO9/42KiOzBBx/M2trasrfffjvLMvvYaDPU7WX/qjxhNMJWrFiRRcQJj61bt/bOiYjsiSeeyLIsy/70pz9ly5cvz/7iL/4iGz9+fPbxj388W7FiRbZ79+7q/AAJGuo2y7L8lt3f/OY3s4aGhqxYLGaXXnpp9uqrr4784hP03nvvZV/+8pezqVOnZlOnTs2+/OUvn3BrU/tY9fzwhz/M5syZk02YMCG76KKLsu3bt/e+tmLFiuyyyy7rM3/btm3ZwoULswkTJmTnnHNO9sgjj4zwihnKNnvggQeyv/qrv8omTpyY/dmf/Vn2N3/zN9kzzzxThVWn6ejtnI9/rFixIssy+9hoM9TtZf+qvEKW/f+r7AAAABLldt0AAEDyhBEAAJA8YQQAACRPGAEAAMkTRgAAQPKEEQAAkDxhBAAAJE8YAQAAyRNGAABA8oQRAACQPGEEAAAk7/8BL1iEz7Qbq4oAAAAASUVORK5CYII=", + "text/plain": [ + "<Figure size 1000x700 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "draw_points(x, y)" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "6c504c53-d3fc-44a1-904c-49ceff572638", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "closest_point_naive_pair", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def closest_point_naive_pair(P):\n", + " ### BEGIN SOLUTION\n", + " n = len(P)\n", + " min_dist = math.inf\n", + " closest_point1 = None\n", + " closest_point2 = None\n", + "\n", + " for i in range(n):\n", + " for j in range(i + 1, n):\n", + " distance = dist(P[i], P[j])\n", + " if distance < min_dist:\n", + " min_dist = distance\n", + " closest_point1 = P[i]\n", + " closest_point2 = P[j]\n", + "\n", + " ### END SOLUTION\n", + " return closest_point1, closest_point2" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "4266685c-19f9-4d2f-bb71-4d047bb54787", + "metadata": { + "nbgrader": { + "grade": true, + "grade_id": "correct_closest_point_naive_pair", + "locked": true, + "points": 1, + "schema_version": 3, + "solution": false, + "task": false + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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iieMAACA4gkjAACgeMIIAAAonjACAACKJ4wAAIDiCSMAAKB4wggAACieMAIAAIonjAAAgOIJIwAAoHjCCAAAKJ4wAgAAiieMAACA4gkjAACgeMIIAAAoXsPC6IknnkhHR0fa2trS1taWjo6OPPnkkwM+5oILLkhTU1O/2wknnNCoKQIAACRJWhr1jd/1rnflpz/9aW677bYkye///u+no6Mj//Iv/zLg404//fRcf/31fdvjxo1r1BQBAACSNCiMHnzwwdx222351re+leOPPz5J8jd/8zeZN29eHnroocyePfsFH9va2ppp06bt8c/q7u5Od3d333ZXV9fQJw4AABSpIW+lW7VqVdra2vqiKElOOOGEtLW15Z577hnwsStXrsyhhx6aI488MhdeeGEeffTRAcdfffXVfW/Xa2try8yZM/fJ7wAAAJSjIWG0adOmHHrooTvtP/TQQ7Np06YXfNwZZ5yRG2+8MV//+tfzsY99LPfdd1/e8IY39DsitKPLL788nZ2dfbcNGzbsk98BAAAox6DeSnfllVfmqquuGnDMfffdlyRpamra6b6qqna5/znnnntu338fffTROe644zJr1qzccsstefvb377Lx7S2tqa1tXVPpg8AALBLgwqjiy++OOedd96AYw4//PB85zvfyS9+8Yud7vvlL3+ZqVOn7vHPmz59embNmpWHH354MNMEGDa9vcmWLcmECUmzD0QAgAPGoMJoypQpmTJlym7HzZs3L52dnfn2t7+d3/7t306S3Hvvvens7MyJJ564xz/v8ccfz4YNGzJ9+vTBTBNgv1u3LlmyJFm2LOnuTlpbkwULksWLk7lzh3t2AMDuNOTvma985Stz+umn58ILL8y3vvWtfOtb38qFF16Ys846q98V6ebMmZMVK1YkSZ566qlcdtllWbVqVX784x9n5cqVectb3pIpU6bkd3/3dxsxTYB9YtmypL09ueGGOoqS+usNN9T7ly0b3vkBALvXsDd63HjjjXn1q1+d+fPnZ/78+XnNa16TpUuX9hvz0EMPpbOzM0kyZsyY/Pu//3ve9ra35cgjj8z555+fI488MqtWrcrBBx/cqGkC7JV165KOjmTbtqSnp/99PT31/o6OehwAMHI1VVVVDfck9qWurq60tbWls7MzkydPHu7pAKPcokX1kaEdo+j5WlqShQuT5312NQCM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HSl6josvAAAAxRNGAABA8YRRg7W2tuZ//+//ndbW1uGeyqhiXRvDujaGdW0ca9sY1rUxrGtjWNfGKW1tR93FFwAAAAbLESMAAKB4wggAACieMAIAAIonjAAAgOIJIwAAoHjCaC898cQT6ejoSFtbW9ra2tLR0ZEnn3xywMdccMEFaWpq6nc74YQT+o3p7u7O//gf/yNTpkzJpEmT8ta3vjU//elPG/ibjCyDXdetW7fmAx/4QF796ldn0qRJmTFjRt7znvfk5z//eb9xr3/963da+/POO6/Bv83w+sxnPpMjjjgi48ePT3t7e77xjW8MOP6uu+5Ke3t7xo8fn5e//OX53Oc+t9OY5cuX56ijjkpra2uOOuqorFixolHTH7EGs64333xzTjvttLz0pS/N5MmTM2/evHz1q1/tN+bzn//8Ts/Npqam/OpXv2r0rzKiDGZdV65cucs1+8EPftBvnOfr4NZ1V/9GNTU15VWvelXfGM/X5O67785b3vKWzJgxI01NTfnSl76028d4fd0zg11br7F7ZrDrWuJrrDDaS+9617uydu3a3Hbbbbntttuydu3adHR07PZxp59+ejZu3Nh3u/XWW/vdv3jx4qxYsSJf/OIX881vfjNPPfVUzjrrrGzbtq1Rv8qIMth1feaZZ/LAAw/kT/7kT/LAAw/k5ptvzg9/+MO89a1v3WnshRde2G/t//qv/7qRv8qwuummm7J48eJcccUVWbNmTU466aScccYZWb9+/S7HP/LIIznzzDNz0kknZc2aNfnQhz6USy65JMuXL+8bs2rVqpx77rnp6OjIunXr0tHRkXPOOSf33nvv/vq1ht1g1/Xuu+/OaaedlltvvTWrV6/O7/zO7+Qtb3lL1qxZ02/c5MmT+z03N27cmPHjx++PX2lEGOy6Puehhx7qt2aveMUr+u7zfB38un7iE5/ot54bNmzIIYcckne+8539xpX+fH366aczd+7cfOpTn9qj8V5f99xg19Zr7J4Z7Lo+p6jX2Ioh+/73v18lqb71rW/17Vu1alWVpPrBD37wgo87//zzq7e97W0veP+TTz5ZjR07tvriF7/Yt+9nP/tZ1dzcXN122237ZO4j2VDXdUff/va3qyTVT37yk759p5xySvU//+f/3JfTHdF++7d/u7rooov67ZszZ071wQ9+cJfj3//+91dz5szpt+8P/uAPqhNOOKFv+5xzzqlOP/30fmPe9KY3Veedd94+mvXIN9h13ZWjjjqquuqqq/q2r7/++qqtrW1fTfGANNh1vfPOO6sk1RNPPPGC39Pzde+frytWrKiampqqH//4x337PF/7S1KtWLFiwDFeX4dmT9Z2V7zGDmxP1rXE11hHjPbCqlWr0tbWluOPP75v3wknnJC2trbcc889Az525cqVOfTQQ3PkkUfmwgsvzKOPPtp33+rVq7N169bMnz+/b9+MGTNy9NFH7/b7jgZ7s67P19nZmaamprzoRS/qt//GG2/MlClT8qpXvSqXXXZZNm/evK+mPqI8++yzWb16db/nUZLMnz//Bddx1apVO41/05velPvvvz9bt24dcEwJz81kaOu6o97e3mzevDmHHHJIv/1PPfVUZs2alcMOOyxnnXXWTn/tHM32Zl2POeaYTJ8+PaeeemruvPPOfvd5vu798/Xaa6/NG9/4xsyaNavf/pKfr0Ph9XX/8Rq7b5X0GiuM9sKmTZty6KGH7rT/0EMPzaZNm17wcWeccUZuvPHGfP3rX8/HPvax3HfffXnDG96Q7u7uvu87bty4vPjFL+73uKlTpw74fUeLoa7r8/3qV7/KBz/4wbzrXe/K5MmT+/a/+93vzrJly7Jy5cr8yZ/8SZYvX563v/3t+2zuI8ljjz2Wbdu2ZerUqf32D/Q82rRp0y7H9/T05LHHHhtwTAnPzWRo67qjj33sY3n66adzzjnn9O2bM2dOPv/5z+fLX/5yli1blvHjx+e1r31tHn744X06/5FqKOs6ffr0XHPNNVm+fHluvvnmzJ49O6eeemruvvvuvjGer3v3fN24cWO+8pWv5Pd+7/f67S/9+ToUXl/3H6+x+0aJr7Etwz2BkejKK6/MVVddNeCY++67L0nS1NS0031VVe1y/3POPffcvv8++uijc9xxx2XWrFm55ZZbBvw/6bv7viNdo9f1OVu3bs15552X3t7efOYzn+l334UXXtj330cffXRe8YpX5LjjjssDDzyQY489dk9+jQPOjmu2u3Xc1fgd9w/2e45GQ12DZcuW5corr8w///M/9/sDwAknnNDvIiyvfe1rc+yxx+av/uqv8slPfnLfTXyEG8y6zp49O7Nnz+7bnjdvXjZs2JC//Mu/zMknnzyk7zlaDXUNPv/5z+dFL3pRzj777H77PV+Hxutr43mN3XdKfI0VRrtw8cUX7/ZKZYcffni+853v5Be/+MVO9/3yl7/cqZ4HMn369MyaNavvrxbTpk3Ls88+myeeeKLfUaNHH300J5544h5/35Fmf6zr1q1bc8455+SRRx7J17/+9X5Hi3bl2GOPzdixY/Pwww+PujCaMmVKxowZs9NfbR599NEXXMdp06btcnxLS0te8pKXDDhmMM/5A9lQ1vU5N910U9773vfmH//xH/PGN75xwLHNzc35rd/6rWL+mrk36/p8J5xwQm644Ya+bc/Xoa9rVVW57rrr0tHRkXHjxg04trTn61B4fW08r7GNN9pfY72VbhemTJmSOXPmDHgbP3585s2bl87Oznz729/ue+y9996bzs7OQQXM448/ng0bNmT69OlJkvb29owdOzZ33HFH35iNGzfmu9/97gEdRo1e1+ei6OGHH87Xvva1vn9oBvK9730vW7du7Vv70WTcuHFpb2/v9zxKkjvuuOMF13HevHk7jb/99ttz3HHHZezYsQOOOZCfm4MxlHVN6r9iXnDBBfnCF76QN7/5zbv9OVVVZe3ataPyubkrQ13XHa1Zs6bfmnm+Dn1d77rrrvzoRz/Ke9/73t3+nNKer0Ph9bWxvMbuH6P+NXa/X+5hlDn99NOr17zmNdWqVauqVatWVa9+9aurs846q9+Y2bNnVzfffHNVVVW1efPm6o//+I+re+65p3rkkUeqO++8s5o3b171a7/2a1VXV1ffYy666KLqsMMOq772ta9VDzzwQPWGN7yhmjt3btXT07Nff7/hMth13bp1a/XWt761Ouyww6q1a9dWGzdu7Lt1d3dXVVVVP/rRj6qrrrqquu+++6pHHnmkuuWWW6o5c+ZUxxxzzKhd1y9+8YvV2LFjq2uvvbb6/ve/Xy1evLiaNGlS39WlPvjBD1YdHR194//zP/+zmjhxYvW+972v+v73v19de+211dixY6t/+qd/6hvzb//2b9WYMWOqj370o9WDDz5YffSjH61aWlr6XUVwtBvsun7hC1+oWlpaqk9/+tP9nptPPvlk35grr7yyuu2226r/+I//qNasWVMtWrSoamlpqe699979/vsNl8Gu6//9v/+3WrFiRfXDH/6w+u53v1t98IMfrJJUy5cv7xvj+Tr4dX3OwoULq+OPP36X39Pztf73fM2aNdWaNWuqJNXHP/7xas2aNX1XQvX6OnSDXVuvsXtmsOta4musMNpLjz/+ePXud7+7Ovjgg6uDDz64eve7373TZQ2TVNdff31VVVX1zDPPVPPnz69e+tKXVmPHjq1e9rKXVeeff361fv36fo/ZsmVLdfHFF1eHHHJINWHChOqss87aacxoNth1feSRR6oku7zdeeedVVVV1fr166uTTz65OuSQQ6px48ZVv/7rv15dcskl1eOPP75/f7n97NOf/nQ1a9asaty4cdWxxx5b3XXXXX33nX/++dUpp5zSb/zKlSurY445pho3blx1+OGHV5/97Gd3+p7/+I//WM2ePbsaO3ZsNWfOnH4vkqUYzLqecsopu3xunn/++X1jFi9eXL3sZS+rxo0bV730pS+t5s+fX91zzz378TcaGQazrn/+539e/fqv/3o1fvz46sUvfnH1ute9rrrlllt2+p6er4N/HXjyySerCRMmVNdcc80uv5/n6/ZLGb/Q/669vg7dYNfWa+yeGey6lvga21RV/+/MPwAAgEI5xwgAACieMAIAAIonjAAAgOIJIwAAoHjCCAAAKJ4wAgAAiieMAACA4gkjAACgeMIIAAAonjACAACKJ4wAAIDi/f9G6OJAiKBZUQAAAABJRU5ErkJggg==", + "text/plain": [ + "<Figure size 1000x700 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt\n", + "import random\n", + "import math\n", + "\n", + "points_count = 10\n", + "color = \"blue\"\n", + "x = [random.gauss(0, 1) for _ in range(points_count)]\n", + "y = [random.gauss(0, 1) for _ in range(points_count)]\n", + "\n", + "points = list(zip(x, y))\n", + "\n", + "closest_point1, closest_point2 = closest_point_naive_pair(points)\n", + "\n", + "plt.figure(figsize=(10, 7))\n", + "_ = plt.plot(x, y, '.', markersize=14, color=color)\n", + "plt.plot([closest_point1[0], closest_point2[0]], [closest_point1[1], closest_point2[1]], '-', color='red', linewidth=2)\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "id": "bcec1dfc-6ce9-4b08-bc1c-f8d0887aa876", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((0, 0), (1, 1))" + ] + }, + "execution_count": 103, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "points1 = [(0, 0), (1, 1), (3, 3), (5, 5)]\n", + "closest_point_naive_pair(points1)" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "aad82edd-144d-4117-9ee6-485b9e739251", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert closest_point_naive_pair(points1) == ((0, 0), (1, 1))" + ] + }, + { + "cell_type": "markdown", + "id": "52ede988-1834-489d-9cca-e9882e90f9af", + "metadata": {}, + "source": [ + "## Exercice 5: implement the merge sort\n", + "\n", + "0. Find a base case\n", + "1. Finding the mid of the array \n", + "2. Divide the array elements into 2 halves \n", + "3. Sorting the first half and the second half independantly\n", + "4. merge by copying arrays into a final one" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "b835049a-27a7-4d45-b819-b9e4d813fdcf", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "merge_sort", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def merge_sort(arr): \n", + " ### BEGIN SOLUTION\n", + " if len(arr) > 1: \n", + " mid = len(arr) // 2 \n", + " L = arr[:mid]\n", + " R = arr[mid:]\n", + " \n", + " merge_sort(L) \n", + " merge_sort(R)\n", + " \n", + " i = j = k = 0\n", + " \n", + " while i < len(L) and j < len(R): \n", + " if L[i] < R[j]: \n", + " arr[k] = L[i] \n", + " i+=1\n", + " else: \n", + " arr[k] = R[j] \n", + " j+=1\n", + " k+=1\n", + " \n", + " while i < len(L): \n", + " arr[k] = L[i] \n", + " i+=1\n", + " k+=1\n", + " \n", + " while j < len(R): \n", + " arr[k] = R[j] \n", + " j+=1\n", + " k+=1\n", + " \n", + " return arr\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "id": "4ad36fe3-7e9e-4ae8-9df6-f816e24d6c28", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 3, 6]" + ] + }, + "execution_count": 109, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "merge_sort([3, 6, 1])" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "id": "85865859-828f-4a6b-aad8-51ba0de8ac5b", + "metadata": { + "nbgrader": { + "grade": true, + "grade_id": "correct_merge_sort", + "locked": true, + "points": 1, + "schema_version": 3, + "solution": false, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "assert merge_sort([4, 2, 3]) == sorted([4, 2, 3])\n", + "assert merge_sort([7, 2, 1]) == [1, 2, 7]" + ] + }, + { + "cell_type": "markdown", + "id": "e5b7304d-7b64-42df-9465-8f4491525a8b", + "metadata": {}, + "source": [ + "# Exercice 6: organize a schedule\n", + "\n", + "_Propose a greedy algorithm that returns a list of time slots that do not overlap._\n", + "\n", + "In this question you may prioritize the ones that end last, so you may sort by [reverse order](https://docs.python.org/3/howto/sorting.html)." + ] + }, + { + "cell_type": "code", + "execution_count": 175, + "id": "e0ed0841-1255-4bdb-b7f8-e689c2a953af", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "interval_scheduling", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def interval_scheduling(intervals):\n", + " ### BEGIN SOLUTION\n", + "\n", + " intervals.sort(key=lambda x: x[1], reverse=True)\n", + " \n", + " selected_intervals = []\n", + " current_end_time = float('-inf')\n", + " \n", + " for interval in intervals:\n", + " start_time, end_time = interval\n", + " if start_time >= current_end_time:\n", + " selected_intervals.append(interval)\n", + " current_end_time = end_time\n", + " return selected_intervals\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 176, + "id": "d6a50280-f016-4686-8515-1b4a136c0fd9", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[(0, 2), (2, 4)]" + ] + }, + "execution_count": 176, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interval_scheduling([(0, 2), (2, 4), (1, 3)])" + ] + }, + { + "cell_type": "code", + "execution_count": 178, + "id": "bd38d673-077d-44b1-b81c-ac7448f5c01c", + "metadata": { + "nbgrader": { + "grade": true, + "grade_id": "correct_interval_scheduling", + "locked": true, + "points": 1, + "schema_version": 3, + "solution": false, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "assert interval_scheduling([(0, 2), (2, 4), (1, 3)]) == [(0, 2), (2, 4)]" + ] + }, + { + "cell_type": "code", + "execution_count": 179, + "id": "de2c9857-9925-4477-96e4-341fa5bc4ec8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert interval_scheduling([(0, 2), (2, 4), (1,3)]) == [(0, 2), (2, 4)]" + ] + }, + { + "cell_type": "markdown", + "id": "c142c3b3-2f01-48d0-8596-601bb133542b", + "metadata": { + "tags": [] + }, + "source": [ + "Now you may prioritize the ones that are the longest." + ] + }, + { + "cell_type": "code", + "execution_count": 181, + "id": "095836b8-2612-4d58-a9ce-b7968489418c", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "interval_scheduling_longest", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def interval_scheduling_longest(intervals):\n", + " ### BEGIN SOLUTION\n", + " intervals.sort(key=lambda x: x[1] - x[0], reverse=True)\n", + " \n", + " selected_intervals = []\n", + " current_end_time = float('-inf')\n", + " \n", + " for interval in intervals:\n", + " start_time, end_time = interval\n", + " if start_time >= current_end_time:\n", + " selected_intervals.append(interval)\n", + " current_end_time = end_time\n", + " return selected_intervals\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 182, + "id": "c87ae1e2-7497-4920-9597-d1b3cddf8580", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[(0, 4), (5, 7)]" + ] + }, + "execution_count": 182, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interval_scheduling_longest([(0, 4), (3, 5), (5, 7)])" + ] + }, + { + "cell_type": "code", + "execution_count": 183, + "id": "afcf3a69-aa36-4b71-93a8-218337ed88b9", + "metadata": { + "nbgrader": { + "grade": true, + "grade_id": "correct_interval_scheduling_longest", + "locked": true, + "points": 1, + "schema_version": 3, + "solution": false, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "assert interval_scheduling_longest([(0, 4), (3, 5), (5, 7)]) == [(0, 4), (5, 7)]" + ] + }, + { + "cell_type": "markdown", + "id": "a559e4db-9ef2-4d20-bb9f-cb638d8c1f24", + "metadata": {}, + "source": [ + "# Exercice 7: knapsack problem\n", + "\n", + "Propose a greedy solution for the Kanpsack problem defined as follows:\n", + "\n", + "$\\sum_{i=1}^n w_i x_i \\leq W$ and $x_i \\in \\{0,1,2,\\dots,c\\}$\n", + "\n", + "Which selects the item with the least weight that can fit within the knapsack's capacity at each step " + ] + }, + { + "cell_type": "markdown", + "id": "5833bde3-1960-43ad-b140-381ac6dd228c", + "metadata": {}, + "source": [ + "`W:` The maximum weight capacity of the knapsack.\n", + "\n", + "`w:` A list of item weights.\n", + "\n", + "`n:` The number of items available to choose from.\n", + "\n", + "Tip:\n", + "\n", + "- Initialize with all the item indices of the remaining items\n", + "- Start with an empty knapsack of total weight 0\n", + "- Create a loop that selects the best item from the remaining (with least weight)\n", + "- Add the item to the knapsack and remove it from the list of items\n", + "- Return the total weight and list of items used" + ] + }, + { + "cell_type": "code", + "execution_count": 134, + "id": "46947633-3f5f-420b-b416-500a0dab4fcb", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0bf75b2bfe8e2e4c", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def greedy_knapsack(W, w):\n", + " ### BEGIN SOLUTION\n", + " w.sort(reverse=True)\n", + " n = len(w)\n", + " remaining_items = list(range(n))\n", + "\n", + " total_weight = 0\n", + " knapsack = []\n", + "\n", + " while remaining_items and total_weight < W:\n", + " best_weight = float('inf')\n", + " best_item = None\n", + "\n", + " for i in remaining_items:\n", + " if w[i] < best_weight:\n", + " best_weight = w[i]\n", + " best_item = i\n", + "\n", + " if best_item is not None:\n", + " weight = w[best_item]\n", + " knapsack.append(weight)\n", + " total_weight += weight\n", + " remaining_items.remove(best_item)\n", + "\n", + " return total_weight, knapsack\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 135, + "id": "737df134-4ee1-49a9-9034-da3b0267ae84", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total weight: 5 and selected weights: [2, 3]\n" + ] + } + ], + "source": [ + "weights = [5, 3, 4, 2]\n", + "max_weight = 5\n", + "\n", + "result, selected_weights = greedy_knapsack(max_weight, weights)\n", + "print(\"Total weight:\", result, \"and selected weights:\", selected_weights)" + ] + }, + { + "cell_type": "code", + "execution_count": 136, + "id": "a66c6e52-2d60-4423-bc3a-a3dcef38da26", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert greedy_knapsack(5, [2, 3, 4, 5]) == (5, [2, 3])" + ] + }, + { + "cell_type": "markdown", + "id": "37ff64e9-fc2c-4593-b85a-d83982445b9d", + "metadata": {}, + "source": [ + "Propose a dynamic programming solution following the general case:\n", + "\n", + "$\n", + "dp[i][w] = \n", + "\\begin{cases}\n", + "0 & \\text{if } i = 0 \\text{ or } w = 0 \\\\\n", + "dp[i-1][w] & \\text{if } w_i > w \\\\\n", + "\\max(dp[i-1][w], w_i + dp[i-1][w - w_i]) & \\text{otherwise}\n", + "\\end{cases}$" + ] + }, + { + "cell_type": "code", + "execution_count": 137, + "id": "2be5f2f7-d9a5-4cf0-afe4-5a5a54ee0434", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-5dc5571a7aca5b0f", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def dynamic_knapsack(W, wt):\n", + " ### BEGIN SOLUTION\n", + " n = len(wt)\n", + "\n", + " dp = [[0 for _ in range(W + 1)] for _ in range(n + 1)]\n", + "\n", + " for i in range(n + 1):\n", + " for w in range(W + 1):\n", + " if i == 0 or w == 0:\n", + " dp[i][w] = 0\n", + " elif wt[i - 1] <= w:\n", + " dp[i][w] = max(wt[i - 1] + dp[i - 1][w - wt[i - 1]], dp[i - 1][w])\n", + " else:\n", + " dp[i][w] = dp[i - 1][w]\n", + "\n", + " max_weight = dp[n][W]\n", + "\n", + " selected_items = []\n", + " w = W\n", + " for i in range(n, 0, -1):\n", + " if dp[i][w] != dp[i - 1][w]:\n", + " selected_items.append(wt[i - 1])\n", + " w -= wt[i - 1]\n", + "\n", + " return max_weight, selected_items\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 138, + "id": "5adf0e0e-73c4-4656-b546-5b02347f1a28", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total weight: 5 and selected weights: [3, 2]\n" + ] + } + ], + "source": [ + "weights = [2, 3, 4, 5]\n", + "max_weight = 5\n", + "result, selected_weights = dynamic_knapsack(max_weight, weights)\n", + "print(\"Total weight:\", result, \"and selected weights:\", selected_weights)" + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "id": "935873d5-14ee-4b79-9e6a-c692d1c73a9a", + "metadata": {}, + "outputs": [], + "source": [ + "assert dynamic_knapsack(max_weight, weights) == (5, [3, 2])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef953a29-7632-4475-8230-54a8a110d19a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7dd8b57-0d0d-43d0-b228-b9c994043f81", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 155, + "id": "a066e026-8a09-46ca-a919-53bc90c8a308", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 156, + "id": "80c70549-29c1-49ff-a7de-f47dbce004a9", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[(2, 4), (0, 2)]" + ] + }, + "execution_count": 156, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "greedy_intevals([(0, 2), (2, 4), (1,3)])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41e37d95-4e71-42f0-81dd-fd6122fc9023", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}