diff --git a/01-introduction.ipynb b/01-introduction.ipynb deleted file mode 100644 index 5f11e83a5bcebc413720829ae31260e885253252..0000000000000000000000000000000000000000 --- a/01-introduction.ipynb +++ /dev/null @@ -1,2203 +0,0 @@ -{ - "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\" # Expected: 5\n", - "assert add(-1, 1) == 0, \"Test Case 2 Failed\" # Expected: 0\n", - "assert add(0, 0) == 0, \"Test Case 3 Failed\" # Expected: 0\n", - "assert add(10, -5) == 5, \"Test Case 4 Failed\" # Expected: 5\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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", - "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++fejatSsAoGfPnti8eTMWLFhgtRedwWCAwWCodh1ERERk32y6c6TX6xESEoLU1FSL9ampqejfv7/VbUJDQyuUX79+PXr37g2dTldlGdM+a1KvNRcvXgQAODpaHraTkxNKS0urvR8iIiKqA7/+CsTHA2+8oW0ctrb2TkpKEp1OJ4mJiZKdnS1xcXHi5uYmx44dExGR+Ph4iYmJMZc/evSouLq6yuTJkyU7O1sSExNFp9PJypUrzWW2bNkiTk5OkpCQIAcOHJCEhARxdnaWbdu2VbteEZEzZ85IZmamrFmzRgBIUlKSZGZmyqlTp0REpLCwUK6//noJCwuTjIwMOXz4sLz++uvi4OAga9asqdbxs7caERFRLdu7VyQ2VkSnEwFEPD1FLlyo1SpsuX7bnByJiCxYsED8/PxEr9dLcHCwpKWlmT+LjY2V8PBwi/KbNm2SXr16iV6vl06dOsmiRYsq7HPFihUSEBAgOp1OAgMDJTk52aZ6RUSWLVsmACq8Zs+ebS7z888/y7333itt2rQRV1dX6dGjR4Wu/VVhckRERFQLSktFUlNFIiNVQmR6DRgg8tVXIiUltVqdLddvB5G/W0dTteTn58PDwwNGoxHu7u5ah0NERGRfioqAzz8HXn8dMM1k4egI3HcfMHUq0LdvnVRry/XbpgbZRERERDWSnw8sWQK89RZw8qRa5+oKPPooEBcHdO6sZXQWmBwRERFR3Tl5Enj7beC991SCBADe3sCECcCTTwKtW2sbnxVMjoiIiKj27dmjep199hlQXKzW3XSTenT24IOAi4u28VWByRERERHVDhEgNVW1Jyo//M7AgcAzzwBDh6r2RQ0ckyMiIiK6NoWFQFKSSopMM044OgIPPKCSot69tY3PRkyOiIiIqGaMRtWW6O231QCOAODmBjz2mGpk3amTltHVGJMjIiIiss3x4yohWrIEOH9erfPxASZNAsaOBVq10ja+a8TkiIiIiKonM1M1sk5KAkpK1LqgIPXo7J//BBrJXKRMjoiIiKhyIsC6dao90Xffla0fNEglRVFRgIODdvHVASZHREREVFFBgeqG/8YbwL59ap2TEzBypOqOHxysbXx1iMkRERERlTl3Dli8GPjvf4FTp9S65s2Bxx9Xjaw7dtQyunrB5IiIiIiAX35RU3u8/z7w119qna+vamT9xBNAy5ZaRlevmBwRERE1ZTt3qvZEK1aUNbLu3l21Jxo1CtDrtY1PA0yOiIiImprSUmDtWpUUbdxYtv6OO1RSFBHR6BpZ24LJERERUVNRUAB8+qlqZJ2drdY5O6s7RFOnAjffrGl4DQWTIyIiosbuzz9VI+t33gFyc9W6Fi3UgI0TJwIdOmgbXwPD5IiIiKixyslRjawTE4ELF9S6du1Ur7PHHwc8PLSMrsFickRERNTYbN+u2hOtXKnaFwFAz56qPVF0dJNsZG0LJkdERESNQWkp8PXXwGuvAd9/X7Y+IkIlRXfc0aQbWduCyREREZE9u3wZ+OQT1cj6p5/UOmdnNdfZ1KlAjx7axmeHmBwRERHZozNngEWLVCPr06fVOnf3skbW7dtrG58dY3JERERkT44cAebPB5YuBS5dUus6dFCNrB97TCVIdE2YHBEREdmDjAzVyHrVqrJG1jffDDz7LPDAA4BOp2l4jQmTIyIiooaqtBRYvVolRT/8ULY+KkolRbffzkbWdYDJERERUUNz6RLw8ceqkfXPP6t1Oh3w4IOqkXW3btrG18gxOSIiImoo8vKAhQuBd98F/vhDrfPwAJ58EpgwAfD11Ta+JoLJERERkdYOHVKNrD/4oKyRdceOwOTJwKOPqqk+qN4wOSIiItJKeroatPGLLwARtS44WLUnuv9+NV4R1TuedSIiovpUUgJ89ZVqZL11a9n64cPVSNbh4WxkrTEmR0RERPXh4kXgww+BN98EDh9W6/R64KGHVCProCBt4yMzJkdERER16fRp1ch6wQLV4BoAWrVSjazHjwfattU2PqqAyREREVFdOHJEtSf68EM1/xkAdOoETJkCjBkDNG+uaXhUOSZHREREte3jj9UcZ6aeZ717q0bW997LRtZ2gL8hIiKi2lJQoOY4W7xYvQ8PB154AQgLYyNrO8LkiIiIqDYcP66632/frt4//7x6OTlpGxfZjMkRERHRtVq3Tk3tceaMamz9ySfAsGFaR0U15FiTjRYuXAh/f3+4uLggJCQEmzdvrrJ8WloaQkJC4OLigs6dO2Ox6XZjOcnJyQgKCoLBYEBQUBBSUlJsrnfVqlWIjIyEl5cXHBwcsHv3bqvxpKenY9CgQXBzc0PLli0xcOBAXDI9FyYiIqqu0lL12GzoUJUYhYQAu3YxMbJzNidHy5cvR1xcHGbOnInMzEyEhYVh6NChOH78uNXyOTk5GDZsGMLCwpCZmYkZM2Zg4sSJSE5ONpdJT0/HyJEjERMTgz179iAmJgbR0dHIyMiwqd4LFy7g1ltvRUJCQqXxp6enIyoqChEREfjxxx+xfft2jB8/Ho6ONcoTiYioqTpzBhgxApg9W41u/cQTwA8/qB5pZN/ERn369JFx48ZZrAsMDJT4+Hir5adNmyaBgYEW68aOHSv9+vUzv4+OjpaoqCiLMpGRkTJq1Kga1ZuTkyMAJDMzs8Jnffv2lVmzZlk/uGowGo0CQIxGY433QUREdm7HDhE/PxFAxMVFZNkyrSOiq7Dl+m3T7ZLCwkLs3LkTERERFusjIiKwtfwQ6OWkp6dXKB8ZGYkdO3agqKioyjKmfdakXmtOnz6NjIwMtGnTBv3794e3tzfCw8Pxww8/VLpNQUEB8vPzLV5ERNREiQBLlgD9+wO//AJ06QJs2wY88ojWkVEtsik5ysvLQ0lJCby9vS3We3t7Izc31+o2ubm5VssXFxcj7++RQisrY9pnTeq15ujRowCAOXPm4PHHH8fatWsRHByMwYMH49ChQ1a3mTdvHjw8PMyvDh06VLs+IiJqRC5eBP71L/X4rLAQuOsuYMcOoGdPrSOjWlajhjYOV4zVICIV1l2t/JXrq7NPW+u9UmlpKQBg7NixGDNmDHr16oX58+cjICAAS5cutbrN9OnTYTQaza8TJ05Uuz4iImokDh9Wd4s++ABwdAQSEoCUFKBlS60jozpgU1d+Ly8vODk5Vbhbc/r06Qp3dUx8fHyslnd2doanp2eVZUz7rEm91rT9e/6aoCsm97vpppsqbVBuMBhgMBiqXQcRETUyX34JxMYCRiPQpg2QlATcfrvWUVEdsunOkV6vR0hICFJTUy3Wp6amon///la3CQ0NrVB+/fr16N27N3Q6XZVlTPusSb3WdOrUCb6+vjh48KDF+p9//hl+fn7V3g8RETUBxcXA9OnAPfeoxKh/f9VNn4lR42dra++kpCTR6XSSmJgo2dnZEhcXJ25ubnLs2DEREYmPj5eYmBhz+aNHj4qrq6tMnjxZsrOzJTExUXQ6naxcudJcZsuWLeLk5CQJCQly4MABSUhIEGdnZ9m2bVu16xUROXPmjGRmZsqaNWsEgCQlJUlmZqacOnXKXGb+/Pni7u4uK1askEOHDsmsWbPExcVFDh8+XK3jZ281IqIm4PffRQYNUr3RAJG4OJHCQq2jomtgy/Xb5uRIRGTBggXi5+cner1egoODJS0tzfxZbGyshIeHW5TftGmT9OrVS/R6vXTq1EkWLVpUYZ8rVqyQgIAA0el0EhgYKMnJyTbVKyKybNkyAVDhNXv2bIty8+bNk/bt24urq6uEhobK5s2bq33sTI6IiBq5LVtEfH1VUuTmJpKUpHVEVAtsuX47iPzdOpqqJT8/Hx4eHjAajXB3d9c6HCIiqi0iwDvvAFOnqkdqgYHAqlXATTdpHRnVAluu3xwWmoiI6K+/gNGjgUmTVGI0ciTw449MjJooTjxLRERN24EDwH33qZ/OzsAbbwATJgA2DBVDjQuTIyIiaro+/1wN7HjhAuDrC6xYoXqlUZPGx2pERNT0FBYCcXHq8dmFC6p7fmYmEyMCwOSIiIiaml9/VcnQ22+r9/HxwPr1aoBHIvCxGhERNSUbNwKjRgGnTwMeHsCHHwJ33611VNTA8M4RERE1fiLAK68Ad9yhEqMePdSksUyMyAreOSIiosbt3DngkUfUHGmAmidt4ULA1VXLqKgBY3JERESN1549qpv+kSOAXg+8+y7w2GPspk9VYnJERESN04cfAuPGAZcvA35+wMqVQO/eWkdFdoBtjoiIqHG5fBkYO1Y9Srt8GYiKAnbuZGJE1cbkiIiIGo9jx4CwMOC999Sjs7lzgTVrAE9PrSMjO8LHakRE1DisXQs8+CDw559A69bA//t/QGSk1lGRHeKdIyIism8lJcCcOcCwYSoxuuUWYNcuJkZUY7xzRERE9isvD3joIWDdOvX+ySeB+fMBg0HbuMiuMTkiIiL7tH07cP/9wPHjQLNmwP/9HxATo3VU1AjwsRoREdkXEZUI3XabSoyuvx7IyGBiRLWGyREREdmPixdVF/1x44DCQuAf/1DTgHTvrnVk1IjwsRoREdmHQ4fUaNdZWYCTE5CQAEydytGuqdYxOSIioobviy/UnGj5+YC3N7B8ORAernVU1EjxsRoRETVcxcXAv/+tHp/l56t2Rrt2MTGiOsU7R0RE1DDl5gKjRwObNqn3U6aoR2k6naZhUePH5IjIXuXkqFGAPTy0joSo9v3wAxAdDZw6BTRvDixbprrtE9UDPlYjskc//wwEBgKhoWpiTaLGQkQN4jhwoEqMgoJUbzQmRlSPmBwR2aPkZNWN+cAB4PXXtY6GqHacP6/uFk2ZoqYEGT1ajV8UEKB1ZNTEMDkiskf/+1/Z8ksvqUdsRPYsO1vNibZypWpT9O67wKefqkdqRPWMyRGRvcnLA9LT1XKvXuqxWlycpiERXZPPPgP69AEOHgTatwe+/x54+mmOX0SaYXJEZG/WrlXtMnr2BD75BHB2Br76yvJuEpE9KCwEJk4E/vlP4MIFYPBg1U2/Xz+tI6MmjskRkb0xJUHDh6vGqlOmqPcTJwKXLmkXF5EtTp5UYxW98456P3MmsG4dcN112sZFBCZHRPalqEjdOQKAESPUz+eeA9q1U+2OXnlFu9iIquu779Qj4W3bgJYtgdWrgRdfVFOCEDUATI6I7MmWLYDRCHh5qTYagGqwOn++Wk5IAI4c0S4+oqqUlgIvvwxERKi2c716ATt3liX6RA0EkyMie7Jmjfo5bJjl/7Lvvx8YMgQoKAAmTFBtkogakrNngXvuUY/PSkuBf/1LJfudO2sdGVEFTI6I7En59kblOTioths6HfDNN8CXX9Z/bESV2b0b6N1bPT4zGID33wcSE4FmzbSOjMgqJkdE9uLwYeCnn1TvtIiIip8HBADPPquWJ00CLl6s3/iIrFm2TI3kfvQo4O8PbN0KPPqo1lERVYnJEZG9MD1SCwtTjVitmTED6NgROH5cDQ5JpJXLl4HHH1ePzy5fVnc7d+4EgoO1jozoqpgcEdkL0yO1qhqvurkBb7+tll97Tc3BRlTfcnKAW29Vj88cHVWi/tVXQKtWWkdGVC01So4WLlwIf39/uLi4ICQkBJs3b66yfFpaGkJCQuDi4oLOnTtj8eLFFcokJycjKCgIBoMBQUFBSElJsbneVatWITIyEl5eXnBwcMDu3bsrjUlEMHToUDg4OOCLL76o1nETaeb8eSAtTS1f2d7oSnffDQwdqrr9jx/PxtlUv77+GggJUYM5enmpsYtmzFBJEpGdsPnbunz5csTFxWHmzJnIzMxEWFgYhg4diuPHj1stn5OTg2HDhiEsLAyZmZmYMWMGJk6ciOTkZHOZ9PR0jBw5EjExMdizZw9iYmIQHR2NjIwMm+q9cOECbr31ViQkJFz1ON566y04cGh6shepqSrZuf564MYbqy5rapxtMKjtyv2tEdWZkhLg+edV8n72LNC3r0qQ7rhD68iIbCc26tOnj4wbN85iXWBgoMTHx1stP23aNAkMDLRYN3bsWOnXr5/5fXR0tERFRVmUiYyMlFGjRtWo3pycHAEgmZmZVmPavXu3tG/fXk6dOiUAJCUlxWo5a4xGowAQo9FY7W2IrtmYMSKASFxc9bd5/nm1Tbt2IufP111sRH/8ITJkiPq+ASLjx4sUFGgdFZEFW67fNt05KiwsxM6dOxFxRU+ZiIgIbN261eo26enpFcpHRkZix44dKCoqqrKMaZ81qbcyFy9exOjRo/Huu+/Cx8fnquULCgqQn59v8SKqV6WlZY2xbRksLz5e9Q769VfgP/+pm9iIMjJUI+vUVMDVFfj0U3XnUq/XOjKiGrMpOcrLy0NJSQm8vb0t1nt7eyM3N9fqNrm5uVbLFxcXIy8vr8oypn3WpN7KTJ48Gf3798fdd99drfLz5s2Dh4eH+dWhQweb6iO6Zjt3AqdPAy1aqJ5q1dWsGfDf/6rlN98EsrPrJj5qmkSAhQvVd/LECfW498cf1SSyRHauRi3krmyrIyJVtt+xVv7K9dXZp631Xumrr77Chg0b8NZbb1V7m+nTp8NoNJpfJ06cqPa2RLXC1EstIsL2/42PGAHceSdQXMzG2VR7LlwAYmKAp59WbeHuvx/Yvh3o2lXryIhqhU3JkZeXF5ycnCrcrTl9+nSFuzomPj4+Vss7OzvD09OzyjKmfdakXms2bNiAI0eOoGXLlnB2doazszMA4L777sPAgQOtbmMwGODu7m7xIqpX1enCX5W33wZcXICNG4Hly2svLmqafv5ZNbb+9FM1hc2bbwKffw7w30ZqRGxKjvR6PUJCQpCammqxPjU1Ff3797e6TWhoaIXy69evR+/evaHT6aosY9pnTeq1Jj4+Hnv37sXu3bvNLwCYP38+li1bVu39ENWb335TPX4cHFT3/Jrw91ddqQFgyhSA7eaoppKT1TQg+/cDPj4q4Z48WX0/iRoTW1t7JyUliU6nk8TERMnOzpa4uDhxc3OTY8eOiYhIfHy8xMTEmMsfPXpUXF1dZfLkyZKdnS2JiYmi0+lk5cqV5jJbtmwRJycnSUhIkAMHDkhCQoI4OzvLtm3bql2viMiZM2ckMzNT1qxZIwAkKSlJMjMz5dSpU5UeD9hbjRqyJUtU75++fa9tP5cuiVx/vdrXlCm1Exs1HUVFIlOnlvVGGzBApIp/V4kaIluu3zYnRyIiCxYsED8/P9Hr9RIcHCxpaWnmz2JjYyU8PNyi/KZNm6RXr16i1+ulU6dOsmjRogr7XLFihQQEBIhOp5PAwEBJTk62qV4RkWXLlgmACq/Zs2dXeixMjqhBu/tudTF64YVr39c336h9OTmJ7N177fujpqG0VCQ6uiwxevZZlSwR2Rlbrt8OImyhaYv8/Hx4eHjAaDSy/RHVrcuXAU9PNYHsrl1Ar17Xvs/77gNWrVI9jNLS+DiEru7//g8YNw7Q6YCkJODee7WOiKhGbLl+czx3ooZq0yaVGPn6AjffXDv7nD9fjUWzeTPwySe1s09qvPbvB+Li1PK8eUyMqMlgckTUUJUf+LG27vB07Ag895xafuYZ4Ny52tkvNT6XLgEjR6o7mFFRquE1URPB5IioIRIp68J/tYlmbTVlChAQoAaWfP752t03NR5Tp6o7R97ewAcfcOJYalL4bSdqiLKzgWPH1OSxgwfX7r71euDdd9XyggXA30NaEJmtWgUsWqSWP/5YJUhETQiTI6KGyHTXaNAgwM2t9vd/xx1AdLSat+2pp9RPIgA4fhx49FG1PG0aMGSItvEQaYDJEVFDVJOJZm31xhsq8UpPBz78sO7qIftRXAw8+KBqi9anD/Dii1pHRKQJJkdEDc2ffwJbtqjl2m5vVF779sCcOWp52jTg7Nm6q4vsw4svAj/8oCY5/uwz1X2fqAlickTU0Kxdqx5zdesG+PnVbV2TJgFBQUBeHjBzZt3WRQ1bWhrwn/+o5f/7P6BzZ23jIdIQkyOihuZaJ5q1hU6nGmUDwOLFwM6ddV8nNTxnzgAPPaSS8kceAUaP1joiIk0xOSJqSIqL1Z0joH6SIwAYOBD45z/V8AFsnN30iKgG2CdPAjfeCLzzjtYREWmOyRFRQ5Kertr+tG4N9OtXf/W+/rpqZ/Ljj0BiYv3VS9pbtAj48ks1xENSEtC8udYREWmOyRFRQ2J6pDZ0KODkVH/1tm0LvPCCWo6PV22QqPHbu1cNCgoAr75aO/P3ETUCTI6IGpL6bG90pfHjge7dVW+5GTPqv36qXxcvAqNGAQUFqlfkxIlaR0TUYDA5ImoocnLUyNhOTkBkZP3X7+xc1jj7/feBjIz6j4HqT1wccOCAumu4bFntzd9H1AgwOSJqKEwDP956K9CqlTYxhIUBDz9c1ji7pESbOKhurVgBLFmiEqJPPgGuu07riIgaFCZHRA2Flo/Uynv1VcDDA9i1S413Q43LsWPA44+r5enT1RQ1RGSByRFRQ/DXX8DGjWpZ6+TI27ts2oiZM4HTp7WNh2pPcbEatsFoVL0hTSOkE5EFJkdEDcG33wKFhWpU4sBAraMBnnxS9Vw6d071XqPGYc4cNVyEuzunByGqApMjoobA1N5o+PCG0TDWyamscfayZWVzvZH92rgRePlltbxkCdCpk6bhEDVkTI6ItFZaWpYcaf1IrbzQUDVyMgA8/bR6JEP2KS9PTQ8iAjz2GBAdrXVERA0akyMirWVmAqdOAW5uQHi41tFYmjdP9ZzbswdYuFDraKgmRIAxY4DfflOPbN96S+uIiBo8JkdEWjP1UouIAAwGbWO50nXXqQQJAJ57DsjN1TYest0776jvmMGgpgdxc9M6IqIGj8kRkdYa4iO18h57DOjdG8jPB559VutoyBa7d5f9zl5/HejZU9NwiOwFkyMiLeXmAtu3q+Vhw7SNpTJOTuqRmmnAwO+/1zoiqo4LF9T0IIWFwF13qXZjRFQtTI6ItPT11+pn796Aj4+2sVTllluAJ55Qy089BRQVaRsPXd3EicDBg0C7dsDSpQ2jFySRnWByRKSlhjIqdnW89BLg6Qns36/asVDDlZRUlhB9+qn6vRFRtTE5ItJKQQGQmqqW7SE58vQEXnlFLc+eDfz6q7bxkHVHjwJjx6rlWbMaXg9IIjvA5IhIK99/r6YN8fFRo1HbgzFj1LQTf/0FPPOM1tHQlYqK1PQg+flqAuPnn9c6IiK7xOSISCumR2rDhwOOdvKn6OioRs52dFSPbjZs0DoiKu/554GMDKBlS/U4zdlZ64iI7JKd/ItM1MiI2Fd7o/KCg9Xca4DqAVVYqG08pHz7bdljz/ffB/z8tI2HyI4xOSLSwsGDqm2IXg/ccYfW0djuxRfVAJE//cQRlxuC06eBmBiVdI8dC9x3n9YREdk1JkdEWjDdNRo4EGjeXNNQaqRlS+C119Ty3LnAiROahtOklZYCjzyixszq2hWYP1/riIjsHpMjIi3Y6yO18h5+GLjtNuDiRWDKFK2jabrefhv45hvAxUW1A2vWTOuIiOwekyOi+nb2LPDDD2p5+HBtY7kWDg6qcbaTE7ByJbB+vdYRNT07dwL//rdanj8f6NZN23iIGgkmR0T1bf16oKQECAoCOnfWOppr06MHMGGCWh4/Xo3dRPXj/Hk1PUhREfCPf5SNbURE14zJEVF9K9+FvzGYM0eN1XTokJrclOrH+PHA4cNAhw6qdxqnByGqNTVKjhYuXAh/f3+4uLggJCQEmzdvrrJ8WloaQkJC4OLigs6dO2Px4sUVyiQnJyMoKAgGgwFBQUFISUmxud5Vq1YhMjISXl5ecHBwwO7duy0+//PPPzFhwgQEBATA1dUVHTt2xMSJE2E0Gm0/CUQ1UVJSNp+aPbc3Ks/Doywpeukl4NgxTcNpEj75BPjoIzXe1KefAq1bax0RUaNic3K0fPlyxMXFYebMmcjMzERYWBiGDh2K48ePWy2fk5ODYcOGISwsDJmZmZgxYwYmTpyI5ORkc5n09HSMHDkSMTEx2LNnD2JiYhAdHY2MjAyb6r1w4QJuvfVWJCQkWI3lt99+w2+//YbXX38dWVlZ+OCDD7B27Vo8+uijtp4GoprZtg3480/V26t/f62jqT3//KeapuLSJSAuTutoGrfDh8vGmZo9GwgL0zYeosZIbNSnTx8ZN26cxbrAwECJj4+3Wn7atGkSGBhosW7s2LHSr18/8/vo6GiJioqyKBMZGSmjRo2qUb05OTkCQDIzM696PJ9//rno9XopKiq6alkREaPRKADEaDRWqzyRhfh4EUBk9GitI6l9+/aJODur4/vf/7SOpnEqKBDp3Vud4wEDRIqLtY6IyG7Ycv226c5RYWEhdu7ciYiICIv1ERER2Lp1q9Vt0tPTK5SPjIzEjh07UFRUVGUZ0z5rUm91GY1GuLu7w7mSYfYLCgqQn59v8SKqsTVr1M/G0t6ovK5dy+4aTZyo7iJR7Zo5E9ixQz1G+/RT1VOQiGqdTclRXl4eSkpK4O3tbbHe29sbubm5VrfJzc21Wr64uBh5eXlVljHtsyb1VseZM2fwn//8B2Or6OUxb948eHh4mF8dOnSocX3UxP3yC5CVpdqJREVpHU3deP55wNdXjf796qtaR9O4rFtX1rZr6VKgfXtt4yFqxGrUINvhil4RIlJh3dXKX7m+Ovu0td6q5OfnY/jw4QgKCsLs2bMrLTd9+nQYjUbz6wRHAqaaMt016t8f8PTUNpa60qJF2QjN8+apJImuXW6uGnQTUPPZ3X23tvEQNXI2JUdeXl5wcnKqcLfm9OnTFe7qmPj4+Fgt7+zsDM+/LxCVlTHtsyb1VuX8+fOIiopC8+bNkZKSAp1OV2lZg8EAd3d3ixdRjTSGUbGr44EH1HxxBQXq8drf/xmiGiotBWJj1fxp3buXTdtCRHXGpuRIr9cjJCQEqampFutTU1PRv5KeN6GhoRXKr1+/Hr179zYnJZWVMe2zJvVWJj8/HxEREdDr9fjqq6/g4uJi0/ZENXLhArBhg1pu7MmRgwPwzjuATqfulq1erXVE9u2NN9TAoc2acXoQovpia2vvpKQk0el0kpiYKNnZ2RIXFydubm5y7NgxERGJj4+XmJgYc/mjR4+Kq6urTJ48WbKzsyUxMVF0Op2sXLnSXGbLli3i5OQkCQkJcuDAAUlISBBnZ2fZtm1btesVETlz5oxkZmbKmjVrBIAkJSVJZmamnDp1SkRE8vPzpW/fvtK9e3c5fPiwnDp1yvwqrmavD/ZWoxr56ivVw8jPT6S0VOto6oepZ56fn8iFC1pHY59+/LGsB+B772kdDZFds+X6bXNyJCKyYMEC8fPzE71eL8HBwZKWlmb+LDY2VsLDwy3Kb9q0SXr16iV6vV46deokixYtqrDPFStWSEBAgOh0OgkMDJTk5GSb6hURWbZsmQCo8Jo9e7aIiGzcuNHq5wAkJyenWsfO5Ihq5Ikn1AXu6ae1jqT+/PWXSIcO6rhnzdI6GvtjNIp07qzO3wMPNJ2kmqiO2HL9dhBhgwBb5Ofnw8PDwzwEANFViagpHn79Vc2e3lh7qlmzahVw332AXg/s2wfccIPWEdkHEeChh4D/9/8APz9g9241cCgR1Zgt12/OrUZU1/bsUYmRqyswcKDW0dSvf/wDiIwECgvVXGD8v1j1fPSRSoycnIDPPmNiRFTPmBwR1TVTL7U77gCaWgcAU+NsvV41Kl61SuuIGr6ff1bd9QHghReA0FBt4yFqgpgcEdW1ptKFvzI33ABMm6aW4+JUzz2yrqAAGDVKnaPbbwf+/W+tIyJqkpgcEdWl06eBH39Uy8OGaRuLlqZPBzp1Ak6eBP7zH62jabimTwcyM9UgoZ98wulBiDTC5IioLn3zjWpnExwMtGundTTacXUF3n5bLb/xBnDggLbxNERff102uvgHH6hpWIhIE0yOiOqS6ZFaY5xo1lZ33aUeLRYXAxMmsHF2eadOqVGwAWDSpKb7CJaogWByRFRXCgvVZKEAL3Ymb7+tGqV/9x3w+edaR9MwlJYCMTFAXh5w883AK69oHRFRk8fkiKiubN4MnD8PtGkD9O6tdTQNQ+fOql0NAEyZos5PU/fqqypZdHVV04MYDFpHRNTkMTkiqitr1qifw4cDjvxTM5s2DejSBfjtN2DuXK2j0da2bcCsWWr53XeBgABt4yEiAEyOiOoO2xtZ5+Kixj4CgLfeUiNnN0VGIzB6NFBSon4+8ojWERHR35gcEdWFn38GDh1SM9MPGaJ1NA3P0KHAPfeoxODpp5te42wRYOxY4NgxwN8fWLRIDZhJRA0CkyOiumC6axQeDnAOPuveegto1gz4/ns1VUZTsmwZsHw54Oyspgfx8NA6IiIqh8kRUV1o6qNiV4efX1l7m6lT1WOmpuDAATWUAQC8+CLQt6+28RBRBUyOiGqb0ah6qgFsb3Q1U6cCN94I/P47MHu21tHUvcuXVfuiixfVXHvPPqt1RERkBZMjotq2fr0a6DAgALj+eq2jadgMhrLG2e+8A+zZo208dW3aNHWM110HfPQRezESNVD8yySqbXykZpuICOD++9VgiE8/rX42RqtXlyWCH34ItG2rbTxEVCkmR0S1qaREzZEFMDmyxfz5gJsbsGUL8PHHWkdT+379FRgzRi1PmaJ66xFRg8XkiKg2bd+upoHw8ABuvVXraOxH+/bA88+r5WefBc6e1Tae2lRSAjz0EHDmDBASAsybp3VERHQVTI6IapPpkVpkpBrjiKovLg646Sbgjz+A557TOpraM28esGkT0Ly56rav12sdERFdBZMjotrE9kY1p9erKTQANSjirl3axlMbtmwB5sxRywsXAjfcoGk4RFQ9TI6IasuJE6onkoMD25TU1KBBwKhRqlH2U0/Zd+Pss2eBf/6z7LFaTIzWERFRNTE5IqotpobYoaGAl5e2sdizN95Qj6AyMoClS7WOpmZEgMcfB44fV8M5LFyodUREZAMmR0S1hRPN1g5fX2DuXLUcH68aMtubJUuA5GTV7uyzz4AWLbSOiIhswOSIqDZcvAh8+61aZnujazdhAtCtm0qMZszQOhrb7N8PTJqklufNA3r31jYeIrIZkyOi2rBxo5oaokMHoHt3raOxfzodsGCBWl6yBPjxR23jqa5Ll1SbqcuXVY/FyZO1joiIaoDJEVFtWLNG/RwxQjXIpms3YIBqxCyiGmeXlGgd0dU98wywbx/g7a1Gweb0IER2iX+5RNdKhO2N6sqrrwLu7sDOneoOUkOWklLW8Pqjj1SCRER2ickR0bXKylLd+Js1U13Rqfb4+AAvvqiWZ8xQA0Q2RCdOAI8+qpanTVPzxRGR3WJyRHStTHeNBg9WCRLVriefBG6+WY0bFB+vdTQVFRcDDz6o4rvlFuA//9E6IiK6RkyOiK5V+fZGVPucncsaZy9dCqSnaxvPlV56Cdi8WXXX5/QgRI0CkyOia5GXV3axHjZM21gas/79y2a1f+opdbemIfj+e+CFF9Ty4sVAly7axkNEtYLJEdG1+OYb1SC7Z0/VjZ/qTkIC0LIlsHu3SkS09uef6nFaaSnwyCNqqhAiahSYHBFdC040W3/atAFeflktz5oF/P67drGIqAbYJ08CN94IvPOOdrEQUa1jckRUU0VFwLp1apnJUf144gkgJAQwGlWvMK0sXgx88YVqX5SUpOaCI6JGg8kRUU1t2aIu0l5eqpcS1T0nJzWWkIODGkto8+b6jyErq2zk61deAXr1qv8YiKhOMTkiqinTI7Vhw9RFm+pHnz7AY4+p5aeeUnfw6svFi2p6kIICNeCnaQ41ImpUapQcLVy4EP7+/nBxcUFISAg2X+V/b2lpaQgJCYGLiws6d+6MxVYaUyYnJyMoKAgGgwFBQUFISUmxud5Vq1YhMjISXl5ecHBwwO7duyvso6CgABMmTICXlxfc3Nxw11134eTJk7adACKA7Y20NG8e0Lq1mqrj3Xfrr97Jk4HsbKBtW2DZMk4VQ9RI2ZwcLV++HHFxcZg5cyYyMzMRFhaGoUOH4vjx41bL5+TkYNiwYQgLC0NmZiZmzJiBiRMnIjk52VwmPT0dI0eORExMDPbs2YOYmBhER0cjIyPDpnovXLiAW2+9FQkJCZXGHxcXh5SUFCQlJeGHH37AX3/9hREjRqDEHuZtoobj8GHg4EE1Bg9HQ65/np6q9xoAzJ4N/PZb3de5ciXw3nsqIfr4Y+C66+q+TiLShtioT58+Mm7cOIt1gYGBEh8fb7X8tGnTJDAw0GLd2LFjpV+/fub30dHREhUVZVEmMjJSRo0aVaN6c3JyBIBkZmZarD937pzodDpJSkoyr/v111/F0dFR1q5dazX+KxmNRgEgRqOxWuWpkXrrLRFA5PbbtY6k6SopEenTR/0eRo+u27qOHRNp2VLVNX163dZFRHXCluu3TXeOCgsLsXPnTkRc8T/liIgIbN261eo26enpFcpHRkZix44dKPq7rUBlZUz7rEm91uzcuRNFRUUW+/H19UW3bt0q3U9BQQHy8/MtXkR8pNYAODqWNc7+7DNg48a6qae4WI1hdO4c0K8fMHdu3dRDRA2GTclRXl4eSkpK4H3FbNPe3t7Izc21uk1ubq7V8sXFxcjLy6uyjGmfNam3slj0ej1atWpV7f3MmzcPHh4e5lcHDvRH+flAWppaZnKkrZAQNfcaADz9dN00zp47F9i6FXB3B/7f/wN0utqvg4galBo1yHa4ohGiiFRYd7XyV66vzj5trbe6qtrP9OnTYTQaza8TJ05cc31k51JT1UX4hhvUAICkrRdfVMMpHDgAvPVW7e570yY1dxqg2hv5+9fu/omoQbIpOfLy8oKTk1OFuyynT5+ucFfHxMfHx2p5Z2dneHp6VlnGtM+a1FtZLIWFhTh79my192MwGODu7m7xoiaOE802LK1aAa++qpbnzlWjVteGvDw1PYhpNOyRI2tnv0TU4NmUHOn1eoSEhCA1NdVifWpqKvr37291m9DQ0Arl169fj969e0P39+3pysqY9lmTeq0JCQmBTqez2M+pU6ewb98+m/ZDTVhpaVlyNHy4trFQmdhYNTnthQvAlCnXvj8R4F//Ur3gAgOBt9++9n0Skf2wtbV3UlKS6HQ6SUxMlOzsbImLixM3Nzc5duyYiIjEx8dLTEyMufzRo0fF1dVVJk+eLNnZ2ZKYmCg6nU5WrlxpLrNlyxZxcnKShIQEOXDggCQkJIizs7Ns27at2vWKiJw5c0YyMzNlzZo1AkCSkpIkMzNTTp06ZS4zbtw4ad++vXz77beya9cuGTRokPTs2VOKi4urdfzsrdbEZWSoHkstWogUFGgdDZWXmSni6Kh+P+vXX9u+/vtftR+DQWT37loJj4i0Zcv12+bkSERkwYIF4ufnJ3q9XoKDgyUtLc38WWxsrISHh1uU37Rpk/Tq1Uv0er106tRJFi1aVGGfK1askICAANHpdBIYGCjJyck21SsismzZMgFQ4TV79mxzmUuXLsn48eOldevW0qxZMxkxYoQcP3682sfO5KiJe+45ddG8/36tIyFrJk5Uv58bbxS5fLlm+9i9W0SvV/t5553ajY+INGPL9dtB5O/W0VQt+fn58PDwgNFoZPujpigkBNi1C/jgA/UohxoWoxEICAB+/x14+WVg+nTbtr9wQf2ODx4E7rpLTS7LUbCJGgVbrt+cW42oun79VSVGDg7A0KFaR0PWeHgAr7+ulv/zH+CXX2zbftIklRi1awcsXcrEiKiJYnJEVF1ff61+9ukDtGmjbSxUuQcfBAYMAC5dUnOhVdfy5UBiokqIPvlETVFCRE0SkyOi6uKo2PbBwQFYsABwcgJSUoBvvrn6Njk5wBNPqOVZs4CBA+s0RCJq2JgcEVXH5cvAt9+qZSZHDV+3buoRGQBMmKB+f5UpKgJGj1Yjn996K/D88/UTIxE1WEyOiKpj0ybg4kXVFqVnT62joeqYMwfw9QWOHAFee63ycrNnAxkZQMuWwKefAs7O9RUhETVQTI6IqsP0SG34cDbStRctWgBvvKGWX35ZPTq70nffAQkJavn99wE/v/qLj4gaLCZHRFcjwvZG9mrkSOD229VjNdNjNpM//gAeekj9fseOBe67T5sYiajBYXJEdDXZ2apLuIsLMHiw1tGQLUyNs52dgdWr1QtQ08A88giQmwt07QrMn69pmETUsDA5Iroa012j228HXF21jYVsd9NNZfOtTZyouvj/979qaAYXFyApCWjWTNsYiahBYXJEdDV8pGb/nnsOaN8eOHYMGDMGmDZNrZ8/X/VsIyIqh8kRUVXOnAG2blXLw4drGwvVXPPmZY/Oli9X3ff/8Q/V1oiI6ApMjoiqsm6dap/SvTt7Mtm7++4DIiLUcocOqncaex4SkRVMjoiqUr4LP9k3Bwc1X9q4cer32rq11hERUQPF0c6IKlNcXDb1BNsbNQ7t2gGLFmkdBRE1cLxzRFSZrVuBc+fUHYZ+/bSOhoiI6gmTI6LKmB6pDRumJjElIqImgckRUWXWrFE/2d6IiKhJYXJEZM3Ro2pkbCcnIDJS62iIiKgeMTkissZ01+i224BWrbSNhYiI6hWTIyJrOCo2EVGTxeSI6Ep//QVs2qSWmRwRETU5TI6IrvTtt0BhIdC5MxAQoHU0RERUz5gcEV2p/CM1Ti9BRNTkMDkiKq+0tKwxNh+pERE1SUyOiMrLzARyc9Us7gMGaB0NERFpgMkRUXmmR2pDhgAGg7axEBGRJpgcEZXHLvxERE0ekyMik1OngB071PKwYdrGQkREmmFyRGTyzTfq5y23AD4+2sZCRESaYXJEZGJ6pMaJZomImjQmR0QAUFAArF+vltneiIioSWNyRAQAaWnAhQtA27ZAr15aR0NERBpickQElA38OHw44Mg/CyKipoxXASIRYPVqtcz2RkRETR6TI6KffgJycgC9HrjjDq2jISIijTE5IjL1Urv9djVtCBERNWk1So4WLlwIf39/uLi4ICQkBJs3b66yfFpaGkJCQuDi4oLOnTtj8eLFFcokJycjKCgIBoMBQUFBSElJsbleEcGcOXPg6+uLZs2aYeDAgdi/f79FmdzcXMTExMDHxwdubm4IDg7GypUra3AWqNHgRLNERFSe2CgpKUl0Op0sWbJEsrOzZdKkSeLm5ia//PKL1fJHjx4VV1dXmTRpkmRnZ8uSJUtEp9PJypUrzWW2bt0qTk5O8vLLL8uBAwfk5ZdfFmdnZ9m2bZtN9SYkJEiLFi0kOTlZsrKyZOTIkdK2bVvJz883l7njjjvklltukYyMDDly5Ij85z//EUdHR9m1a1e1jt9oNAoAMRqNtp46aoj+/FPEyUkEEDl6VOtoiIiojthy/bY5OerTp4+MGzfOYl1gYKDEx8dbLT9t2jQJDAy0WDd27Fjp16+f+X10dLRERUVZlImMjJRRo0ZVu97S0lLx8fGRhIQE8+eXL18WDw8PWbx4sXmdm5ubfPTRRxb7ad26tbz//vuVHnN5TI4amc8+U4lRUJDWkRARUR2y5fpt02O1wsJC7Ny5ExERERbrIyIisHXrVqvbpKenVygfGRmJHTt2oKioqMoypn1Wp96cnBzk5uZalDEYDAgPD7eI7bbbbsPy5cvx559/orS0FElJSSgoKMDAgQOtxl9QUID8/HyLFzUinGiWiIiuYFNylJeXh5KSEnh7e1us9/b2Rm5urtVtcnNzrZYvLi5GXl5elWVM+6xOvaafV4tt+fLlKC4uhqenJwwGA8aOHYuUlBR06dLFavzz5s2Dh4eH+dWhQwer5cgOlZSUzafG5IiIiP5WowbZDg4OFu9FpMK6q5W/cn119lkbZWbNmoWzZ8/i22+/xY4dOzBlyhQ88MADyMrKshr79OnTYTQaza8TJ05UepxkZ7ZtA/78E2jVCggN1ToaIiJqIJxtKezl5QUnJ6cKd4lOnz5d4Y6NiY+Pj9Xyzs7O8PT0rLKMaZ/Vqdfn71nUc3Nz0bZtW6tljhw5gnfffRf79u1D165dAQA9e/bE5s2bsWDBAqu96AwGAwwGQxVnheyW6ZFaVBTgbNOfAhERNWI23TnS6/UICQlBamqqxfrU1FT079/f6jahoaEVyq9fvx69e/eGTqersoxpn9Wp19/fHz4+PhZlCgsLkZaWZi5z8eJFddBXTA/h5OSE0tLSq58AalzY3oiIiKyxtbW3qUt9YmKiZGdnS1xcnLi5ucmxY8dERCQ+Pl5iYmLM5U1d+SdPnizZ2dmSmJhYoSv/li1bxMnJSRISEuTAgQOSkJBQaVf+yuoVUV35PTw8ZNWqVZKVlSWjR4+26MpfWFgo119/vYSFhUlGRoYcPnxYXn/9dXFwcJA1a9ZU6/jZW62ROHZM9VJzdBQ5c0braIiIqI7VaVd+EZEFCxaIn5+f6PV6CQ4OlrS0NPNnsbGxEh4eblF+06ZN0qtXL9Hr9dKpUydZtGhRhX2uWLFCAgICRKfTSWBgoCQnJ9tUr4jqzj979mzx8fERg8EgAwYMkKysLIsyP//8s9x7773Spk0bcXV1lR49elTo2l8VJkeNxIIFKjkKC9M6EiIiqge2XL8dRP5uHU3Vkp+fDw8PDxiNRri7u2sdDtXUsGGqp1pCAvDvf2sdDRER1TFbrt+cW42angsXgA0b1DLbGxER0RWYHFHT8913QEEB0KkTEBSkdTRERNTAMDmipqf8RLNVjM9FRERNE5MjalpEyrrwDx+ubSxERNQgMTmipmX3buC33wBXV6CS+fSIiKhpY3JETYvprtGQIYCLi7axEBFRg8TkiJqW8u2NiIiIrGByRE3H778DP/6olocN0zYWIiJqsJgcUdPxzTeqQXZwMODrq3U0RETUQDE5oqaDE80SEVE1MDmipqGwEFi/Xi0zOSIioiowOaKmYfNm4Px5wNsbCAnROhoiImrAmBxR02B6pDZsGODIrz0REVWOVwlq/ESA1avVMh+pERHRVTA5osbv55+BI0cAnU4N/khERFQFJkfU+JkGfhw4EGjRQtNQiIio4WNyRI0fJ5olIiIbMDmixu3cOdVTDWB7IyIiqhYmR9S4rV8PFBcDgYFAly5aR0NERHaAyRE1bpxoloiIbMTkiBqvkhLg66/VMtsbERFRNTE5osbrxx+BvDzAwwO49VatoyEiIjvB5IgaL1MvtagoNcYRERFRNTA5osaL7Y2IiKgGmBxR43TiBLBnD+DgoO4cERERVROTI2qcTHeNQkMBLy9tYyEiIrvC5IgaJ1N7Iz5SIyIiGzE5osbn4kXgu+/UMpMjIiKyEZMjanw2bgQuXwY6dAC6ddM6GiIisjNMjqjxKf9IzcFB21iIiMjuMDmixkWE7Y2IiOiaMDmixiUrCzh5EmjWDLj9dq2jISIiO8TkiBoX012jwYNVgkRERGQjJkfUuPCRGhERXSMmR9R4/PEHsG2bWh4+XNtYiIjIbjE5osZj7VrVIPvmm4H27bWOhoiI7FSNkqOFCxfC398fLi4uCAkJwebNm6ssn5aWhpCQELi4uKBz585YvHhxhTLJyckICgqCwWBAUFAQUlJSbK5XRDBnzhz4+vqiWbNmGDhwIPbv319hP+np6Rg0aBDc3NzQsmVLDBw4EJcuXbLxLFCDY3qkxrtGRER0DWxOjpYvX464uDjMnDkTmZmZCAsLw9ChQ3H8+HGr5XNycjBs2DCEhYUhMzMTM2bMwMSJE5GcnGwuk56ejpEjRyImJgZ79uxBTEwMoqOjkZGRYVO9r776Kt588028++672L59O3x8fDBkyBCcP3/eoq6oqChERETgxx9/xPbt2zF+/Hg4OvImml0rKlJ3jgC2NyIiomsjNurTp4+MGzfOYl1gYKDEx8dbLT9t2jQJDAy0WDd27Fjp16+f+X10dLRERUVZlImMjJRRo0ZVu97S0lLx8fGRhIQE8+eXL18WDw8PWbx4sXld3759ZdasWdU5VKuMRqMAEKPRWON9UB3YsEEEELnuOpHiYq2jISKiBsaW67dNt0sKCwuxc+dOREREWKyPiIjA1q1brW6Tnp5eoXxkZCR27NiBoqKiKsuY9lmdenNycpCbm2tRxmAwIDw83Fzm9OnTyMjIQJs2bdC/f394e3sjPDwcP/zwQ6XHXFBQgPz8fIsXNUCmR2rDhgFOTtrGQkREds2m5CgvLw8lJSXw9va2WO/t7Y3c3Fyr2+Tm5lotX1xcjLy8vCrLmPZZnXpNP6sqc/ToUQDAnDlz8Pjjj2Pt2rUIDg7G4MGDcejQIavxz5s3Dx4eHuZXhw4drJYjja1Zo37ykRoREV2jGjW0cbhivioRqbDuauWvXF+dfV5rmdLSUgDA2LFjMWbMGPTq1Qvz589HQEAAli5dajX26dOnw2g0ml8nTpyo9DhJI4cOAQcPAs7OwJAhWkdDRER2ztmWwl5eXnBycqpwl+j06dMV7tiY+Pj4WC3v7OwMT0/PKsuY9lmden18fACoO0ht27a1Wsa0PigoyGI/N910U6UNyg0GAwwGg9XPqIEw3TUaMADw8NA2FiIisns23TnS6/UICQlBamqqxfrU1FT079/f6jahoaEVyq9fvx69e/eGTqersoxpn9Wp19/fHz4+PhZlCgsLkZaWZi7TqVMn+Pr64uDBgxb7+fnnn+Hn51etc0ANEEfFJiKi2mRra++kpCTR6XSSmJgo2dnZEhcXJ25ubnLs2DEREYmPj5eYmBhz+aNHj4qrq6tMnjxZsrOzJTExUXQ6naxcudJcZsuWLeLk5CQJCQly4MABSUhIEGdnZ9m2bVu16xURSUhIEA8PD1m1apVkZWXJ6NGjpW3btpKfn28uM3/+fHF3d5cVK1bIoUOHZNasWeLi4iKHDx+u1vGzt1oDYzSK6HSqp9rPP2sdDRERNVC2XL9tTo5ERBYsWCB+fn6i1+slODhY0tLSzJ/FxsZKeHi4RflNmzZJr169RK/XS6dOnWTRokUV9rlixQoJCAgQnU4ngYGBkpycbFO9Iqo7/+zZs8XHx0cMBoMMGDBAsrKyKuxn3rx50r59e3F1dZXQ0FDZvHlztY+dyVEDs3KlSoxuuEHrSIiIqAGz5frtIPJ362iqlvz8fHh4eMBoNMLd3V3rcGjMGOCDD4DJk4E339Q6GiIiaqBsuX5zWGiyX6WlwNdfq2W2NyIiolrC5Ijs144dwOnTgLs7cNttWkdDRESNBJMjsl+mXmoREYBer20sRETUaDA5IvvFLvxERFQHmByRffr1VyAzE3BwAIYO1ToaIiJqRJgckX0yNcTu2xdo00bbWIiIqFFhckT2yfRIbfhwbeMgIqJGh8kR2Z9Ll4Bvv1XLbG9ERES1jMkR2Z9Nm4CLF4F27YCePbWOhoiIGhkmR2R/1qxRP0eMUA2yiYiIahGTI7IvImxvREREdYrJEdmX/fuBX34BXFyAwYO1joaIiBohJkdkX0x3jQYNAlxdtY2FiIgaJSZHZF/KtzciIiKqA0yOyH6cOQNs3aqW2d6IiIjqCJMjsh9r1wKlpUD37kDHjlpHQ0REjRSTI7IfnGiWiIjqAZMjsg/FxerOEcDkiIiI6hSTI7IPW7cC584Bnp5qslkiIqI6wuSI7IPpkdrQoYCTk7axEBFRo8bkqCF5/XVg716to2iY2N6IiIjqCZOjhmLvXuDZZ9VEqnfdBWzbpnVEDcfRo8CBA+qOUWSk1tEQEVEjx+SooXB1BUaOVBOprl4NhIaq6TG++07NJ9aUmQZ+DAsDWrbUNBQiImr8mBw1FNdfDyQlAT/9BIwZAzg7Axs2AHfcoRKl1aubbpLEiWaJiKgeMTlqaG68EVi6FDhyBBg/Xk2wmpGhHrX17KkSqJISraOsP+fPA5s2qWW2NyIionrA5Kih6tgReOcd4Ngx4N//Blq0ALKygNGjgcBAIDERKCzUOsq69+236ji7dAECArSOhoiImgAmRw2dtzeQkAD88gvwwgtA69bA4cPAY4+phOG//wUuXtQ6yrpTfqJZBwdtYyEioiaByZG9aNUKeO45lSS98QbQti1w8iQwaRLQqZNKoIxGraOsXaWlZckR2xsREVE9YXJkb5o3B6ZMUd3bFy1SidEffwDTpwN+fiqBysvTOsrasWsXkJurjnnAAK2jISKiJoLJkb1ycQHGjQMOHQI++gi46SZ15+jFF1WSNGUK8OuvWkd5bUy91CIiAINB21iIiKjJYHJk75ydgZgYYN8+IDkZCA5WbZDmzwc6dwbGjlV3mexR+fZGRERE9YTJUWPh6Ajcey+wY4eavT4sTPXyeu89NTxATAywf7/WUVbfqVPqWAA1nxoREVE9YXLU2Dg4qCk2vv9evaKi1LhIn3wCdOtWlkA1dF9/rX7ecgvg46NtLERE1KQwOWrMwsKAb75RydB996nEKSVFJRymBKqhjrrNiWaJiEgjTI6agpAQYOVK1S7p4YfVBK7r1wPh4WUJVENKkgoKgNRUtczkiIiI6hmTo6YkKAj48EPVw23cOECvB7ZsAYYNK0ugSku1jhJISwMuXFBjOfXqpXU0RETUxNQoOVq4cCH8/f3h4uKCkJAQbN68ucryaWlpCAkJgYuLCzp37ozFixdXKJOcnIygoCAYDAYEBQUhJSXF5npFBHPmzIGvry+aNWuGgQMHYn8ljZBFBEOHDoWDgwO++OKL6h98Y+Dvr8ZIyskBpk4F3NyAzEzggQeArl1VAlVUpF185Sea5ajYRERUz2xOjpYvX464uDjMnDkTmZmZCAsLw9ChQ3H8+HGr5XNycjBs2DCEhYUhMzMTM2bMwMSJE5GcnGwuk56ejpEjRyImJgZ79uxBTEwMoqOjkZGRYVO9r776Kt588028++672L59O3x8fDBkyBCcP3++QlxvvfUWHJr6hdfXF3j9dTXq9vPPAy1bAj/9BDzyiOrhtmgRcPly/cYkwvZGRESkLbFRnz59ZNy4cRbrAgMDJT4+3mr5adOmSWBgoMW6sWPHSr9+/czvo6OjJSoqyqJMZGSkjBo1qtr1lpaWio+PjyQkJJg/v3z5snh4eMjixYstttu9e7e0b99eTp06JQAkJSXlKkddxmg0CgAxGo3V3sZuGI0ir7wi0qaNiEpTRHx8RF57TSQ/v35iyM5W9RoMIufP10+dRETU6Nly/bbpzlFhYSF27tyJiIgIi/URERHYunWr1W3S09MrlI+MjMSOHTtQ9Pejm8rKmPZZnXpzcnKQm5trUcZgMCA8PNwitosXL2L06NF499134VONLuIFBQXIz8+3eDVa7u7AtGnAsWPAO+8AHTqo6TuefVaNuj13LvDnn3Ubg+mu0e23q2lDiIiI6plNyVFeXh5KSkrg7e1tsd7b2xu5ublWt8nNzbVavri4GHl/zwFWWRnTPqtTr+nn1WKbPHky+vfvj7vvvrtaxzxv3jx4eHiYXx06dKjWdnatWTNg/Hjg8GFg6VL1iO3sWWDOHJUkTZumkqa6UL69ERERkQZq1CD7yrY6IlJl+x1r5a9cX519XmuZr776Chs2bMBbb71VaaxXmj59OoxGo/l14sSJam9r9/R6YMwYIDsbWL4c6NkT+Osv4LXX1IS3Tz+t2ivVlj//VL3nACZHRESkGZuSIy8vLzg5OVW4S3T69OkKd2xMfHx8rJZ3dnaGp6dnlWVM+6xOvaZHZFWV2bBhA44cOYKWLVvC2dkZzs7OAID77rsPAwcOtBq/wWCAu7u7xavJcXICoqNVj7b//Q8IDVVjES1cCFx/vUqgDh689nrWrVOjeXftqnrUERERacCm5Eiv1yMkJASppgH6/paamor+/ftb3SY0NLRC+fXr16N3797Q6XRVljHtszr1+vv7w8fHx6JMYWEh0tLSzGXi4+Oxd+9e7N692/wCgPnz52PZsmW2nIqmycFB3dHZsgXYsAG44w6guBj44APgpptUAvX3Oa0RTjRLREQNga2tvZOSkkSn00liYqJkZ2dLXFycuLm5ybFjx0REJD4+XmJiYszljx49Kq6urjJ58mTJzs6WxMRE0el0snLlSnOZLVu2iJOTkyQkJMiBAwckISFBnJ2dZdu2bdWuV0QkISFBPDw8ZNWqVZKVlSWjR4+Wtm3bSn4VPa3A3mrXZts2kbvvLuvdBogMGyayZYtt+ykqEmndWm3//fd1EioRETVdtly/bU6OREQWLFggfn5+otfrJTg4WNLS0syfxcbGSnh4uEX5TZs2Sa9evUSv10unTp1k0aJFFfa5YsUKCQgIEJ1OJ4GBgZKcnGxTvSKqO//s2bPFx8dHDAaDDBgwQLKysqo8FiZHtWTvXpHRo0UcHcuSpPBwkfXrRUpLr7795s1qm1atVKJERERUi2y5fjuINKRJtRq+/Px8eHh4wGg0Ns32R1dz+DDwyiuWo2zfcgswYwZw112AYyVPcuPj1Xb//Cfw6af1Fy8RETUJtly/Obca1a7rrweWLAGOHgUmTVLDAmzfDvzjH0CPHirxKS6uuB3bGxERUQPB5IjqRvv2wFtvqQElZ8xQA0zu3w889BAQGKgSqIICVfbYMWDfPnVXKTJSw6CJiIiYHFFda9MGeOklNR7Siy8CXl7AkSPAE08AXbqoBOrzz1XZW28FWrfWNFwiIiImR1Q/WrYEZs5Ud4nmzwfatQN+/RWYPBn4979VGT5SIyKiBoDJEdUvNzcgLk7dPXrvPaBz57LP7rxTs7CIiIhMnLUOgJoogwF4/HE1uvaXX6qpSm66SeuoiIiImByRxpydgfvu0zoKIiIiMz5WIyIiIiqHyRERERFROUyOiIiIiMphckRERERUDpMjIiIionKYHBERERGVw+SIiIiIqBwmR0RERETlMDkiIiIiKofJEREREVE5TI6IiIiIymFyRERERFQOkyMiIiKicpy1DsDeiAgAID8/X+NIiIiIqLpM123TdbwqTI5sdP78eQBAhw4dNI6EiIiIbHX+/Hl4eHhUWcZBqpNCkVlpaSl+++03tGjRAg4ODhaf5efno0OHDjhx4gTc3d01itD+8LzVDM9bzfC82Y7nrGZ43mqmrs6biOD8+fPw9fWFo2PVrYp458hGjo6OaN++fZVl3N3d+YdQAzxvNcPzVjM8b7bjOasZnreaqYvzdrU7RiZskE1ERERUDpMjIiIionKYHNUig8GA2bNnw2AwaB2KXeF5qxmet5rhebMdz1nN8LzVTEM4b2yQTURERFQO7xwRERERlcPkiIiIiKgcJkdERERE5TA5IiIiIiqHyRERERFROUyOatHChQvh7+8PFxcXhISEYPPmzVqHpJk5c+bAwcHB4uXj42P+XEQwZ84c+Pr6olmzZhg4cCD2799vsY+CggJMmDABXl5ecHNzw1133YWTJ0/W96HUqe+//x533nknfH194eDggC+++MLi89o6T2fPnkVMTAw8PDzg4eGBmJgYnDt3ro6Prm5c7Zw98sgjFb57/fr1syjT1M4ZAMybNw+33HILWrRogTZt2uCee+7BwYMHLcrw+2apOueM37eKFi1ahB49ephHuA4NDcU333xj/twuvmdCtSIpKUl0Op0sWbJEsrOzZdKkSeLm5ia//PKL1qFpYvbs2dK1a1c5deqU+XX69Gnz5wkJCdKiRQtJTk6WrKwsGTlypLRt21by8/PNZcaNGyft2rWT1NRU2bVrl9x+++3Ss2dPKS4u1uKQ6sTXX38tM2fOlOTkZAEgKSkpFp/X1nmKioqSbt26ydatW2Xr1q3SrVs3GTFiRH0dZq262jmLjY2VqKgoi+/emTNnLMo0tXMmIhIZGSnLli2Tffv2ye7du2X48OHSsWNH+euvv8xl+H2zVJ1zxu9bRV999ZWsWbNGDh48KAcPHpQZM2aITqeTffv2iYh9fM+YHNWSPn36yLhx4yzWBQYGSnx8vEYRaWv27NnSs2dPq5+VlpaKj4+PJCQkmNddvnxZPDw8ZPHixSIicu7cOdHpdJKUlGQu8+uvv4qjo6OsXbu2TmPXypUX+to6T9nZ2QJAtm3bZi6Tnp4uAOSnn36q46OqW5UlR3fffXel2zT1c2Zy+vRpASBpaWkiwu9bdVx5zkT4fauuVq1ayfvvv2833zM+VqsFhYWF2LlzJyIiIizWR0REYOvWrRpFpb1Dhw7B19cX/v7+GDVqFI4ePQoAyMnJQW5ursX5MhgMCA8PN5+vnTt3oqioyKKMr68vunXr1mTOaW2dp/T0dHh4eKBv377mMv369YOHh0ejPZebNm1CmzZtcOONN+Lxxx/H6dOnzZ/xnClGoxEA0Lp1awD8vlXHlefMhN+3ypWUlCApKQkXLlxAaGio3XzPmBzVgry8PJSUlMDb29tivbe3N3JzczWKSlt9+/bFRx99hHXr1mHJkiXIzc1F//79cebMGfM5qep85ebmQq/Xo1WrVpWWaexq6zzl5uaiTZs2Ffbfpk2bRnkuhw4dik8//RQbNmzAG2+8ge3bt2PQoEEoKCgAwHMGqDYfU6ZMwW233YZu3boB4PftaqydM4Dft8pkZWWhefPmMBgMGDduHFJSUhAUFGQ33zPna94DmTk4OFi8F5EK65qKoUOHmpe7d++O0NBQdOnSBR9++KG5sWJNzldTPKe1cZ6slW+s53LkyJHm5W7duqF3797w8/PDmjVrcO+991a6XVM6Z+PHj8fevXvxww8/VPiM3zfrKjtn/L5ZFxAQgN27d+PcuXNITk5GbGws0tLSzJ839O8Z7xzVAi8vLzg5OVXIVk+fPl0hO26q3Nzc0L17dxw6dMjca62q8+Xj44PCwkKcPXu20jKNXW2dJx8fH/z+++8V9v/HH380iXPZtm1b+Pn54dChQwB4ziZMmICvvvoKGzduRPv27c3r+X2rXGXnzBp+3xS9Xo/rr78evXv3xrx589CzZ0+8/fbbdvM9Y3JUC/R6PUJCQpCammqxPjU1Ff3799coqoaloKAABw4cQNu2beHv7w8fHx+L81VYWIi0tDTz+QoJCYFOp7Moc+rUKezbt6/JnNPaOk+hoaEwGo348ccfzWUyMjJgNBqbxLk8c+YMTpw4gbZt2wJouudMRDB+/HisWrUKGzZsgL+/v8Xn/L5VdLVzZg2/b9aJCAoKCuzne3bNTbpJRMq68icmJkp2drbExcWJm5ubHDt2TOvQNDF16lTZtGmTHD16VLZt2yYjRoyQFi1amM9HQkKCeHh4yKpVqyQrK0tGjx5ttStn+/bt5dtvv5Vdu3bJoEGDGl1X/vPnz0tmZqZkZmYKAHnzzTclMzPTPAREbZ2nqKgo6dGjh6Snp0t6erp0797dbrsJV3XOzp8/L1OnTpWtW7dKTk6ObNy4UUJDQ6Vdu3ZN+pyJiDz55JPi4eEhmzZtsuh2fvHiRXMZft8sXe2c8ftm3fTp0+X777+XnJwc2bt3r8yYMUMcHR1l/fr1ImIf3zMmR7VowYIF4ufnJ3q9XoKDgy26ezY1pnErdDqd+Pr6yr333iv79+83f15aWiqzZ88WHx8fMRgMMmDAAMnKyrLYx6VLl2T8+PHSunVradasmYwYMUKOHz9e34dSpzZu3CgAKrxiY2NFpPbO05kzZ+TBBx+UFi1aSIsWLeTBBx+Us2fP1tNR1q6qztnFixclIiJCrrvuOtHpdNKxY0eJjY2tcD6a2jkTEavnDIAsW7bMXIbfN0tXO2f8vln3r3/9y3wtvO6662Tw4MHmxEjEPr5nDiIi137/iYiIiKhxYJsjIiIionKYHBERERGVw+SIiIiIqBwmR0RERETlMDkiIiIiKofJEREREVE5TI6IiIiIymFyRERERFQOkyMiIiKicpgcEREREZXD5IiIiIionP8PEVaXDT1Gc0wAAAAASUVORK5CYII=", - "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/04-programming-strategies.ipynb b/04-programming-strategies.ipynb deleted file mode 100644 index 4d336d8ab1496013ea85e3f39fe7a2d63a06ee63..0000000000000000000000000000000000000000 --- a/04-programming-strategies.ipynb +++ /dev/null @@ -1,781 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "90ae134f", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# UE5 Fundamentals of Algorithms\n", - "## Lecture 4: Programming strategies\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": "b3ce5394", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "# Programming strategies\n", - "\n", - "> A programming strategy are algorithms aimed at solving a specific problem in a precise manner.\n", - "\n", - "Examples of Strategies:\n", - "\n", - "- **Divide and Conquer:** Divide a problem into simpler sub-problems, solve the sub-problems, and then combine the solutions to solve the original problem.\n", - "\n", - "- **Dynamic Programming:** Solve a problem by breaking it down into sub-problems, calculating and memorizing the results of sub-problems to avoid unnecessary recomputation.\n", - "\n", - "- **Greedy Algorithm:** Make a series of choices that seem locally optimal at each step to find a solution, with the hope that the result will be globally optimal as well.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "19c91bb3", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# Divide and conquer\n", - "\n", - "> The **Divide and Conquer** strategy involves breaking a complex problem into smaller, similar subproblems, solving them recursively, and then combining their solutions to address the original problem efficiently.\n", - "\n", - "1. **Divide:** Divide the original problem into subproblems of the same type.\n", - "\n", - "2. **Conquer:** Solve each of these subproblems recursively.\n", - "\n", - "3. **Combine:** Combine the answers appropriately.\n", - "\n", - "_It is very close to the recursive approach_\n" - ] - }, - { - "cell_type": "markdown", - "id": "c84c6bdd", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "\n", - "### Examples of divide and conquer algorithms:\n", - "\n", - "- Binary search\n", - "- Quick sort and merge sort\n", - "- Map Reduce\n", - "- Others: Fast multiplication (Karatsuba)" - ] - }, - { - "cell_type": "markdown", - "id": "fa188fcb", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Binary search\n", - "\n", - "_Given a sorted list, find or insert a specific value while keeping the order._\n", - "\n", - "<img src=\"figures/recherche-dichotomique.png\" style=\"width:500px\">" - ] - }, - { - "cell_type": "markdown", - "id": "ba8f8ab0", - "metadata": {}, - "source": [ - "## Quick sort\n", - "\n", - "<img src=\"figures/quicksort.png\" style=\"height:400px\">" - ] - }, - { - "cell_type": "markdown", - "id": "2ba80ce1", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Merge sort\n", - "\n", - "<img src=\"figures/tri-fusion.png\" style=\"width:500px\">" - ] - }, - { - "cell_type": "markdown", - "id": "68bf754c", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Map reduce\n", - "\n", - "<img src=\"figures/Mapreduce.png\" style=\"width:700px\">" - ] - }, - { - "cell_type": "markdown", - "id": "5e864f8b", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Map reduce (without map reduce..)" - ] - }, - { - "cell_type": "markdown", - "id": "32ad76f1", - "metadata": {}, - "source": [ - "_Calculate the sum of squares values from a list of numerical values._" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "0df729b8", - "metadata": {}, - "outputs": [], - "source": [ - "data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "27efe14e", - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[(1, 1), (4, 2), (9, 3), (16, 4), (25, 5), (36, 6), (49, 7), (64, 8), (81, 9), (100, 10)]\n" - ] - } - ], - "source": [ - "result = {}\n", - "for num in data:\n", - " square = num * num\n", - " if square in result:\n", - " result[square] += num\n", - " else:\n", - " result[square] = num\n", - "\n", - "final_result = list(result.items())\n", - "\n", - "print(final_result)" - ] - }, - { - "cell_type": "markdown", - "id": "68e3c445", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Map reduce (Python)\n", - "\n", - "_Using a \"combine\" step._" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "e4773ca8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[(1, [1]), (2, [4]), (3, [9]), (4, [16]), (5, [25]), (6, [36]), (7, [49]), (8, [64]), (9, [81]), (10, [100])]\n" - ] - } - ], - "source": [ - "data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", - "\n", - "def mapper(numbers):\n", - " result = []\n", - " for num in numbers: # calculate the squares\n", - " result.append((num, num * num))\n", - " return result\n", - "\n", - "def reducer(pairs):\n", - " result = {}\n", - " for key, value in pairs: # sums the squares\n", - " if key in result:\n", - " result[key] += value \n", - " else:\n", - " result[key] = value\n", - " return result.items()\n", - "\n", - "chunk_size = 2\n", - "chunks = [data[i:i+chunk_size] for i in range(0, len(data), chunk_size)]\n", - "\n", - "mapped_data = [mapper(chunk) for chunk in chunks] # map\n", - "\n", - "grouped_data = {}# combine\n", - "for chunk in mapped_data:\n", - " for key, value in chunk:\n", - " if key in grouped_data:\n", - " grouped_data[key].append(value)\n", - " else:\n", - " grouped_data[key] = [value]\n", - "\n", - "reduced_data = [reducer(list(grouped_data.items()))] # reduce\n", - "\n", - "# Flatten the result\n", - "final_result = []\n", - "for item in reduced_data:\n", - " final_result.extend(item)\n", - "\n", - "print(final_result)" - ] - }, - { - "cell_type": "markdown", - "id": "d6f04202", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Discussion \n", - "\n", - "- Similarities with recursion by dividing a problem in a sub-problem\n", - "\n", - "- But with a combination step\n", - "\n", - "- Can be implemented in a non-recursive way\n", - "\n", - "- Often a $n log(n)$ complexity" - ] - }, - { - "cell_type": "markdown", - "id": "67fdbeea", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# Greedy algorithms\n", - "\n", - "> Algorithms that make an optimal choice first, and then stick to it\n", - "\n", - "Examples:\n", - "\n", - "- Change-making problem\n", - "- Knapsack problem" - ] - }, - { - "cell_type": "markdown", - "id": "1464792e", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Change-making problem\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "19852a60", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Coin change (pseudo code)\n", - "\n", - "1. Sort the coins\n", - "\n", - "2. Initialize a variable to count coins used\n", - "\n", - "3. Substrack the number of coins used (if limited)\n", - "\n", - "4. Continue this process until amount becomes zero.\n" - ] - }, - { - "cell_type": "markdown", - "id": "8c5e5909", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Coin change (Python)\n", - "\n", - "_Greedy solution._" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "a5a4633a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "3\n" - ] - } - ], - "source": [ - "def coin_change_greedy(coins, amount):\n", - " coins.sort(reverse=True) # Sort the coins in descending order\n", - " \n", - " coin_count = 0\n", - " remaining_amount = amount\n", - " \n", - " for coin in coins:\n", - " while remaining_amount >= coin:\n", - " remaining_amount -= coin\n", - " coin_count += 1\n", - " \n", - " if remaining_amount == 0:\n", - " return coin_count\n", - " else:\n", - " return -1\n", - "\n", - "coins = [1, 2, 5]\n", - "amount = 11\n", - "print(coin_change_greedy(coins, amount)) # 3 (11 = 5 + 5 + 1)" - ] - }, - { - "cell_type": "markdown", - "id": "97703194", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Huffman coding" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6372a46e", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "99042d46", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5cd75ee6", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "id": "1d7cfa02", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# Dynamic programming\n", - "\n", - "> **Dynamic programming** involves breaking down a problem into subproblems, *solving* these subproblems, and *combining* their solutions to obtain the solution to the original problem. The steps are as follows:\n", - "\n", - "1. Characterize the structure of an optimal solution.\n", - "2. Define the value of an optimal solution recursively.\n", - "3. Reconstruct the optimal solution from the computations.\n", - "\n", - "Notes :\n", - "- Applies to problems with optimal substructure.\n", - "- Also applies to problems where solutions are often interrelated (distinguishing it from divide and conquer).\n", - "- Utilizes a memoization approach, involving storing an intermediate solution (e.g., in a table).\n" - ] - }, - { - "cell_type": "markdown", - "id": "d8a79a3d", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "### Examples of dynamic programming algorithms\n", - "\n", - "- Fibonacci Sequence\n", - "- Rod Cutting\n", - "- Shortest Path Finding\n", - "- Sequence Alignment, Longest Subsequence Finding" - ] - }, - { - "cell_type": "markdown", - "id": "bedc74a2", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Fibonnacci (reminder)\n", - "\n", - "To calculate the $n$-th number in the Fibonacci sequence, which is determined as follows:\n", - "\n", - "latex\n", - "Copy code\n", - "$fib(n) = fib(n-1) + fib(n-2)$, $n \\in \\mathbb{N}$\n", - "Where the sequence starts with 1, 1, and then continues as 2, 3, 5, 8, 13, 21, and so on, to find the 9th number ($n = 9$).\n", - "\n", - "Let's calculate the 9th Fibonacci number step by step:\n", - "\n", - "$fib(1) = 1$\n", - "\n", - "$fib(2) = 1$\n", - "\n", - "$fib(3) = fib(2) + fib(1) = 1 + 1 = 2$\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "4ccca6dd", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Fibonnacci (naive)" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "57411e8e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[0, 1, 1, 2, 3, 5, 8, 13]\n" - ] - } - ], - "source": [ - "def fib(n):\n", - " if n < 2:\n", - " return n\n", - " else:\n", - " return fib(n - 1) + fib(n - 2)\n", - "print(list(map(fib, range(8))))" - ] - }, - { - "cell_type": "markdown", - "id": "b79f68cf", - "metadata": {}, - "source": [ - "Call tree (for $n = 6$) :" - ] - }, - { - "cell_type": "markdown", - "id": "b3bca648", - "metadata": {}, - "source": [ - "<img src=\"figures/fibonacci-tree.png\" style=\"width:400px\">" - ] - }, - { - "cell_type": "markdown", - "id": "a3a595ab", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Fibonnacci (optimized using a lookup table)" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "5b1d2bb3", - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Le nombre de fibonacci est 8\n" - ] - } - ], - "source": [ - "def fib(n, lookup): \n", - " \n", - " # Cas d'arrêt\n", - " if n == 0 or n == 1 : \n", - " lookup[n] = n \n", - " \n", - " # On calcule la valeur si pas déjà calculée\n", - " if lookup[n] is None: \n", - " lookup[n] = fib(n-1 , lookup) + fib(n-2 , lookup) \n", - " \n", - " # On renvoie la n-eme valeur\n", - " return lookup[n] \n", - " \n", - "def main(): \n", - " n = 6 \n", - " max = 100\n", - " # Initialise la table de cache\n", - " lookup = [None]*(max)\n", - " print(\"Le nombre de fibonacci est \", fib(n, lookup))\n", - " # Le nombre de fibonacci est 8\n", - "\n", - "if __name__==\"__main__\": \n", - " main() " - ] - }, - { - "cell_type": "markdown", - "id": "f4e93e84", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Rod cutting" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "cffad9ad", - "metadata": {}, - "outputs": [ - { - "ename": "SyntaxError", - "evalue": "invalid non-printable character U+2000 (2415646412.py, line 2)", - "output_type": "error", - "traceback": [ - "\u001b[0;36m Cell \u001b[0;32mIn[6], line 2\u001b[0;36m\u001b[0m\n\u001b[0;31m (3), optimale pour t = (0; 1; 1; 5) ;\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid non-printable character U+2000\n" - ] - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "ca5381d3", - "metadata": {}, - "outputs": [], - "source": [ - "def valeur(decoupe, t):\n", - " s = 0\n", - " for taille in decoupe:\n", - " s += t[taille]\n", - " return s" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "1f4298bc", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "30" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "decoupe = [1, 2, 3, 2] # Par exemple, une découpe possible\n", - "t = [1, 5, 8, 9, 10, 17, 17, 20]\n", - "valeur(decoupe, t)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "1ac70348", - "metadata": {}, - "outputs": [], - "source": [ - "def coupe_brute_force(n, t):\n", - " if n == 0:\n", - " return 0\n", - " max_valeur = float('-inf')\n", - " for i in range(1, n + 1):\n", - " valeur_courante = t[i] + coupe_brute_force(n - i, t)\n", - " max_valeur = max(max_valeur, valeur_courante)\n", - " return max_valeur" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "82e44946", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The maximum value for a rod of length 8 is 22.\n" - ] - } - ], - "source": [ - "lengths = [0, 1, 2, 3, 4, 5, 6, 7, 8]\n", - "values = [0, 1, 5, 8, 9, 10, 17, 17, 20]\n", - "\n", - "# Length of the rod you want to cut\n", - "rod_length = 8\n", - "\n", - "# Call the brute force cutting function\n", - "max_value = coupe_brute_force(rod_length, values)\n", - "\n", - "print(f\"The maximum value for a rod of length {rod_length} is {max_value}.\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35079643", - "metadata": {}, - "outputs": [], - "source": [ - "arr = [1, 5, 8, 9, 10, 17, 17, 20] " - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "6387dcea", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Valeur maximum de découpe 22 8\n" - ] - } - ], - "source": [ - "INT_MIN = 0\n", - "\n", - "def cutRod(price, n): \n", - "\n", - " # Initialisation tables de cache\n", - " val = [0 for x in range(n+1)] \n", - " val[0] = 0\n", - " \n", - " for i in range(1, n+1): \n", - " max_val = INT_MIN \n", - " for j in range(i): \n", - " max_val = max(max_val, price[j] + val[i-j-1]) \n", - " val[i] = max_val \n", - " \n", - " return val[n] \n", - " \n", - "if __name__==\"__main__\": \n", - " arr = [1, 5, 8, 9, 10, 17, 17, 20] \n", - " size = len(arr) \n", - " print(\"Valeur maximum de découpe \" + str(cutRod(arr, size)), len(arr) ) " - ] - } - ], - "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/README.md b/README.md index 813e1d5158d9a12ee7acc6049a5df4bbb0cc0857..dc5ed3fe85776970d20ff2f761042712f8272338 100644 --- a/README.md +++ b/README.md @@ -10,22 +10,34 @@ Instructor: [Romain Vuillemot](romain.vuillemot@ec-lyon.fr) - Optimize algorithms - Writing programs using algorithms +## Course Material + +- [lectures](https://gitlab.ec-lyon.fr/rvuillem/algo-bsc/-/tree/main/lectures): pdf of the lectures + +- [notebooks](https://gitlab.ec-lyon.fr/rvuillem/algo-bsc/-/tree/main/pdf): notebook of the lectures + +- [exercises](https://gitlab.ec-lyon.fr/rvuillem/algo-bsc/-/tree/main/exercises): exercises + +- [solutions](https://gitlab.ec-lyon.fr/rvuillem/algo-bsc/-/tree/main/pdf): solutions of exercises + +- [pdf](https://gitlab.ec-lyon.fr/rvuillem/algo-bsc/-/tree/main/pdf) books (pdf) + ## Outline -### Lecture 1 - **Data structures and complexity** | [notebook](01-introduction.ipynb) | [exercices](01-introduction-exercices.ipynb) +### Lecture 1 - **Data structures and complexity** 📖 [Python for Everybody](pdf/pythonlearn.pdf) chapter 9 (dictionnaries), chapter 10 (tuples) -### Lecture 2 - **Recursion** | [notebook](02-recursion.ipynb) | [exercices](exercises/02-recursion-exercices.ipynb) +### Lecture 2 - **Recursion** 📖 [Think Python](pdf/thinkpython2.pdf) chapter 5 (Conditionals and recursion) and Real Python [recursion](https://realpython.com/python-recursion/). -### Lecture 3 - **Lists, search, sort** | [notebook](03-lists-search-sort.ipynb) | [exercices](exercises/03-lists-search-sort-exercises.ipynb) +### Lecture 3 - **Lists, search, sort** 📖 [Think Python](pdf/thinkpython2.pdf) chapter 11 (lists), chapter 12 (tuples), [Python for Everybody](pdf/pythonlearn.pdf) chapter 8 (lists) and Real Python [lists](https://realpython.com/python-list/) and [sorts](https://realpython.com/sorting-algorithms-python/). -### Lecture 4 - **Programming strategies: divide and conquer** | [notebook](04-programming-strategies.ipynb) | [exercices](exercises/04-05-06-programming-strategies-exercises.ipynb) +### Lecture 4 - **Programming strategies: divide and conquer** 📖 [Divide & conquer](https://en.wikibooks.org/wiki/Algorithms/Divide_and_Conquer) @@ -39,27 +51,35 @@ Instructor: [Romain Vuillemot](romain.vuillemot@ec-lyon.fr) ### Lecture 7 - **Stacks and queues** -chapter 1.2.1 (Queue/Stacks), [Open Data Structures](pdf/ods-python.pdf) - - ---- - -📝 Assignment 1 - Analyzing a dataset +📖 Chapter 1.2.1 (Queue/Stacks), [Open Data Structures](pdf/ods-python.pdf) ---- ### Lecture 8 - **Binary trees** 📖 [Problem Solving with Algorithms](pdf/problemsolving.pdf) chapter 6 (trees and tree algorithms) +📝 Quizz 1 + ### Lecture 9 - **Binary trees traversals** 📖 [Problem Solving with Algorithms](pdf/problemsolving.pdf) chapter 6.7 (trees traversal) +📝 Quizz 2 + +--- + +📝 Assignment 1 - Analyzing a dataset + +--- + ### Lecture 10 - **Trees** +📖 Chapter I.3 (Binary Search Trees), [Data Structures and Algorithms](pdf/Dsa.pdf) + ### Lecture 11 - **Trees algorithms** +📖 Chapter 6 (Trees), [Open Data Structures](pdf/ods-python.pdf) + ### Lecture 12 - **Graphs** ### Lecture 13 - **Graphs spanning trees** diff --git a/exercises/01-data-structures-complexity-exercises.ipynb b/exercises/01-data-structures-complexity-exercises.ipynb index 50f1d8fdd67cb0271978a336e741d5ad527d2efd..eb87658d88cb93d1ccc8a931346c7a6bfeccfcba 100644 --- a/exercises/01-data-structures-complexity-exercises.ipynb +++ b/exercises/01-data-structures-complexity-exercises.ipynb @@ -2,41 +2,10 @@ "cells": [ { "cell_type": "markdown", - "id": "fa46e55d", + "id": "a0b1f35b", "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": "4c1206d1", - "metadata": {}, - "outputs": [], - "source": [ - "ID = \"\"\n", - "COLLABORATORS_ID = []" - ] - }, - { - "cell_type": "markdown", - "id": "6157338f", - "metadata": {}, - "source": [ - "---" + "NAME:" ] }, { @@ -92,7 +61,7 @@ } }, "source": [ - "### Exercise 1: Identify the complexity" + "### Exercise 1: Identify the complexity of the following function" ] }, { @@ -104,11 +73,11 @@ }, "outputs": [], "source": [ - "def nested_loop_example(arr):\n", - " n = len(arr)\n", + "def nested_loop_example(L):\n", + " n = len(L)\n", " for i in range(n):\n", " for j in range(n):\n", - " print(arr[i] + arr[j])" + " print(L[i] + L[j])" ] }, { @@ -141,7 +110,7 @@ "id": "612dc873-419b-42c5-be36-0accd03ffa79", "metadata": {}, "source": [ - "### Exercise 2: Identify the complexity" + "### Exercise 2: Identify the complexity of the following function" ] }, { @@ -187,7 +156,7 @@ "id": "aa4a6ce7-e542-4b23-8a10-ca0bf93de041", "metadata": {}, "source": [ - "### Exercise 3: Identify the complexity" + "### Exercise 3: Identify the complexity of the following function" ] }, { @@ -199,13 +168,13 @@ }, "outputs": [], "source": [ - "def binary_search(arr, target):\n", - " low, high = 0, len(arr) - 1\n", + "def binary_search(L, target):\n", + " low, high = 0, len(L) - 1\n", " while low <= high:\n", " mid = (low + high) // 2\n", - " if arr[mid] == target:\n", + " if L[mid] == target:\n", " return mid\n", - " elif arr[mid] < target:\n", + " elif L[mid] < target:\n", " low = mid + 1\n", " else:\n", " high = mid - 1\n", diff --git a/exercises/02-recursion-exercises.ipynb b/exercises/02-recursion-exercises.ipynb index 5d727a2d641bef5dc48ae5be44efd40f8253ab28..5bb9efa8ff4b32d695f18ff8abe9acbf7cd0dce3 100644 --- a/exercises/02-recursion-exercises.ipynb +++ b/exercises/02-recursion-exercises.ipynb @@ -2,41 +2,10 @@ "cells": [ { "cell_type": "markdown", - "id": "01d26f8d", + "id": "aed5654c", "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": "a6f5bcf0", - "metadata": {}, - "outputs": [], - "source": [ - "ID = \"\"\n", - "COLLABORATORS_ID = []" - ] - }, - { - "cell_type": "markdown", - "id": "51dc6634", - "metadata": {}, - "source": [ - "---" + "NAME:" ] }, { @@ -380,7 +349,9 @@ "tags": [] }, "source": [ - "### Exercise 4: Reverse a string" + "### Exercise 4: Reverse a string\n", + "\n", + "In a recursive way:" ] }, { @@ -447,20 +418,51 @@ "assert reverse_string(\"ABC\") == \"CBA\"" ] }, + { + "cell_type": "markdown", + "id": "c1587148-f816-4af2-8f3a-6d8a8480d54d", + "metadata": { + "tags": [] + }, + "source": [ + "In an iterative way:" + ] + }, { "cell_type": "code", "execution_count": null, "id": "159b6d78-03ae-4cf8-8545-e822b7160b32", "metadata": { + "deletable": false, + "nbgrader": { + "cell_type": "code", + "checksum": "d95c7211470341d4c1e960c2b506d676", + "grade": false, + "grade_id": "cell-ead02fa889911e42", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, "tags": [] }, "outputs": [], "source": [ "def iterative_reverse_string(s):\n", - " reversed_str = \"\"\n", - " for char in s:\n", - " reversed_str = char + reversed_str\n", - " return reversed_str" + " # YOUR CODE HERE\n", + " raise NotImplementedError()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0dcdbd99-5f57-4d1c-bc31-8f8afc617d0e", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "iterative_reverse_string(\"Romain\")" ] }, { @@ -469,14 +471,18 @@ "id": "43e10b0c", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "assert iterative_reverse_string(\"Romain\") == reverse_string(\"Romain\")" + ] }, { "cell_type": "markdown", "id": "5ab8f3c9-ddee-45ab-a013-fdd67d9853e0", "metadata": {}, "source": [ - "### Example 5: convert an interative suite into a recursive tail function" + "### Example 5: convert an interative suite into a recursive tail function\n", + "\n", + "_A recursive tail function is a function where the last operation is the recursive call._" ] }, { @@ -585,7 +591,7 @@ } }, "source": [ - "### Example 6: check if a word is an annagram\n", + "### Example 6: check if a word is a palindrom\n", "\n", "Check if a word can be read backwards." ] @@ -598,7 +604,7 @@ "deletable": false, "nbgrader": { "cell_type": "code", - "checksum": "1edb53556b907062a6d2f29282317e03", + "checksum": "a11e9594f7e72af63464586312e41230", "grade": false, "grade_id": "annagram_rec", "locked": false, @@ -610,7 +616,7 @@ }, "outputs": [], "source": [ - "def annagram_rec(word):\n", + "def palindrom_rec(word):\n", " # YOUR CODE HERE\n", " raise NotImplementedError()" ] @@ -624,7 +630,7 @@ }, "outputs": [], "source": [ - "annagram_rec(\"laval\")" + "palindrom_rec(\"laval\")" ] }, { @@ -636,7 +642,7 @@ "editable": false, "nbgrader": { "cell_type": "code", - "checksum": "22a5df98d007ceb58975b5877958e3f8", + "checksum": "ef3b3eadc729e907ea2d4da9b32fed0c", "grade": true, "grade_id": "correct_annagram_rec", "locked": true, @@ -649,11 +655,11 @@ }, "outputs": [], "source": [ - "assert annagram_rec(\"\")\n", - "assert annagram_rec(\"AA\")\n", - "assert not annagram_rec(\"ABC\")\n", - "assert annagram_rec(\"ABA\")\n", - "assert annagram_rec(\"LAVAL\")" + "assert palindrom_rec(\"\")\n", + "assert palindrom_rec(\"AA\")\n", + "assert not palindrom_rec(\"ABC\")\n", + "assert palindrom_rec(\"ABA\")\n", + "assert palindrom_rec(\"LAVAL\")" ] }, { @@ -661,7 +667,9 @@ "id": "798c2875-7940-488a-8458-ad08f6a71c70", "metadata": {}, "source": [ - "### Example 7: Calculate GCD\n" + "### Example 7: Calculate GCD\n", + "\n", + "_Here is the iterative way, write the recursive equivalent._" ] }, { @@ -744,7 +752,7 @@ "id": "d8b05edf-9536-4fc1-bfcc-ddbbeee426fc", "metadata": {}, "source": [ - "### Example 8: Check if a list is sorted" + "### Example 8: Check if a list of numbers is sorted" ] }, { @@ -755,7 +763,7 @@ "deletable": false, "nbgrader": { "cell_type": "code", - "checksum": "9fcedc9d6262baf07013d0b6f4e8175d", + "checksum": "862e6666538916866c4f2ee16d767102", "grade": false, "grade_id": "calculate_average_recursive", "locked": false, @@ -767,7 +775,7 @@ }, "outputs": [], "source": [ - "def is_sorted(lst):\n", + "def is_sorted(L):\n", " # YOUR CODE HERE\n", " raise NotImplementedError()" ] @@ -791,7 +799,7 @@ "editable": false, "nbgrader": { "cell_type": "code", - "checksum": "9cdabb05d7be086c1b769672a158fdc6", + "checksum": "c433fabbd5212a4d85564afad33f568e", "grade": true, "grade_id": "correct_calculate_average_recursive", "locked": true, @@ -804,7 +812,8 @@ }, "outputs": [], "source": [ - "assert is_sorted([2, 3])" + "assert is_sorted([2, 3])\n", + "assert is_sorted([])" ] }, { @@ -812,7 +821,7 @@ "id": "79eef392-1d3d-46c0-aeee-ac805686e6f1", "metadata": {}, "source": [ - "### Example 9: Check for prime number" + "### Example 9: Check for prime number in a recursive function" ] }, { @@ -891,7 +900,7 @@ "deletable": false, "nbgrader": { "cell_type": "code", - "checksum": "01c4970fffe483944b93049b5d8404a0", + "checksum": "dc82bb9d69427f625dd25265e766086e", "grade": false, "grade_id": "count_occurrences", "locked": false, @@ -903,7 +912,7 @@ }, "outputs": [], "source": [ - "def count_occurrences(arr, target, index=0):\n", + "def count_occurrences(L, target, index=0):\n", " # YOUR CODE HERE\n", " raise NotImplementedError()" ] @@ -950,7 +959,7 @@ "id": "bb1784f5-2426-4661-aa13-dba96743ceb5", "metadata": {}, "source": [ - "# Bonus points" + "# Bonus" ] }, { diff --git a/exercises/03-lists-search-sort-exercises.ipynb b/exercises/03-lists-search-sort-exercises.ipynb index d64010d6f3467b5100f3b6b1aad2839c4abb411f..952d5b38e9a63ccf228129cd0048e88e6a968cbb 100644 --- a/exercises/03-lists-search-sort-exercises.ipynb +++ b/exercises/03-lists-search-sort-exercises.ipynb @@ -2,41 +2,10 @@ "cells": [ { "cell_type": "markdown", - "id": "7989517f", + "id": "4dbb0dcc", "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": "c659f857", - "metadata": {}, - "outputs": [], - "source": [ - "ID = \"\"\n", - "COLLABORATORS_ID = []" - ] - }, - { - "cell_type": "markdown", - "id": "a58b61ff", - "metadata": {}, - "source": [ - "---" + "NAME:" ] }, { @@ -217,7 +186,7 @@ "deletable": false, "nbgrader": { "cell_type": "code", - "checksum": "18319fc9882c760de9d2f2dde25e2c76", + "checksum": "8b5bb88f225392915e460f5f56be2463", "grade": false, "grade_id": "sort_list_of_lists_by_nth_element", "locked": false, @@ -229,7 +198,7 @@ }, "outputs": [], "source": [ - "def sort_list_of_lists_by_nth_element(list_of_lists, N):\n", + "def sort_list_of_lists_by_nth_element(L_L, N):\n", " # YOUR CODE HERE\n", " raise NotImplementedError()" ] @@ -257,7 +226,7 @@ "editable": false, "nbgrader": { "cell_type": "code", - "checksum": "135ef51ad6ac5ce059a50fccc46a99e4", + "checksum": "b1e4adf55c21c8009a5e869493531cfe", "grade": true, "grade_id": "correct_sort_list_of_lists_by_nth_element", "locked": true, @@ -272,15 +241,15 @@ "source": [ "list1 = [[3, 5, 1], [1, 2, 9], [7, 4, 6], [2, 8, 3]]\n", "sorted_list1 = sort_list_of_lists_by_nth_element(list1, 2)\n", - "assert sorted_list1 == [[3, 5, 1], [2, 8, 3], [7, 4, 6], [1, 2, 9]], \"Test Case 1 Failed\"\n", + "assert sorted_list1 == [[3, 5, 1], [2, 8, 3], [7, 4, 6], [1, 2, 9]]\n", "\n", "list2 = [[9, 5, 7], [3, 6, 1], [0, 2, 4], [8, 1, 5]]\n", "sorted_list2 = sort_list_of_lists_by_nth_element(list2, 0)\n", - "assert sorted_list2 == [[0, 2, 4], [3, 6, 1], [8, 1, 5], [9, 5, 7]], \"Test Case 2 Failed\"\n", + "assert sorted_list2 == [[0, 2, 4], [3, 6, 1], [8, 1, 5], [9, 5, 7]]\n", "\n", "list3 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]\n", "sorted_list3 = sort_list_of_lists_by_nth_element(list3, 1)\n", - "assert sorted_list3 == [[1, 2, 3], [4, 5, 6], [7, 8, 9]], \"Test Case 3 Failed\"\n" + "assert sorted_list3 == [[1, 2, 3], [4, 5, 6], [7, 8, 9]]\n" ] }, { @@ -394,7 +363,7 @@ "deletable": false, "nbgrader": { "cell_type": "code", - "checksum": "46e9b2d0caa1061f9d00cf2859441701", + "checksum": "32e7897746d2149cd1f74d8d1897c379", "grade": false, "grade_id": "remove_elements", "locked": false, @@ -406,7 +375,7 @@ }, "outputs": [], "source": [ - "def remove_elements(lst, condition):\n", + "def remove_elements(L, condition):\n", " # YOUR CODE HERE\n", " raise NotImplementedError()" ] @@ -477,7 +446,7 @@ "source": [ "## Exercise 5: Sort a dictionnary by values\n", "\n", - "List a dictionnary (not a list!) by value." + "_Create a new sorted list as result._" ] }, { @@ -651,7 +620,7 @@ "source": [ "## Bonus\n", "\n", - "You may get bonus points for the following answers:\n", + "Extra tasks:\n", "\n", "- add exceptions https://docs.python.org/3/library/exceptions.html\n", "- add more test cases" @@ -660,7 +629,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2cc14fc2-2714-4242-83e2-31907b8a19f5", + "id": "d7622873-8c3b-496a-977c-5413309653e3", "metadata": {}, "outputs": [], "source": [] diff --git a/exercises/04-05-06-programming-strategies-exercises.ipynb b/exercises/04-05-06-programming-strategies-exercises.ipynb index 1a37afee2497791d8d64044d5eed09612ce1f6ff..edfeb6156960fc18323d5787acff2e9d36fde2c4 100644 --- a/exercises/04-05-06-programming-strategies-exercises.ipynb +++ b/exercises/04-05-06-programming-strategies-exercises.ipynb @@ -2,41 +2,10 @@ "cells": [ { "cell_type": "markdown", - "id": "c271b1aa", + "id": "a8fca59b", "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": "757d4143", - "metadata": {}, - "outputs": [], - "source": [ - "ID = \"\"\n", - "COLLABORATORS_ID = []" - ] - }, - { - "cell_type": "markdown", - "id": "451d6862", - "metadata": {}, - "source": [ - "---" + "NAME:" ] }, { @@ -141,7 +110,9 @@ "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._" + "_Find two numbers in an array `nums` that add up to a specific target value `target`. Return a list of the values index._ \n", + "\n", + "_Tip: make sure you do not use twice the same array element._" ] }, { @@ -190,7 +161,7 @@ "editable": false, "nbgrader": { "cell_type": "code", - "checksum": "71cc7d97ace17ad8446e79c255727951", + "checksum": "2007c6b25ddb1a5c4fa117a0ed571b25", "grade": true, "grade_id": "correct_two_sum", "locked": true, @@ -203,7 +174,7 @@ }, "outputs": [], "source": [ - "assert two_sum([2, 7, 11, 15], 9) == [0, 1] # 2 + 7 = 9\n", + "assert two_sum([2, 7, 11, 15, 19], 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" @@ -498,7 +469,7 @@ "deletable": false, "nbgrader": { "cell_type": "code", - "checksum": "725597ac005b370c6135a3f1920beb2c", + "checksum": "7e5d1e25bd4a7d8aceca67ae23771b79", "grade": false, "grade_id": "merge_sort", "locked": false, @@ -510,7 +481,7 @@ }, "outputs": [], "source": [ - "def merge_sort(arr): \n", + "def merge_sort(L):\n", " # YOUR CODE HERE\n", " raise NotImplementedError()" ] @@ -855,55 +826,7 @@ { "cell_type": "code", "execution_count": null, - "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": 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", + "id": "ef20e870-fcba-4610-bf22-37af0b0691a0", "metadata": {}, "outputs": [], "source": [] diff --git a/exercises/07-stacks-queues-exercises.ipynb b/exercises/07-stacks-queues-exercises.ipynb index cfbe2107bfebf771dbefd59816f4083bf69beb28..0f00dda13680d09e14a9e78b665d8a777f5dc5f8 100644 --- a/exercises/07-stacks-queues-exercises.ipynb +++ b/exercises/07-stacks-queues-exercises.ipynb @@ -2,41 +2,10 @@ "cells": [ { "cell_type": "markdown", - "id": "49d0b44c", + "id": "7b900342", "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": [ - "---" + "NAME:" ] }, { @@ -246,7 +215,7 @@ }, "outputs": [], "source": [ - "# how to use the modue\n", + "# how to use the queue module\n", "priority_queue = PriorityQueue()\n", "priority_queue.put((3, 'apple'))\n", "priority_queue.put((1, 'banana'))\n", @@ -401,8 +370,7 @@ "outputs": [], "source": [ "postfix_expression = \"3 4 + 2 *\"\n", - "result = evaluate_postfix(postfix_expression)\n", - "print(\"Result:\", result)" + "print(evaluate_postfix(postfix_expression))" ] }, { diff --git a/lectures/01-introduction slides.pdf b/lectures/01-introduction slides.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6966950a6212dfa4e43ad217a48e60dfc1afbab5 Binary files /dev/null and b/lectures/01-introduction slides.pdf differ diff --git a/lectures/02-recursion slides.pdf b/lectures/02-recursion slides.pdf new file mode 100644 index 0000000000000000000000000000000000000000..69c08c65ffd5a17d6be17c27767aad9e501a2f2f Binary files /dev/null and b/lectures/02-recursion slides.pdf differ diff --git a/lectures/03-lists-search-sort slides.pdf b/lectures/03-lists-search-sort slides.pdf new file mode 100644 index 0000000000000000000000000000000000000000..16211b335d5bdf808445c0ee3c80383405968fc4 Binary files /dev/null and b/lectures/03-lists-search-sort slides.pdf differ diff --git a/lectures/04-05-06-programming-strategies slides.pdf b/lectures/04-05-06-programming-strategies slides.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5c1a118687b438d3942e0eb0de797bf6e40631bf Binary files /dev/null and b/lectures/04-05-06-programming-strategies slides.pdf differ diff --git a/lectures/07-stacks-queues slides.pdf b/lectures/07-stacks-queues slides.pdf new file mode 100644 index 0000000000000000000000000000000000000000..56d0f7dba512cf447aeaffc8ce9cc4d91400f21e Binary files /dev/null and b/lectures/07-stacks-queues slides.pdf differ diff --git a/01-data-structures-complexity.ipynb b/notebooks/01-data-structures-complexity.ipynb similarity index 100% rename from 01-data-structures-complexity.ipynb rename to notebooks/01-data-structures-complexity.ipynb diff --git a/02-recursion.ipynb b/notebooks/02-recursion.ipynb similarity index 100% rename from 02-recursion.ipynb rename to notebooks/02-recursion.ipynb diff --git a/03-lists-search-sort.ipynb b/notebooks/03-lists-search-sort.ipynb similarity index 100% rename from 03-lists-search-sort.ipynb rename to notebooks/03-lists-search-sort.ipynb diff --git a/04-05-06-programming-strategies.ipynb b/notebooks/04-05-06-programming-strategies.ipynb similarity index 100% rename from 04-05-06-programming-strategies.ipynb rename to notebooks/04-05-06-programming-strategies.ipynb diff --git a/07-stacks-queues.ipynb b/notebooks/07-stacks-queues.ipynb similarity index 100% rename from 07-stacks-queues.ipynb rename to notebooks/07-stacks-queues.ipynb diff --git a/notebooks/figures/Barnsley_fern_plotted_with_VisSim.png b/notebooks/figures/Barnsley_fern_plotted_with_VisSim.png new file mode 100644 index 0000000000000000000000000000000000000000..f70f438c2f2f8ae43f86045aa98c384848f7dd3d Binary files /dev/null and b/notebooks/figures/Barnsley_fern_plotted_with_VisSim.png differ diff --git a/notebooks/figures/Logo_ECL.png b/notebooks/figures/Logo_ECL.png new file mode 100644 index 0000000000000000000000000000000000000000..00b57d6ca30841a9eb9360ded009fad011f38f6e Binary files /dev/null and b/notebooks/figures/Logo_ECL.png differ diff --git a/notebooks/figures/Mapreduce.png b/notebooks/figures/Mapreduce.png new file mode 100644 index 0000000000000000000000000000000000000000..2f0dbeb942f4935751903435ba5b66b48cc92ff7 Binary files /dev/null and b/notebooks/figures/Mapreduce.png differ diff --git a/notebooks/figures/a-series-paper-sizes-1.jpg b/notebooks/figures/a-series-paper-sizes-1.jpg new file mode 100755 index 0000000000000000000000000000000000000000..76178c439fc92ea68d321743f7536bc527cf2310 Binary files /dev/null and b/notebooks/figures/a-series-paper-sizes-1.jpg differ diff --git a/notebooks/figures/arbre-largeur-hauteur.png b/notebooks/figures/arbre-largeur-hauteur.png new file mode 100755 index 0000000000000000000000000000000000000000..f34c3f3ad643361adc7927c8bac3da7681862081 Binary files /dev/null and b/notebooks/figures/arbre-largeur-hauteur.png differ diff --git a/notebooks/figures/arbre-tableau.png b/notebooks/figures/arbre-tableau.png new file mode 100644 index 0000000000000000000000000000000000000000..4e4e7f37c6644bd4f5944da50fe14e0a5f761769 Binary files /dev/null and b/notebooks/figures/arbre-tableau.png differ diff --git a/notebooks/figures/big-o-chart.png b/notebooks/figures/big-o-chart.png new file mode 100644 index 0000000000000000000000000000000000000000..b9a195e17ecdc9cadadf1c1d6a12ffda9eff4c2c Binary files /dev/null and b/notebooks/figures/big-o-chart.png differ diff --git a/notebooks/figures/closest-point.gif b/notebooks/figures/closest-point.gif new file mode 100644 index 0000000000000000000000000000000000000000..efdaaa86c8f82a4ce22fb55fff7e6470a68cfcb6 Binary files /dev/null and b/notebooks/figures/closest-point.gif differ diff --git a/notebooks/figures/codage-huffman.png b/notebooks/figures/codage-huffman.png new file mode 100755 index 0000000000000000000000000000000000000000..e8a04ad7430078b3f84f025a7a7f78c1d365f3a2 Binary files /dev/null and b/notebooks/figures/codage-huffman.png differ diff --git a/notebooks/figures/coins-changing.png b/notebooks/figures/coins-changing.png new file mode 100644 index 0000000000000000000000000000000000000000..bb32d17700f8393ef09e6ffbd00083f011d048eb Binary files /dev/null and b/notebooks/figures/coins-changing.png differ diff --git a/notebooks/figures/complexite-arrays.png b/notebooks/figures/complexite-arrays.png new file mode 100755 index 0000000000000000000000000000000000000000..1d8b59c4724a3ab719a22b7748a4be58a7792684 Binary files /dev/null and b/notebooks/figures/complexite-arrays.png differ diff --git a/notebooks/figures/complexite-data-structures.png b/notebooks/figures/complexite-data-structures.png new file mode 100755 index 0000000000000000000000000000000000000000..15a72363455bf8a9f85410e9bfb3c60656cf39ca Binary files /dev/null and b/notebooks/figures/complexite-data-structures.png differ diff --git a/notebooks/figures/execution-recursive.png b/notebooks/figures/execution-recursive.png new file mode 100755 index 0000000000000000000000000000000000000000..55635d646b0b87f047539df9de3488033ff280cf Binary files /dev/null and b/notebooks/figures/execution-recursive.png differ diff --git a/notebooks/figures/fibonacci-tree.png b/notebooks/figures/fibonacci-tree.png new file mode 100755 index 0000000000000000000000000000000000000000..e26d52b54d57fe0278ffe488d47e4255814af2bc Binary files /dev/null and b/notebooks/figures/fibonacci-tree.png differ diff --git a/notebooks/figures/flowchart.png b/notebooks/figures/flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..c85c4e20f8f7ed2c2f9fe26fb91fbdd845b47437 Binary files /dev/null and b/notebooks/figures/flowchart.png differ diff --git a/notebooks/figures/hauteur-arbre.png b/notebooks/figures/hauteur-arbre.png new file mode 100755 index 0000000000000000000000000000000000000000..a6bbe7249a007db116c905a67bfb6dea0bd3a329 Binary files /dev/null and b/notebooks/figures/hauteur-arbre.png differ diff --git a/notebooks/figures/logo-ecl.png b/notebooks/figures/logo-ecl.png new file mode 100644 index 0000000000000000000000000000000000000000..64dbe6a8dbcc291560a7d01f352614c0eafb5cba Binary files /dev/null and b/notebooks/figures/logo-ecl.png differ diff --git a/notebooks/figures/logo-emlyon.png b/notebooks/figures/logo-emlyon.png new file mode 100644 index 0000000000000000000000000000000000000000..d55b2f66777a0e61adeeecf372e08e414f9353e0 Binary files /dev/null and b/notebooks/figures/logo-emlyon.png differ diff --git a/notebooks/figures/placeholder.png b/notebooks/figures/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..2e3fd26830cccc32005da06088d7e79444aad707 Binary files /dev/null and b/notebooks/figures/placeholder.png differ diff --git a/notebooks/figures/quicksort.png b/notebooks/figures/quicksort.png new file mode 100644 index 0000000000000000000000000000000000000000..ac14331d9e33828c46d56c68caabb464333a7d74 Binary files /dev/null and b/notebooks/figures/quicksort.png differ diff --git a/notebooks/figures/recherche-dichotomique.png b/notebooks/figures/recherche-dichotomique.png new file mode 100755 index 0000000000000000000000000000000000000000..209824cc844c1e0bbbf9450d042d831438f3fc64 Binary files /dev/null and b/notebooks/figures/recherche-dichotomique.png differ diff --git a/notebooks/figures/rod-cutting-tree.png b/notebooks/figures/rod-cutting-tree.png new file mode 100755 index 0000000000000000000000000000000000000000..d30173fbb75468ef56cc69f83eb1e03092a14834 Binary files /dev/null and b/notebooks/figures/rod-cutting-tree.png differ diff --git a/notebooks/figures/rod-cutting.png b/notebooks/figures/rod-cutting.png new file mode 100755 index 0000000000000000000000000000000000000000..d7c810145b7a2a9d8f415a53988dcf7a1f2841a5 Binary files /dev/null and b/notebooks/figures/rod-cutting.png differ diff --git a/notebooks/figures/stacks-queues.png b/notebooks/figures/stacks-queues.png new file mode 100644 index 0000000000000000000000000000000000000000..cab5f174bc7a4d1796bca02354084ad89679d0fc Binary files /dev/null and b/notebooks/figures/stacks-queues.png differ diff --git a/notebooks/figures/tri-fusion.png b/notebooks/figures/tri-fusion.png new file mode 100755 index 0000000000000000000000000000000000000000..59912eea5d3181ae45971451c760a8b37faf83dd Binary files /dev/null and b/notebooks/figures/tri-fusion.png differ diff --git a/notebooks/figures/xkcd_fixing_problems.png b/notebooks/figures/xkcd_fixing_problems.png new file mode 100644 index 0000000000000000000000000000000000000000..67d1fce7b1617bc9ba438cdbb7184647a9a9a7ba Binary files /dev/null and b/notebooks/figures/xkcd_fixing_problems.png differ diff --git a/solutions/01-data-structures-complexity-exercises.ipynb b/solutions/01-data-structures-complexity-exercises.ipynb index c365c436ec6fbd5d214cc35ec540216037132d65..e72bdcb6cb95ffb836e7dd238a0ba67d6946f37e 100644 --- a/solutions/01-data-structures-complexity-exercises.ipynb +++ b/solutions/01-data-structures-complexity-exercises.ipynb @@ -53,28 +53,28 @@ } }, "source": [ - "### Exercise 1: Identify the complexity" + "### Exercise 1: Identify the complexity of the following function" ] }, { "cell_type": "code", - "execution_count": 110, + "execution_count": 9, "id": "431fe8c1-91d1-40f3-a7a4-f4a3770a4a01", "metadata": { "tags": [] }, "outputs": [], "source": [ - "def nested_loop_example(arr):\n", - " n = len(arr)\n", + "def nested_loop_example(L):\n", + " n = len(L)\n", " for i in range(n):\n", " for j in range(n):\n", - " print(arr[i] + arr[j])" + " print(L[i] + L[j])" ] }, { "cell_type": "code", - "execution_count": 111, + "execution_count": 10, "id": "e68b3e9a-418f-4950-9f27-18bb0fe90794", "metadata": { "nbgrader": { @@ -100,12 +100,12 @@ "id": "612dc873-419b-42c5-be36-0accd03ffa79", "metadata": {}, "source": [ - "### Exercise 2: Identify the complexity" + "### Exercise 2: Identify the complexity of the following function" ] }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 11, "id": "76102853-e1f3-4717-8a59-1091195a19eb", "metadata": {}, "outputs": [], @@ -118,7 +118,7 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 12, "id": "dead6a52-7996-4eae-9d2a-f75d5d26bbb7", "metadata": { "nbgrader": { @@ -144,25 +144,25 @@ "id": "aa4a6ce7-e542-4b23-8a10-ca0bf93de041", "metadata": {}, "source": [ - "### Exercise 3: Identify the complexity" + "### Exercise 3: Identify the complexity of the following function" ] }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 13, "id": "70af3e43-8053-49c9-ba58-346a3e915bdb", "metadata": { "tags": [] }, "outputs": [], "source": [ - "def binary_search(arr, target):\n", - " low, high = 0, len(arr) - 1\n", + "def binary_search(L, target):\n", + " low, high = 0, len(L) - 1\n", " while low <= high:\n", " mid = (low + high) // 2\n", - " if arr[mid] == target:\n", + " if L[mid] == target:\n", " return mid\n", - " elif arr[mid] < target:\n", + " elif L[mid] < target:\n", " low = mid + 1\n", " else:\n", " high = mid - 1\n", @@ -171,7 +171,7 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 14, "id": "9c22866c-b4fc-4228-b0ab-5882d964f5f6", "metadata": { "nbgrader": { @@ -202,7 +202,7 @@ }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 8, "id": "6e8f2878-ce5f-4cd8-a5a5-7bce8f655ab8", "metadata": { "tags": [] diff --git a/solutions/02-recursion-exercises.ipynb b/solutions/02-recursion-exercises.ipynb index 0329eb812a2711ec2472efb6a87dd81a37e74a7d..9e5ff999c0cb675c277eea94e564470877bcd2ab 100644 --- a/solutions/02-recursion-exercises.ipynb +++ b/solutions/02-recursion-exercises.ipynb @@ -26,7 +26,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "id": "0dfe1da3-b50b-49c0-aaff-483616e13863", "metadata": { "tags": [] @@ -52,7 +52,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "431fe8c1-91d1-40f3-a7a4-f4a3770a4a01", "metadata": { "nbgrader": { @@ -82,7 +82,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "f6baae3c-3660-4add-ab4b-48016cba3030", "metadata": { "tags": [] @@ -94,7 +94,7 @@ "9" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -105,7 +105,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "e68b3e9a-418f-4950-9f27-18bb0fe90794", "metadata": { "nbgrader": { @@ -140,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "07668fd8", "metadata": { "nbgrader": { @@ -166,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "f9784710-4b2b-434c-bc47-da46fa410749", "metadata": { "tags": [] @@ -178,7 +178,7 @@ "9" ] }, - "execution_count": 8, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -189,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "9b0161f8-0539-4e5e-921c-1886e61c0783", "metadata": { "nbgrader": { @@ -218,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "de424156-0b9b-41d0-8e38-ce335f35cec0", "metadata": { "nbgrader": { @@ -244,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "cec0caca-cb2c-4e4d-b004-27b3cf2ff611", "metadata": { "tags": [] @@ -256,7 +256,7 @@ "1" ] }, - "execution_count": 11, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -267,7 +267,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "id": "bf276ca2-48ed-4e87-80f2-776f54b7062b", "metadata": { "nbgrader": { @@ -300,7 +300,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "id": "abca03a0-7bcd-4ee6-b109-ff2f2da52bb6", "metadata": { "nbgrader": { @@ -326,7 +326,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "abddd3b1-f75f-4cb6-a09e-54eed489c5b0", "metadata": { "tags": [] @@ -338,7 +338,7 @@ "1024" ] }, - "execution_count": 14, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -349,7 +349,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "id": "8a6605fe-4f6f-45de-84a3-7601e2e2e6f6", "metadata": { "nbgrader": { @@ -377,12 +377,14 @@ "tags": [] }, "source": [ - "### Exercise 4: Reverse a string" + "### Exercise 4: Reverse a string\n", + "\n", + "In a recursive way:" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "id": "ddc9826a-0863-4777-a08d-81b66652b5a5", "metadata": { "nbgrader": { @@ -408,7 +410,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "id": "13acad7e-d03c-4ea6-ad86-baf02e0910eb", "metadata": { "tags": [] @@ -420,7 +422,7 @@ "'niamoR'" ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -452,41 +454,88 @@ "assert reverse_string(\"ABC\") == \"CBA\"" ] }, + { + "cell_type": "markdown", + "id": "c1587148-f816-4af2-8f3a-6d8a8480d54d", + "metadata": { + "tags": [] + }, + "source": [ + "In an iterative way:" + ] + }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 21, "id": "159b6d78-03ae-4cf8-8545-e822b7160b32", "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ead02fa889911e42", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, "tags": [] }, "outputs": [], "source": [ "def iterative_reverse_string(s):\n", + " ### BEGIN SOLUTION\n", " reversed_str = \"\"\n", " for char in s:\n", " reversed_str = char + reversed_str\n", - " return reversed_str" + " return reversed_str\n", + " ### END SOLUTION" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, + "id": "0dcdbd99-5f57-4d1c-bc31-8f8afc617d0e", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'niamoR'" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "iterative_reverse_string(\"Romain\")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, "id": "43e10b0c", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "assert iterative_reverse_string(\"Romain\") == reverse_string(\"Romain\")" + ] }, { "cell_type": "markdown", "id": "5ab8f3c9-ddee-45ab-a013-fdd67d9853e0", "metadata": {}, "source": [ - "### Example 5: convert an interative suite into a recursive tail function" + "### Example 5: convert an interative suite into a recursive tail function\n", + "\n", + "_A recursive tail function is a function where the last operation is the recursive call._" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 24, "id": "219add7f", "metadata": { "slideshow": { @@ -506,7 +555,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 25, "id": "0787df24-8234-4286-b910-5f9e456dd279", "metadata": { "tags": [] @@ -526,7 +575,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 26, "id": "9c17cf1b-6d05-4589-af2b-c05cfcc33202", "metadata": { "nbgrader": { @@ -551,7 +600,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 27, "id": "43507c43-de61-414d-b389-c0a771ad9e0c", "metadata": { "tags": [] @@ -571,7 +620,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 28, "id": "dd923b7c-0cab-4678-8dc3-aad2ab9b25f9", "metadata": { "nbgrader": { @@ -602,14 +651,14 @@ } }, "source": [ - "### Example 6: check if a word is an annagram\n", + "### Example 6: check if a word is a palindrom\n", "\n", "Check if a word can be read backwards." ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 29, "id": "51bb3d08", "metadata": { "nbgrader": { @@ -624,17 +673,17 @@ }, "outputs": [], "source": [ - "def annagram_rec(word):\n", + "def palindrom_rec(word):\n", " ### BEGIN SOLUTION\n", " if len(word) < 2: \n", " return True\n", - " return (word[0] == word[-1]) and annagram_rec(word[1:len(word)-1])\n", + " return (word[0] == word[-1]) and palindrom_rec(word[1:len(word)-1])\n", " ### END SOLUTION" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 30, "id": "0c279628-9b96-4687-8e20-a954ab646e0f", "metadata": { "tags": [] @@ -646,18 +695,18 @@ "True" ] }, - "execution_count": 26, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "annagram_rec(\"laval\")" + "palindrom_rec(\"laval\")" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 31, "id": "cf6fa61a-7c6f-4a32-96c2-b9fd50deacc6", "metadata": { "nbgrader": { @@ -673,11 +722,11 @@ }, "outputs": [], "source": [ - "assert annagram_rec(\"\")\n", - "assert annagram_rec(\"AA\")\n", - "assert not annagram_rec(\"ABC\")\n", - "assert annagram_rec(\"ABA\")\n", - "assert annagram_rec(\"LAVAL\")" + "assert palindrom_rec(\"\")\n", + "assert palindrom_rec(\"AA\")\n", + "assert not palindrom_rec(\"ABC\")\n", + "assert palindrom_rec(\"ABA\")\n", + "assert palindrom_rec(\"LAVAL\")" ] }, { @@ -685,12 +734,14 @@ "id": "798c2875-7940-488a-8458-ad08f6a71c70", "metadata": {}, "source": [ - "### Example 7: Calculate GCD\n" + "### Example 7: Calculate GCD\n", + "\n", + "_Here is the iterative way, write the recursive equivalent._" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 32, "id": "48c65c83-be0c-41c4-8c04-1d29ac4415cb", "metadata": { "tags": [] @@ -705,7 +756,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 33, "id": "bf6e1a76", "metadata": { "nbgrader": { @@ -731,7 +782,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 34, "id": "4f1feace", "metadata": {}, "outputs": [ @@ -741,7 +792,7 @@ "2" ] }, - "execution_count": 30, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -752,7 +803,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 35, "id": "e6ff1765-ff4b-49a5-80d3-684d2627e961", "metadata": { "nbgrader": { @@ -776,12 +827,12 @@ "id": "d8b05edf-9536-4fc1-bfcc-ddbbeee426fc", "metadata": {}, "source": [ - "### Example 8: Check if a list is sorted" + "### Example 8: Check if a list of numbers is sorted" ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 37, "id": "1661dfb5-88f2-411f-8fe2-63bbaa29ce72", "metadata": { "nbgrader": { @@ -796,12 +847,12 @@ }, "outputs": [], "source": [ - "def is_sorted(lst):\n", + "def is_sorted(L):\n", " ### BEGIN SOLUTION\n", - " if len(lst) <= 1:\n", + " if len(L) <= 1:\n", " return True\n", " else:\n", - " return lst[0] <= lst[1] and is_sorted(lst[1:])\n", + " return L[0] <= L[1] and is_sorted(L[1:])\n", " ### END SOLUTION" ] }, @@ -828,7 +879,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 38, "id": "9f18b6b7-d980-4a72-a2a8-3aa201003d21", "metadata": { "nbgrader": { @@ -844,7 +895,8 @@ }, "outputs": [], "source": [ - "assert is_sorted([2, 3])" + "assert is_sorted([2, 3])\n", + "assert is_sorted([])" ] }, { @@ -852,12 +904,12 @@ "id": "79eef392-1d3d-46c0-aeee-ac805686e6f1", "metadata": {}, "source": [ - "### Example 9: Check for prime number" + "### Example 9: Check for prime number in a recursive function" ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 40, "id": "ac374a08-11c9-47e6-ba11-419549911266", "metadata": { "nbgrader": { @@ -892,7 +944,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 41, "id": "996fc91f", "metadata": {}, "outputs": [ @@ -902,7 +954,7 @@ "True" ] }, - "execution_count": 36, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" } @@ -913,7 +965,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 42, "id": "5be1051c-3b60-4810-b855-6f8575ad6380", "metadata": { "nbgrader": { @@ -942,7 +994,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 45, "id": "cfeb0ad0-ed82-499e-9af3-64923291a0e7", "metadata": { "nbgrader": { @@ -957,20 +1009,20 @@ }, "outputs": [], "source": [ - "def count_occurrences(arr, target, index=0):\n", + "def count_occurrences(L, target, index=0):\n", " ### BEGIN SOLUTION\n", - " if index == len(arr):\n", + " if index == len(L):\n", " return 0\n", " \n", - " count = (1 if arr[index] == target else 0)\n", + " count = (1 if L[index] == target else 0)\n", " \n", - " return count + count_occurrences(arr, target, index + 1)\n", + " return count + count_occurrences(L, target, index + 1)\n", " ### END SOLUTION" ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 46, "id": "49daec07-00b3-412e-ad44-5bafadbd9f36", "metadata": { "tags": [] @@ -982,7 +1034,7 @@ "4" ] }, - "execution_count": 39, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -993,7 +1045,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 47, "id": "14a2eb85-1126-4506-93bb-4bf624d046b6", "metadata": { "nbgrader": { @@ -1017,7 +1069,7 @@ "id": "bb1784f5-2426-4661-aa13-dba96743ceb5", "metadata": {}, "source": [ - "# Bonus points" + "# Bonus" ] }, { @@ -1118,7 +1170,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 51, "id": "1c73ad0e-ddc9-483d-85a1-f2a65d04f30b", "metadata": { "tags": [] diff --git a/solutions/03-lists-search-sort-exercises.ipynb b/solutions/03-lists-search-sort-exercises.ipynb index 0edce773392da83e24246b94abb9cf5301d71aaf..1ea99e28b10d9dd9d9dda7b5ac88e7199ef21230 100644 --- a/solutions/03-lists-search-sort-exercises.ipynb +++ b/solutions/03-lists-search-sort-exercises.ipynb @@ -40,7 +40,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 2, "id": "665c7b64-428d-468a-860b-65d0d12e98e1", "metadata": { "tags": [] @@ -56,7 +56,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 3, "id": "d776ca94-1ed2-4443-91e2-a1591a1690b8", "metadata": { "tags": [] @@ -68,7 +68,7 @@ "1" ] }, - "execution_count": 16, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -79,7 +79,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 4, "id": "e7b5950c-a6f0-4795-995b-903d717f35c9", "metadata": { "tags": [] @@ -106,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 5, "id": "f3b233ec-7077-479d-9c04-f1a4c35f3111", "metadata": { "nbgrader": { @@ -132,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 6, "id": "f280eeea-4812-4a30-80b5-3fe1cafa9283", "metadata": { "tags": [] @@ -144,7 +144,7 @@ "-1" ] }, - "execution_count": 3, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -155,7 +155,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 7, "id": "423d2637-8bd6-4e8f-95ff-2765dae5bce7", "metadata": { "nbgrader": { @@ -191,7 +191,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 10, "id": "8271ff47-efb4-48a0-ac4c-bba6ede7e578", "metadata": { "nbgrader": { @@ -206,16 +206,16 @@ }, "outputs": [], "source": [ - "def sort_list_of_lists_by_nth_element(list_of_lists, N):\n", + "def sort_list_of_lists_by_nth_element(L_L, N):\n", " ### BEGIN SOLUTION\n", - " sorted_list = sorted(list_of_lists, key=lambda x: x[N])\n", + " sorted_list = sorted(L_L, key=lambda x: x[N])\n", " return sorted_list\n", " ### END SOLUTION" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 11, "id": "4577bd24-9e50-4172-8246-d1bccfa21618", "metadata": { "tags": [] @@ -237,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 12, "id": "ed3cdeed-07fa-461f-b7ae-210e1605ca76", "metadata": { "nbgrader": { @@ -255,15 +255,15 @@ "source": [ "list1 = [[3, 5, 1], [1, 2, 9], [7, 4, 6], [2, 8, 3]]\n", "sorted_list1 = sort_list_of_lists_by_nth_element(list1, 2)\n", - "assert sorted_list1 == [[3, 5, 1], [2, 8, 3], [7, 4, 6], [1, 2, 9]], \"Test Case 1 Failed\"\n", + "assert sorted_list1 == [[3, 5, 1], [2, 8, 3], [7, 4, 6], [1, 2, 9]]\n", "\n", "list2 = [[9, 5, 7], [3, 6, 1], [0, 2, 4], [8, 1, 5]]\n", "sorted_list2 = sort_list_of_lists_by_nth_element(list2, 0)\n", - "assert sorted_list2 == [[0, 2, 4], [3, 6, 1], [8, 1, 5], [9, 5, 7]], \"Test Case 2 Failed\"\n", + "assert sorted_list2 == [[0, 2, 4], [3, 6, 1], [8, 1, 5], [9, 5, 7]]\n", "\n", "list3 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]\n", "sorted_list3 = sort_list_of_lists_by_nth_element(list3, 1)\n", - "assert sorted_list3 == [[1, 2, 3], [4, 5, 6], [7, 8, 9]], \"Test Case 3 Failed\"\n" + "assert sorted_list3 == [[1, 2, 3], [4, 5, 6], [7, 8, 9]]\n" ] }, { @@ -278,7 +278,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "id": "2d6a6f11-d24b-4f0e-a7d2-298243e35c4d", "metadata": { "nbgrader": { @@ -303,7 +303,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 14, "id": "ea0883dc-d08c-40ea-9e0b-4f0a3abc517f", "metadata": { "tags": [] @@ -325,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 15, "id": "88ae441e-58b1-4d6c-a639-530919658d03", "metadata": { "nbgrader": { @@ -362,7 +362,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 21, "id": "e079e47b-2f9e-42d1-a78b-56c92ad84d63", "metadata": { "tags": [] @@ -375,7 +375,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 24, "id": "536734bd-d4cc-4f83-8042-3c0e774c4034", "metadata": { "nbgrader": { @@ -390,15 +390,15 @@ }, "outputs": [], "source": [ - "def remove_elements(lst, condition):\n", + "def remove_elements(L, condition):\n", " ### BEGIN SOLUTION\n", - " lst[:] = [x for x in lst if not condition(x)]\n", + " L[:] = [x for x in L if not condition(x)]\n", " ### END SOLUTION" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 25, "id": "a65bb6f1-b7c7-4156-ba8b-9b72a25616f3", "metadata": { "tags": [] @@ -420,7 +420,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 26, "id": "254155dc-b271-4881-ac65-5a11d13990ea", "metadata": { "nbgrader": { @@ -466,12 +466,12 @@ "source": [ "## Exercise 5: Sort a dictionnary by values\n", "\n", - "List a dictionnary (not a list!) by value." + "_Create a new sorted list as result._" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 29, "id": "556be9d8-b8c3-4f1d-bcb1-8f099968af9d", "metadata": { "nbgrader": { @@ -498,7 +498,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 30, "id": "3c32c4ee-56f5-46b5-a8b6-b740e6d5fef5", "metadata": { "tags": [] @@ -512,7 +512,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 31, "id": "532e5225-a2d8-4c10-a036-1efde9acc5fd", "metadata": { "nbgrader": { @@ -560,7 +560,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 32, "id": "51a86852-f8e2-4c05-8053-13fdf2a4832b", "metadata": { "nbgrader": { @@ -590,7 +590,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 33, "id": "fbfa74f1-4675-452f-897b-21b0c7025f81", "metadata": { "tags": [] @@ -610,7 +610,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 35, "id": "266e89e0-4b59-441f-bb67-69ad4e35e2a9", "metadata": { "nbgrader": { @@ -646,7 +646,7 @@ "source": [ "## Bonus\n", "\n", - "You may get bonus points for the following answers:\n", + "Extra tasks:\n", "\n", "- add exceptions https://docs.python.org/3/library/exceptions.html\n", "- add more test cases" @@ -655,7 +655,7 @@ { "cell_type": "code", "execution_count": null, - "id": "2cc14fc2-2714-4242-83e2-31907b8a19f5", + "id": "d7622873-8c3b-496a-977c-5413309653e3", "metadata": {}, "outputs": [], "source": [] diff --git a/solutions/04-05-06-programming-strategies-exercises.ipynb b/solutions/04-05-06-programming-strategies-exercises.ipynb index 5bee7faa259005d9bec99b1e6b4b7ba18a89307c..7f1057d927890e0ee4aadb663a1e354c647b7f51 100644 --- a/solutions/04-05-06-programming-strategies-exercises.ipynb +++ b/solutions/04-05-06-programming-strategies-exercises.ipynb @@ -32,7 +32,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 1, "id": "f3b233ec-7077-479d-9c04-f1a4c35f3111", "metadata": { "tags": [] @@ -61,7 +61,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 2, "id": "f280eeea-4812-4a30-80b5-3fe1cafa9283", "metadata": { "tags": [] @@ -73,7 +73,7 @@ "274" ] }, - "execution_count": 47, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -84,7 +84,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 3, "id": "423d2637-8bd6-4e8f-95ff-2765dae5bce7", "metadata": { "nbgrader": { @@ -114,12 +114,14 @@ "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._" + "_Find two numbers in an array `nums` that add up to a specific target value `target`. Return a list of the values index._ \n", + "\n", + "_Tip: make sure you do not use twice the same array element._" ] }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 4, "id": "f5a964df-958e-4758-8ca2-1139c59a7585", "metadata": { "nbgrader": { @@ -136,17 +138,17 @@ "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", + " for i in range(len(nums) - 1):\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, + "execution_count": 5, "id": "1cec37b0-5d27-4973-b53a-053a46992c0c", "metadata": { "tags": [] @@ -158,7 +160,7 @@ "[0, 2]" ] }, - "execution_count": 75, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -169,7 +171,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 6, "id": "395cb69f-b99a-4c60-88f5-11216a5ec857", "metadata": { "nbgrader": { @@ -185,7 +187,7 @@ }, "outputs": [], "source": [ - "assert two_sum([2, 7, 11, 15], 9) == [0, 1] # 2 + 7 = 9\n", + "assert two_sum([2, 7, 11, 15, 19], 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" @@ -210,7 +212,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 7, "id": "f73f28a4-d39c-497d-9cbb-1c4edd1c628f", "metadata": { "tags": [] @@ -222,7 +224,7 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 8, "id": "0be305c3-c054-4092-b041-bd7e705e2178", "metadata": { "nbgrader": { @@ -245,7 +247,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 9, "id": "c0e25948-eefd-4cb0-a893-c3d5e34ff0ee", "metadata": { "nbgrader": { @@ -274,7 +276,7 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": 10, "id": "965d8274-470e-4a3e-8b88-60d458de74e2", "metadata": { "tags": [] @@ -286,7 +288,7 @@ "1.4142135623730951" ] }, - "execution_count": 92, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -298,7 +300,7 @@ }, { "cell_type": "code", - "execution_count": 95, + "execution_count": 11, "id": "5d875175-45c3-4594-a434-23e15cfb88f7", "metadata": { "nbgrader": { @@ -319,7 +321,7 @@ "0.0" ] }, - "execution_count": 95, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -331,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 12, "id": "755116cb-456e-48ad-8dfe-bd72617488d9", "metadata": { "tags": [] @@ -353,7 +355,7 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 13, "id": "a5902565-9c98-482a-8e63-9cc37214beb2", "metadata": { "tags": [] @@ -375,7 +377,7 @@ }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 14, "id": "e35557a8-54cb-4324-b280-4479835685db", "metadata": { "tags": [] @@ -383,7 +385,7 @@ "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/OhHP4rHH3887rrrrkou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", + "image/png": "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", "text/plain": [ "<Figure size 1000x700 with 1 Axes>" ] @@ -398,7 +400,7 @@ }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 15, "id": "6c504c53-d3fc-44a1-904c-49ceff572638", "metadata": { "nbgrader": { @@ -434,7 +436,7 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 16, "id": "4266685c-19f9-4d2f-bb71-4d047bb54787", "metadata": { "nbgrader": { @@ -451,7 +453,7 @@ "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==", + "image/png": "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F//e3XffnRUrVuSjH/1otmzZkksuuSQLFy7Mjh07Djv+6aefzhVXXJFLLrkkW7ZsyUc+8pFcf/31Wbdu3TDPnNFgsPvXy5588skB31NveMMbhmnGjFl1BuWOO+6ot7e3H9fYpUuX1t/1rnc1dD6MLce7f/X19dVnzJhR/6u/+qv+db/+9a/r7e3t9VtvvbWBM2Q0+ad/+qd6kvrjjz/ev27jxo31JPUnnnjiiNv57uKV3vjGN9aXL18+YN0555xT//CHP3zY8X/2Z39WP+eccwas+8M//MP6vHnzGjZHRq/B7l8PPfRQPUn9F7/4xTDMjpI4YtRgGzZsyLRp03L22Wfnuuuuy3PPPdfsKTEGPP3009m9e3cWLFjQv661tTVvfetb89hjjzVxZowkGzduTHt7e970pjf1r5s3b17a29uPuZ/47uJl+/fvz+bNmwd83yTJggULjrgfbdy48ZDxl112WTZt2pSXXnqpYXNl9DmR/etlF1xwQWbOnJlLL700Dz30UCOnSSGEUQMtXLgwd955Z77zne/k05/+dL7//e/n7W9/e3p6epo9NUa53bt3J0mmT58+YP306dP7P4Pdu3dn2rRph6yfNm3aUfcT3138e3v27MmBAwcG9X2ze/fuw47v7e3Nnj17GjZXRp8T2b9mzpyZ2267LevWrcvXvva1zJkzJ5deemkeeeSR4ZgyY1jRYXTTTTcd9uK9f//atGnTCf/5V111Va688sqcf/75WbRoUb71rW/lxz/+ce67774h/C0YqRq9fyVJrVYbsFyv1w9Zx9gzmH3rcPvDsfYT310czmC/bw43/nDrIRnc/jVnzpxcd911ufDCCzN//vzccsstufLKK/OpT31qOKbKGNbS7Ak004c+9KG8//3vP+qYM888c8h+3syZM3PGGWfkqaeeGrI/k5GrkfvXjBkzklT/Kjtz5sz+9c8999wh/+rG2HO8+9YPfvCD/OxnPzvks5///OeD2k98d5Vt6tSpGT9+/CH/en+075sZM2YcdnxLS0te85rXNGyujD4nsn8dzrx587J27dqhnh6FKTqMpk6dmqlTpw7bz3v++eezc+fOAX+RZexq5P511llnZcaMGVm/fn0uuOCCJNV52g8//HA++clPNuRnMnIc7741f/78dHV15Xvf+17e+MY3Jkm++93vpqurKxdffPFx/zzfXWWbMGFCOjo6sn79+rznPe/pX79+/fq8613vOuw28+fPzze/+c0B6x588MFcdNFFOeWUUxo6X0aXE9m/DmfLli2+ozhpRZ9KNxg7duzI1q1bs2PHjhw4cCBbt27N1q1b8+KLL/aPOeecc3LPPfckSV588cWsWrUqGzduzE9+8pNs2LAhixYtytSpUwf8jw/J4PevWq2WFStW5BOf+ETuueee/OM//mOuvfbaTJ48OVdffXWzfg1GmHPPPTeXX355rrvuujz++ON5/PHHc9111+Wd73xn5syZ0z/OdxfHsnLlynzxi1/M7bffnu3bt+eGG27Ijh07snz58iTJjTfemGuuuaZ//PLly/PTn/40K1euzPbt23P77bfnS1/6UlatWtWsX4ERbLD71+rVq3Pvvffmqaeeyo9+9KPceOONWbduXT70oQ8161dgrGjqPfFGkaVLl9aTHPJ66KGH+sckqd9xxx31er1e/9WvflVfsGBB/bWvfW39lFNOqZ9++un1pUuX1nfs2NGcX4ARbbD7V71e3bL7Yx/7WH3GjBn11tbW+lve8pb6D3/4w+GfPCPa888/X//ABz5QnzJlSn3KlCn1D3zgA4fc4tZ3F8fj85//fP2MM86oT5gwoX7hhRfWH3744f7Pli5dWn/rW986YPyGDRvqF1xwQX3ChAn1M888s/6FL3xhmGfMaDKY/euTn/xk/Td/8zfrEydOrP/Gb/xG/c1vfnP9vvvua8KsGWtq9fq/XQ0JAABQKKfSAQAAxRNGAABA8YQRAABQPGEEAAAUTxgBAADFE0YAAEDxhBEAAFA8YQQAABRPGAEAAMUTRgAAQPGEEQAAULz/H0TsW122D28CAAAAAElFTkSuQmCC", "text/plain": [ "<Figure size 1000x700 with 1 Axes>" ] @@ -533,7 +535,7 @@ }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 24, "id": "b835049a-27a7-4d45-b819-b9e4d813fdcf", "metadata": { "nbgrader": { @@ -548,44 +550,44 @@ }, "outputs": [], "source": [ - "def merge_sort(arr): \n", + "def merge_sort(L):\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", + " if len(L) > 1:\n", + " mid = len(L) // 2\n", + " left_half = L[:mid]\n", + " right_half = L[mid:]\n", + "\n", + " merge_sort(left_half)\n", + " merge_sort(right_half)\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", + "\n", + " while i < len(left_half) and j < len(right_half):\n", + " if left_half[i] < right_half[j]:\n", + " L[k] = left_half[i]\n", + " i += 1\n", + " else:\n", + " L[k] = right_half[j]\n", + " j += 1\n", + " k += 1\n", + "\n", + " while i < len(left_half):\n", + " L[k] = left_half[i]\n", + " i += 1\n", + " k += 1\n", + "\n", + " while j < len(right_half):\n", + " L[k] = right_half[j]\n", + " j += 1\n", + " k += 1\n", + "\n", + " return L\n", " ### END SOLUTION" ] }, { "cell_type": "code", - "execution_count": 109, + "execution_count": 25, "id": "4ad36fe3-7e9e-4ae8-9df6-f816e24d6c28", "metadata": { "tags": [] @@ -597,7 +599,7 @@ "[1, 3, 6]" ] }, - "execution_count": 109, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -608,7 +610,7 @@ }, { "cell_type": "code", - "execution_count": 110, + "execution_count": 26, "id": "85865859-828f-4a6b-aad8-51ba0de8ac5b", "metadata": { "nbgrader": { @@ -642,7 +644,7 @@ }, { "cell_type": "code", - "execution_count": 175, + "execution_count": 50, "id": "e0ed0841-1255-4bdb-b7f8-e689c2a953af", "metadata": { "nbgrader": { @@ -659,24 +661,27 @@ "source": [ "def interval_scheduling(intervals):\n", " ### BEGIN SOLUTION\n", + " if not intervals:\n", + " return []\n", "\n", - " intervals.sort(key=lambda x: x[1], reverse=True)\n", + " intervals.sort(key=lambda x: x[1])\n", " \n", - " selected_intervals = []\n", - " current_end_time = float('-inf')\n", + " selected_intervals = [intervals[0]]\n", + " current_end_time = intervals[0][1]\n", " \n", - " for interval in intervals:\n", + " for interval in intervals[1:]:\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", + "\n", " return selected_intervals\n", " ### END SOLUTION" ] }, { "cell_type": "code", - "execution_count": 176, + "execution_count": 51, "id": "d6a50280-f016-4686-8515-1b4a136c0fd9", "metadata": { "tags": [] @@ -688,7 +693,7 @@ "[(0, 2), (2, 4)]" ] }, - "execution_count": 176, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -699,7 +704,7 @@ }, { "cell_type": "code", - "execution_count": 178, + "execution_count": 52, "id": "bd38d673-077d-44b1-b81c-ac7448f5c01c", "metadata": { "nbgrader": { @@ -720,7 +725,7 @@ }, { "cell_type": "code", - "execution_count": 179, + "execution_count": 53, "id": "de2c9857-9925-4477-96e4-341fa5bc4ec8", "metadata": { "tags": [] @@ -742,7 +747,7 @@ }, { "cell_type": "code", - "execution_count": 181, + "execution_count": 54, "id": "095836b8-2612-4d58-a9ce-b7968489418c", "metadata": { "nbgrader": { @@ -775,7 +780,7 @@ }, { "cell_type": "code", - "execution_count": 182, + "execution_count": 55, "id": "c87ae1e2-7497-4920-9597-d1b3cddf8580", "metadata": { "tags": [] @@ -787,7 +792,7 @@ "[(0, 4), (5, 7)]" ] }, - "execution_count": 182, + "execution_count": 55, "metadata": {}, "output_type": "execute_result" } @@ -798,7 +803,7 @@ }, { "cell_type": "code", - "execution_count": 183, + "execution_count": 56, "id": "afcf3a69-aa36-4b71-93a8-218337ed88b9", "metadata": { "nbgrader": { @@ -853,7 +858,7 @@ }, { "cell_type": "code", - "execution_count": 134, + "execution_count": 57, "id": "46947633-3f5f-420b-b416-500a0dab4fcb", "metadata": { "nbgrader": { @@ -898,7 +903,7 @@ }, { "cell_type": "code", - "execution_count": 135, + "execution_count": 58, "id": "737df134-4ee1-49a9-9034-da3b0267ae84", "metadata": { "tags": [] @@ -922,7 +927,7 @@ }, { "cell_type": "code", - "execution_count": 136, + "execution_count": 59, "id": "a66c6e52-2d60-4423-bc3a-a3dcef38da26", "metadata": { "tags": [] @@ -950,7 +955,7 @@ }, { "cell_type": "code", - "execution_count": 137, + "execution_count": 60, "id": "2be5f2f7-d9a5-4cf0-afe4-5a5a54ee0434", "metadata": { "nbgrader": { @@ -995,7 +1000,7 @@ }, { "cell_type": "code", - "execution_count": 138, + "execution_count": 61, "id": "5adf0e0e-73c4-4656-b546-5b02347f1a28", "metadata": {}, "outputs": [ @@ -1014,69 +1019,10 @@ "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", + "id": "ef20e870-fcba-4610-bf22-37af0b0691a0", "metadata": {}, "outputs": [], "source": [] diff --git a/exercises/07-stacks-queues.ipynb b/solutions/07-stacks-queues-exercises.ipynb similarity index 50% rename from exercises/07-stacks-queues.ipynb rename to solutions/07-stacks-queues-exercises.ipynb index de89e546f9532bc55e4f57f47f9ed17ceeeb8bb5..adc6ca745daa7fd7919295371d1c8da5c5260d0d 100644 --- a/exercises/07-stacks-queues.ipynb +++ b/solutions/07-stacks-queues-exercises.ipynb @@ -1,44 +1,5 @@ { "cells": [ - { - "cell_type": "markdown", - "id": "bfc5eefb", - "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": "ebe01f37", - "metadata": {}, - "outputs": [], - "source": [ - "ID = \"\"\n", - "COLLABORATORS_ID = []" - ] - }, - { - "cell_type": "markdown", - "id": "796b04b3", - "metadata": {}, - "source": [ - "---" - ] - }, { "cell_type": "markdown", "id": "2f1f2dcd-96a9-45ef-90a6-4ad488635679", @@ -69,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "4932473d-2734-4e81-b777-ca10decfd9e8", "metadata": { "tags": [] @@ -97,13 +58,10 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "8b77ae34-ef7c-4664-94e0-8928156f2224", "metadata": { - "deletable": false, "nbgrader": { - "cell_type": "code", - "checksum": "128a37273e0e5da052abe4bf08bb1c27", "grade": false, "grade_id": "cell-5b0828e97507162e", "locked": false, @@ -116,25 +74,46 @@ "outputs": [], "source": [ "def reverse_string(s):\n", - " # YOUR CODE HERE\n", - " raise NotImplementedError()" + " ### BEGIN SOLUTION ###\n", + " stack = Stack()\n", + " reversed_string = \"\"\n", + "\n", + " for char in s: # push char in the stack\n", + " stack.push(char)\n", + "\n", + " while not stack.is_empty(): # pop from the stack\n", + " reversed_string += stack.pop()\n", + "\n", + " return reversed_string\n", + " ### END SOLUTION ###" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "63719c8e-f60c-4544-8e41-cb6380ae4bcf", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'olleH'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "reverse_string(\"Hello\")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "81e93620-0664-4a9d-ba5f-894937c9769e", "metadata": { "tags": [] @@ -155,13 +134,10 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "cf6fbdd5-53c5-45c2-a0c5-a5ed845c4f81", "metadata": { - "deletable": false, "nbgrader": { - "cell_type": "code", - "checksum": "048d788477e620bf78240329c0dd8771", "grade": false, "grade_id": "is_palindrome", "locked": false, @@ -174,32 +150,40 @@ "outputs": [], "source": [ "def is_palindrome(s):\n", - " # YOUR CODE HERE\n", - " raise NotImplementedError()" + " ### BEGIN SOLUTION ###\n", + " return s == reverse_string(s)\n", + " ### END SOLUTION ###" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "586bafba-2fbb-4833-b2e3-609db9b28fbf", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "is_palindrome(\"ABA\")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "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, @@ -227,7 +211,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "ddcccaf6-d235-4327-826f-7a62a4c23f28", "metadata": { "tags": [] @@ -239,14 +223,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "2da2db1e-f55d-43b4-877f-96ef944818e8", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(1, 'banana')\n" + ] + } + ], "source": [ - "# how to use the modue\n", + "# how to use the queue module\n", "priority_queue = PriorityQueue()\n", "priority_queue.put((3, 'apple'))\n", "priority_queue.put((1, 'banana'))\n", @@ -257,13 +249,10 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "804ea32d-5bf8-42b9-ae52-6318b26f4065", "metadata": { - "deletable": false, "nbgrader": { - "cell_type": "code", - "checksum": "7f6b90fc037aa2a24fa9ce3b4dfca6dd", "grade": false, "grade_id": "cell-4b9a5ecdee87514e", "locked": false, @@ -275,13 +264,30 @@ }, "outputs": [], "source": [ - "# YOUR CODE HERE\n", - "raise NotImplementedError()" + "### BEGIN SOLUTION ###\n", + "class MinHeap:\n", + " def __init__(self):\n", + " self.heap = PriorityQueue()\n", + "\n", + " def insert(self, value):\n", + " self.heap.put(value)\n", + "\n", + " def pop(self):\n", + " if not self.is_empty():\n", + " return self.heap.get()\n", + "\n", + " def peek(self):\n", + " if not self.is_empty():\n", + " return self.heap.queue[0]\n", + "\n", + " def is_empty(self):\n", + " return self.heap.empty()\n", + "### END SOLUTION ###" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "1b2d28c4-277b-44fa-b7e8-590aa00f8f70", "metadata": { "tags": [] @@ -296,7 +302,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "ed61bced-f000-41c6-8ecd-d669b4edb700", "metadata": { "tags": [] @@ -307,6 +313,146 @@ "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": 13, + "id": "4cc7f805-0887-4422-b6b7-3d591d0df1fb", + "metadata": { + "nbgrader": { + "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", + "### BEGIN SOLUTION ###\n", + " if op == '+':\n", + " return a + b\n", + " elif op == '-':\n", + " return a - b\n", + " elif op == '*':\n", + " return a * b\n", + " elif op == '/':\n", + " return a / b\n", + "### END SOLUTION ###" + ] + }, + { + "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": 14, + "id": "e792c90d-1b38-47f5-9879-399debc934b9", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e9236618b265b34f", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def evaluate_postfix(expression):\n", + "### BEGIN SOLUTION ###\n", + " stack = []\n", + " operators = set(['+', '-', '*', '/'])\n", + "\n", + " tokens = expression.split()\n", + " \n", + " for token in tokens:\n", + " if token not in operators:\n", + " stack.append(float(token))\n", + " else:\n", + " if len(stack) < 2:\n", + " raise ValueError(\"Invalid expression\")\n", + " b = stack.pop()\n", + " a = stack.pop()\n", + " result = apply_operator(token, b, a)\n", + " stack.append(result)\n", + "\n", + " if len(stack) != 1:\n", + " raise ValueError(\"Invalid expression\")\n", + "\n", + " return stack[0]\n", + "### END SOLUTION ###" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "ea6e4840-1b7e-4265-b37d-e8c45ea6b3ed", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "14.0\n" + ] + } + ], + "source": [ + "postfix_expression = \"3 4 + 2 *\"\n", + "print(evaluate_postfix(postfix_expression))" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "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": {