From 416f720aa3da707de17b44909f474c991165b41b Mon Sep 17 00:00:00 2001 From: Romain Vuillemot <romain.vuillemot@gmail.com> Date: Tue, 10 Oct 2023 08:19:32 +0200 Subject: [PATCH] more updates --- 04-05-06-programming-strategies.ipynb | 1137 +++++++++++++++++++++++++ 07-stacks-queues.ipynb | 671 +++++++++++++++ 2 files changed, 1808 insertions(+) create mode 100644 04-05-06-programming-strategies.ipynb create mode 100644 07-stacks-queues.ipynb diff --git a/04-05-06-programming-strategies.ipynb b/04-05-06-programming-strategies.ipynb new file mode 100644 index 0000000..f1d734f --- /dev/null +++ b/04-05-06-programming-strategies.ipynb @@ -0,0 +1,1137 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "75778ca0", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# UE5 Fundamentals of Algorithms\n", + "## Lecture 4-5-6: 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": "f0c7488c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Outline\n", + "- Definitions of programming strategies\n", + "- Divide and conquer\n", + "- Greedy algorithms\n", + "- Dynamic programming" + ] + }, + { + "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": "bd22ed47", + "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": "d12319a6", + "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": "fbb1b64c", + "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\">\n", + "\n", + "See [the notebook](03-lists-search-sort.ipynb)." + ] + }, + { + "cell_type": "markdown", + "id": "bba7c4c6", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Quick sort\n", + "\n", + "Recursive sorting algorithm which works in two steps:\n", + "\n", + "1. select a pivot element \n", + "2. partitioning the array into smaller sub-arrays, then sorting those sub-arrays.\n", + "\n", + "<img src=\"figures/quicksort.png\" style=\"height:400px\">" + ] + }, + { + "cell_type": "markdown", + "id": "400d3619", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Merge sort\n", + "\n", + "Divide an array recursively into two halves (based on a _pivot_ value), sorting each half, and then merging the sorted halves back together. This process continues until the entire array is sorted.<br> Complexity: $O(n log(n))$.\n", + "\n", + "<img src=\"figures/tri-fusion.png\" style=\"width:500px\">" + ] + }, + { + "cell_type": "markdown", + "id": "7d33da6c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Map reduce\n", + "\n", + "Divide a large dataset into smaller chunks and processes them independantly. Two main steps: \n", + "- the Map stage, where data is filtered and transformed into key-value pairs\n", + "- the Reduce stage, where data is aggregated and the final result is produced.\n", + "<img src=\"figures/Mapreduce.png\" style=\"width:700px\">" + ] + }, + { + "cell_type": "markdown", + "id": "a590fbe9", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Map reduce (without map reduce..)" + ] + }, + { + "cell_type": "markdown", + "id": "9f163e56", + "metadata": {}, + "source": [ + "_Calculate the sum of squares values from a list of numerical values._" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "ba28ddd1", + "metadata": {}, + "outputs": [], + "source": [ + "data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "f34730fa", + "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", + "385\n" + ] + } + ], + "source": [ + "result = {}\n", + "for num in data:\n", + " square = num * num\n", + " result[square] = num\n", + "\n", + "final_result = list(result.items())\n", + "\n", + "print(final_result)\n", + "print(sum([x[0] for x in final_result]))" + ] + }, + { + "cell_type": "markdown", + "id": "261c7a2c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Map reduce (Python)\n", + "\n", + "1. Divide the problem in sub-problems\n", + "2. Apply the mapping function\n", + "3. Reduce the results" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "9ef9a5cc", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "385\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" + ] + }, + { + "cell_type": "markdown", + "id": "975a4b17", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Map reduce (Python)\n", + "\n", + "1. Divide the problem in sub-problems\n", + "2. Apply the mapping function\n", + "3. Reduce the results" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "77215ac8", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'data' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[5], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m chunk_size \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m2\u001b[39m\n\u001b[0;32m----> 2\u001b[0m chunks \u001b[38;5;241m=\u001b[39m [data[i:i\u001b[38;5;241m+\u001b[39mchunk_size] \u001b[38;5;28;01mfor\u001b[39;00m i \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(\u001b[38;5;241m0\u001b[39m, \u001b[38;5;28mlen\u001b[39m(\u001b[43mdata\u001b[49m), chunk_size)]\n\u001b[1;32m 4\u001b[0m mapped_data \u001b[38;5;241m=\u001b[39m [mapper(chunk) \u001b[38;5;28;01mfor\u001b[39;00m chunk \u001b[38;5;129;01min\u001b[39;00m chunks] \n\u001b[1;32m 6\u001b[0m grouped_data \u001b[38;5;241m=\u001b[39m {}\u001b[38;5;66;03m# map\u001b[39;00m\n", + "\u001b[0;31mNameError\u001b[0m: name 'data' is not defined" + ] + } + ], + "source": [ + "\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] \n", + "\n", + "grouped_data = {}# map\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", + "result = sum([x[1][0] for x in final_result])\n", + "\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "id": "be8744c6", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Discussion on Divide and Conquer\n", + "\n", + "- Similarities with recursion by dividing a problem in a sub-problem\n", + "\n", + "- But with a combination step (which may hold most of the code difficulty)\n", + "\n", + "- Can be implemented in a non-recursive way\n", + "\n", + "- $n log(n)$ complexity when split the problem and solves each split" + ] + }, + { + "cell_type": "markdown", + "id": "c1f7b96a", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Greedy algorithms\n", + "\n", + "> Algorithms that make a locally optimal choice.\n", + "\n", + "### Examples:\n", + "\n", + "- Change-making problem\n", + "- Knapsack problem\n", + "- Maze solving\n", + "- Graph coloring" + ] + }, + { + "cell_type": "markdown", + "id": "17867aaf", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Example: Change-making problem\n", + "\n", + "\n", + "$Q_{opt}(S,M) = min \\ \\sum_{i=1}^n x_i$.\n", + " \n", + "$S$: all the available coins\n", + " \n", + "$M$: amount\n", + " \n", + "Greedy solution:\n", + "\n", + "1. Sort the coins in descending order\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": "adb8552d", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Example: Change-making problem (Python)\n", + "\n", + "_Greedy solution to return the minimal number of coins necessary._" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "e900b357", + "metadata": {}, + "outputs": [], + "source": [ + "coins = [1, 2, 5]\n", + "amount = 11" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "b4e91e95", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def coin_change_greedy(coins, amount):\n", + " coins.sort(reverse=True) # important! sort 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" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "131184bf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n" + ] + } + ], + "source": [ + "print(coin_change_greedy(coins, amount)) # 3 (11 = 5 + 5 + 1)" + ] + }, + { + "cell_type": "markdown", + "id": "3ab88980", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Example: Change-making problem (Python)\n", + "\n", + "_Greedy solution that returns the **list of coins** used._\n", + "\n", + "\n", + "Tip: use a list with the same structure as coins." + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "9401818b", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Minimum coins needed: 6\n", + "Coins used: [2, 1, 0, 3]\n" + ] + } + ], + "source": [ + "def coin_change_greedy(coins, amount):\n", + " coins.sort(reverse=True) \n", + " \n", + " coin_count = 0\n", + " remaining_amount = amount\n", + " used_coins = [0] * len(coins)\n", + " \n", + " for i, coin in enumerate(coins):\n", + " while remaining_amount >= coin:\n", + " remaining_amount -= coin\n", + " coin_count += 1\n", + " used_coins[i] += 1 \n", + " \n", + " if remaining_amount == 0:\n", + " return coin_count, used_coins\n", + " else:\n", + " return -1, []\n", + "\n", + "coins = [25, 10, 5, 1]\n", + "amount = 63\n", + "min_coins, coins_used = coin_change_greedy(coins, amount)\n", + "\n", + "print(f\"Minimum coins needed: {min_coins}\")\n", + "print(\"Coins used:\", coins_used)" + ] + }, + { + "cell_type": "markdown", + "id": "1ae1863a", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Example: Change-making problem (Python)\n", + "\n", + "_Greedy solution that returns the **list of coins** used from **a limited availability of coins**._\n", + "\n", + "\n", + "Tip: use a list of coins availability of same structure as coins." + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "8a936fe3", + "metadata": {}, + "outputs": [], + "source": [ + "coins = [25, 10, 5, 1]\n", + "amount = 63\n", + "coin_availability = [1, 2, 3, 4]" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "1700c684", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Minimum coins needed: 9\n", + "Coins used: [1, 2, 3, 3]\n" + ] + } + ], + "source": [ + "def coin_change_greedy(coins, amount, coin_availability):\n", + " coins.sort(reverse=True)\n", + "\n", + " coin_count = 0\n", + " remaining_amount = amount\n", + " used_coins = [0] * len(coins)\n", + " \n", + " for i, coin in enumerate(coins):\n", + " while remaining_amount >= coin and used_coins[i] < coin_availability[i]:\n", + " remaining_amount -= coin\n", + " coin_count += 1\n", + " used_coins[i] += 1\n", + " \n", + " if remaining_amount == 0:\n", + " return coin_count, used_coins\n", + " else:\n", + " return -1, []\n", + "\n", + "min_coins, coins_used = coin_change_greedy(coins, amount, coin_availability)\n", + "\n", + "\n", + "print(f\"Minimum coins needed: {min_coins}\")\n", + "print(\"Coins used:\", coins_used)" + ] + }, + { + "cell_type": "markdown", + "id": "7aeac877", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Discussion on Greedy algorithms\n", + "\n", + "- Often considered as an _heuristic_\n", + "- Easy to understand, implement and communicate\n", + "- They often lead to non-optimal solution" + ] + }, + { + "cell_type": "markdown", + "id": "5f36ec7b", + "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": "b125a618", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Examples of dynamic programming algorithms\n", + "\n", + "- Fibonacci Sequence\n", + "- Rod Cutting\n", + "- Sequence Alignment, Longest Subsequence Finding\n", + "- Shortest Path Findin" + ] + }, + { + "cell_type": "markdown", + "id": "3e3de7c4", + "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": "cced97f7", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Fibonnacci (naive)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7bc7701a", + "metadata": {}, + "outputs": [], + "source": [ + "def fib(n):\n", + " if n < 2:\n", + " return n\n", + " else:\n", + " return fib(n - 1) + fib(n - 2)" + ] + }, + { + "cell_type": "markdown", + "id": "81eb2956", + "metadata": {}, + "source": [ + "Call tree (for $n = 6$):" + ] + }, + { + "cell_type": "markdown", + "id": "b588ca8b", + "metadata": {}, + "source": [ + "<img src=\"figures/fibonacci-tree.png\" style=\"width:400px\">" + ] + }, + { + "cell_type": "markdown", + "id": "42b6ecd1", + "metadata": {}, + "source": [ + "Requires to calculate the same F-value multiple times." + ] + }, + { + "cell_type": "markdown", + "id": "fc74f400", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Fibonnacci (dynamic programming)\n", + "\n", + "_Optimized using a `lookup` table, which is a data structure to memoize values that have already been computed._" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "9eae506b", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6-th Fibonacci number is 8\n" + ] + } + ], + "source": [ + "def fib(n, lookup):\n", + " if n == 0 or n == 1:\n", + " lookup[n] = n\n", + "\n", + " if lookup[n] is None:\n", + " lookup[n] = fib(n - 1, lookup) + fib(n - 2, lookup)\n", + "\n", + " return lookup[n]\n", + "\n", + "def main():\n", + " n = 6\n", + "\n", + " lookup = [None] * (n + 1)\n", + " result = fib(n, lookup)\n", + " print(f\"{n}-th Fibonacci number is {result}\")\n", + "\n", + "if __name__==\"__main__\": \n", + " main() " + ] + }, + { + "cell_type": "markdown", + "id": "ac5e42aa", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Rod cutting \n", + "\n", + "_Given a list of cuts and prices, identify the optimal cuts. Given the example below, what is the best cutting strategy for a rod of size 2?_\n", + "\n", + "<img src=\"figures/rod-cutting.png\" style=\"width:500px\">" + ] + }, + { + "cell_type": "markdown", + "id": "2f933396", + "metadata": {}, + "source": [ + "| size (i) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |\n", + "|--------------|---|---|---|---|---|---|---|---|\n", + "| price (pi) | 1 | 5 | 8 | 9 |10 |17 |17 |20 |\n" + ] + }, + { + "cell_type": "markdown", + "id": "47119bfb", + "metadata": {}, + "source": [ + "Solution: $2$ with $5 + 5 = 10$." + ] + }, + { + "cell_type": "markdown", + "id": "61aa819a", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Rod cutting: check a solution\n", + "\n", + "Given the previous table of size and price, check the cost of a given solution by defining a function `check_rod_cutting(prices, n)`." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "c42b61d4", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def check_rod_cutting(prices, n):\n", + " table = [0] * (n + 1)\n", + "\n", + " for i in range(1, n + 1):\n", + " max_price = float('-inf')\n", + " for j in range(1, i + 1):\n", + " max_price = max(max_price, prices[j] + table[i - j])\n", + " table[i] = max_price\n", + "\n", + " return table[n]" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "9a3bd3d3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The maximum total price for a rod of length 2 is 5\n" + ] + } + ], + "source": [ + "prices = [0, 1, 5, 8, 9, 10, 17, 17, 20]\n", + "n = 2\n", + "\n", + "max_total_price = check_rod_cutting(prices, n)\n", + "print(f\"The maximum total price for a rod of length {n} is {max_total_price}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "151c0a39", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Rod cutting (brute force)\n", + "\n", + "Let's solve the rod cutting problem using a brute force (naive) approach.\n", + "\n", + "1. define a value function\n", + "2. identify a base case\n", + "3. identify a recursion mechanism" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "6e7a3a0d", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def cut_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": 34, + "id": "1ee43707", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The maximum value for a rod of length 2 is 5.\n" + ] + } + ], + "source": [ + "lengths = [0, 1, 2, 3, 4, 5, 6, 7, 8]\n", + "values = [0, 1, 5, 8, 9, 10, 17, 17, 20]\n", + "rod_length = 2\n", + "max_value = coupe_brute_force(rod_length, values)\n", + "print(f\"The maximum value for a rod of length {rod_length} is {max_value}.\")" + ] + }, + { + "cell_type": "markdown", + "id": "1596f825", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Rod cutting (dynamic programming)\n", + "\n", + "\n", + "General case:\n", + "\n", + "- Cutting a rod of length $i$ optimally.\n", + "- Cutting a rod of length $(n - i)$ optimally.\n", + "\n", + "\n", + "<img src=\"figures/rod-cutting-tree.png\" style=\"width:500px\">\n", + "\n", + "General case: $V_{n} = max_{1 \\leq i \\leq n} (p_i + V_{n - i})$ " + ] + }, + { + "cell_type": "markdown", + "id": "be5d9a15", + "metadata": {}, + "source": [ + "| size (i) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |\n", + "|--------------|---|---|---|---|---|---|---|---|\n", + "| price (pi) | 1 | 5 | 8 | 9 |10 |17 |17 |20 |\n" + ] + }, + { + "cell_type": "markdown", + "id": "b2845bd5", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "$V_{3} = \\max_{1 \\leq i \\leq 3} (p_i + V_{3 - i})$\n", + "\n", + "Let's calculate `V_3` step by step for each possible value of `i`:\n", + "\n", + "1. If `i = 1`, we cut the rod into two pieces: one of length 1 and one of length 2.\n", + " - $V_1 = p_1 = 2$\n", + " - $V_{3 - 1} = V_2$\n", + "\n", + "2. If `i = 2`, we cut the rod into two pieces: one of length 2 and one of length 1.\n", + " - $V_2 = p_2 = 5$\n", + " - $V_{3 - 2} = V_1$\n", + "\n", + "3. If `i = 3`, we cut the rod into one piece of length 3.\n", + " - $V_3 = p_3 = 9$\n", + " - $V_{3 - 3} = V_0$ (Assuming that $V_0 = 0$ as a base case.)" + ] + }, + { + "cell_type": "markdown", + "id": "eb092761", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Now, we can calculate the values for `V_2` and `V_1` recursively using the same formula:\n", + "\n", + "For `V_2`:\n", + "$$V_2 = \\max(p_1 + V_1, p_2 + V_0) = \\max(2 + V_1, 5 + 0) = \\max(2 + 2, 5 + 0) = \\max(4, 5) = 5$$\n", + "\n", + "For `V_1`:\n", + "$$V_1 = \\max(p_1 + V_0) = \\max(2 + 0) = 2$$\n", + "\n", + "So, `V_2` is 5 and `V_1` is 2.\n", + "\n", + "Now, we can calculate `V_3` using the values of `V_2` and `V_1`:\n", + "\n", + "$$V_3 = \\max(p_1 + V_2, p_2 + V_1, p_3 + V_0) = \\max(2 + 5, 5 + 2, 9 + 0) = \\max(7, 7, 9) = 9$$" + ] + }, + { + "cell_type": "markdown", + "id": "0e097cbf", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Rod cutting (dynamic programming)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "5812781c", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Max size cut 22 8\n" + ] + } + ], + "source": [ + "INT_MIN = 0\n", + "\n", + "def cutRod(price, n): \n", + "\n", + " # init cache tables\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(\"Max size cut \" + str(cutRod(arr, size)), len(arr) ) " + ] + }, + { + "cell_type": "markdown", + "id": "fd3e3af7", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Lessons on dynamic programming\n", + "\n", + "- It is necessary to study each problem on a case-by-case basis.\n", + "\n", + "- Storing a large number of partial results, which requires significant memory usage.\n", + "\n", + "- Suitable for only certain problems (min, max, counting the number of solutions).\n" + ] + } + ], + "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/07-stacks-queues.ipynb b/07-stacks-queues.ipynb new file mode 100644 index 0000000..e41de91 --- /dev/null +++ b/07-stacks-queues.ipynb @@ -0,0 +1,671 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a4973a08", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# UE5 Fundamentals of Algorithms\n", + "## Lecture 7: Stacks and queues\n", + "### Ecole Centrale de Lyon, Bachelor of Science in Data Science for Responsible Business\n", + "#### Romain Vuillemot\n", + "<center><img src=\"figures/Logo_ECL.png\" style=\"width:300px\"></center>\n", + "\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "ffa36fea", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Outline\n", + "- Definitions\n", + "- Stacks\n", + "- Queues\n", + "- Priority queues" + ] + }, + { + "cell_type": "markdown", + "id": "cd2e76d7", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Defintions\n", + "\n", + "> Stacks and queues allow the manipulation of values (or objects) sequentially. They have many operations, the main ones are: addition (push) and removal (pop), but with different order strategies:\n", + "\n", + "<img src=\"figures/stacks-queues.png\" style=\"width:500px\">\n", + "\n", + "- `stacks` follow the Last-In, First-Out (LIFO) principle\n", + "- `queues`follows the First-In, First-Out (FIFO) principle\n", + "\n", + "Note that stacks and queues define the operations and their results, but not their implementation." + ] + }, + { + "cell_type": "markdown", + "id": "cc2c4270", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Operations\n", + "\n", + "- `empty()`: Checks for emptiness.\n", + "- `full()`: Checks if it's full (if a maximum size was provided during creation).\n", + "- `get()`: Returns (and removes) an element.\n", + "- `push()`: Adds an element.\n", + "- `size()`: Returns the size of the list.\n", + "- `reverse()`: Reverses the order of elements.\n", + "- `peek()`: Returns an element (without removing it)." + ] + }, + { + "cell_type": "markdown", + "id": "c0885103", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Stacks\n", + "\n", + "> A stack is an abstract data type that follows the Last-In, First-Out (LIFO) principle\n", + "\n", + "- It supports operations on a collection of elements.\n", + "- The element inserted last is at the _head_.\n", + "- Easily achievable with a simple list! See this [Python tutorial](https://docs.python.org/3/tutorial/datastructures.html)" + ] + }, + { + "cell_type": "markdown", + "id": "363737b7", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Stacks (using lists)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "6326e146", + "metadata": {}, + "outputs": [], + "source": [ + "stack = [3, 4, 5]\n", + "stack.append(6) # push\n", + "stack.append(7)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "6604a331", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[3, 4, 5, 6, 7]\n", + "[3, 4, 5, 6]\n", + "[3, 4]\n", + "4\n" + ] + } + ], + "source": [ + "print(stack)\n", + "stack.pop() # get\n", + "print(stack)\n", + "stack.pop()\n", + "stack.pop()\n", + "print(stack)\n", + "print(stack[-1]) # peek" + ] + }, + { + "cell_type": "markdown", + "id": "075dc32b", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Stacks (using modules)\n", + "\n", + "https://docs.python.org/3/library/queue.html" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "b467ca83", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4 3 2 1 0 " + ] + } + ], + "source": [ + "import queue\n", + "pile = queue.LifoQueue()\n", + "\n", + "for i in range(5): pile.put(i)\n", + "\n", + "while not pile.empty(): \n", + " print(pile.get(), end=\" \")" + ] + }, + { + "cell_type": "markdown", + "id": "697d2be8", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Stacks (using OOP)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "8ae9a611", + "metadata": {}, + "outputs": [], + "source": [ + "class Stack():\n", + " def __init__(self, values = []):\n", + " self.__values = []\n", + " for v in values:\n", + " self.push(v)\n", + "\n", + " def push(self, v):\n", + " self.__values.append(v)\n", + " return v\n", + "\n", + " def get(self):\n", + " v = self.__values.pop()\n", + " return v\n", + "\n", + " def display(self):\n", + " for v in self.__values:\n", + " print(v)\n", + "\n", + " def size(self):\n", + " return len(self.__values)" + ] + }, + { + "cell_type": "markdown", + "id": "a0901fcb", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Stacks (using OOP)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "c61545dc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A\n", + "B\n", + "C\n" + ] + } + ], + "source": [ + "data = [\"A\", \"B\", \"C\"]\n", + "\n", + "s = Stack()\n", + "for d in data:\n", + " s.push(d)\n", + " e = s.pop()\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "id": "9145ef5e", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Queues\n", + "\n", + "> A stack is an abstract data type that follows the First-In, First-Out (FIFO) principle\n", + "\n", + "- Similar to a Srtack\n", + "- But the returned element is the first one inserted" + ] + }, + { + "cell_type": "markdown", + "id": "724ac3b7", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Queues (list)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "b2cc2dd0", + "metadata": {}, + "outputs": [], + "source": [ + "queue = [3, 4, 5]\n", + "queue.append(6)\n", + "queue.append(7) # push" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "1157237e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[3, 4, 5, 6, 7]\n", + "[4, 5, 6, 7]\n", + "[6, 7]\n", + "6\n" + ] + } + ], + "source": [ + "print(queue) \n", + "queue.pop(0) # get\n", + "print(queue)\n", + "queue.pop(0)\n", + "queue.pop(0)\n", + "print(queue)\n", + "print(queue[0]) # peek" + ] + }, + { + "cell_type": "markdown", + "id": "78d3504a", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Queues (module)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "a6392890", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 1 2 3 4 " + ] + } + ], + "source": [ + "import queue\n", + "\n", + "q = queue.Queue()\n", + "\n", + "for i in range (5): q.put(i)\n", + "\n", + "while not q.empty(): \n", + " print(q.get(), end=\" \")" + ] + }, + { + "cell_type": "markdown", + "id": "c2269f3b", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Priority queues\n", + "\n", + "> A **priority queue** is a queue (or stack or list) that returns an element based on the characteristics of a variable (priority).\n", + "\n", + "- For a quantitative variable, it's the minimum or maximum of the queue. For other types of variables (e.g., categories), any order relation is valid.\n", + "\n", + "- Queues can exhibit the same behavior but have a different internal state: either constantly updated or updated after reads/writes.\n", + "\n", + "- The internal state can be preserved with a sorting function, thus optimizing the complexity of the data structure.\n" + ] + }, + { + "cell_type": "markdown", + "id": "a46609ab", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Priority queues (module)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "6a31eba0", + "metadata": {}, + "outputs": [], + "source": [ + "from heapq import heapify, heappush, heappop\n", + "heap = [10, 8, 1, 2, 4, 9, 3, 4, 7]\n", + "heapify(heap)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "0fbe9868", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 3, 4, 4, 9, 10, 8, 7]" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "heap" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "c72a1070", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "heappop(heap)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "be80320d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2, 4, 3, 4, 7, 9, 10, 8]" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "heap" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "2aa86e2e", + "metadata": {}, + "outputs": [], + "source": [ + "heappush(heap, 5)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "5d5f290a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2, 4, 3, 4, 7, 9, 10, 8, 5]" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "heap" + ] + }, + { + "cell_type": "markdown", + "id": "b841cc1f", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Priority queues (using OOP)" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "e34b0c73", + "metadata": {}, + "outputs": [], + "source": [ + "class PriorityQueue(object): \n", + " def __init__(self): \n", + " self.__queue = [] \n", + " \n", + " def __str__(self): \n", + " return ' '.join([str(i) for i in self.__queue]) \n", + " \n", + " def isEmpty(self): \n", + " return len(self.__queue) == 0\n", + " \n", + " def insert(self, data): \n", + " self.__queue.append(data) \n", + " \n", + " def size(self): \n", + " return len(self.__queue)\n", + "\n", + " def delete(self): \n", + " min = 0\n", + " for i in range(0,len(self.__queue)): \n", + " if self.__queue[i][2] < self.__queue[min][2]: \n", + " min = i \n", + " item = self.__queue[min] \n", + " del self.__queue[min] \n", + " return item \n" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "0e326166", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "7\n", + "12\n", + "14\n" + ] + } + ], + "source": [ + "import queue\n", + "\n", + "myQueue = queue.PriorityQueue()\n", + "\n", + "# Insert elements into the priority queue\n", + "myQueue.put(12)\n", + "myQueue.put(1)\n", + "myQueue.put(14)\n", + "myQueue.put(7)\n", + "\n", + "# Print the contents of the priority queue\n", + "while not myQueue.empty():\n", + " print(myQueue.get())" + ] + }, + { + "cell_type": "markdown", + "id": "01f5f437", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + " ## Improvements\n", + "\n", + "- Handle empty lists" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5aa5a7ad", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def dequeue(self):\n", + " if not self.is_empty():\n", + " return self.items.pop(0)\n", + " else:\n", + " raise IndexError(\"Queue is empty\")\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89407366", + "metadata": {}, + "outputs": [], + "source": [ + "import queue\n", + "q = queue.Queue(5000)\n", + "# q.put([1,2,3,4])\n", + "q.put([2,3,4,5])\n", + "try:\n", + " [x1, x2, x3, x4] = q.get(block=True)\n", + "except queue.Empty:\n", + " print(\"Problème : aucun élément dans la file\")\n", + "else:\n", + " print([x1, x2, x3, x4])\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1bf32fdb", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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 +} -- GitLab