{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "3aefd380",
   "metadata": {},
   "source": [
    "<div style=\"width:100%\"><a href=\"https://www.dunod.com/sciences-techniques/programmation-python-par-pratique-problemes-et-exercices-corriges-0\"><img src=\"https://python.guillod.org/livre/banner2.png\"/></a></div>\n",
    "\n",
    "Ce fichier reflète les énoncés des exercices d'un chapitre de la deuxième édition du livre *[Programmation Python par la pratique](https://www.dunod.com/sciences-techniques/programmation-python-par-pratique-problemes-et-exercices-corriges-0)*. Tous les énoncés peuvent être téléchargés au format [Jupyter Notebook](https://python.guillod.org/livre/) ou exécutés directement en ligne sur [mybinder](https://mybinder.org/v2/gh/guillod/livre-python/HEAD). Les corrigés sont disponibles dans le livre en version papier (ISBN [9782100862122](https://www.dunod.com/sciences-techniques/programmation-python-par-pratique-problemes-et-exercices-corriges-0)) ou numérique (ISBN [9782100875153](https://www.dunod.com/sciences-techniques/programmation-python-par-pratique-problemes-et-exercices-corriges-0)) publiés chez Dunod. Ce fichier reflète les exercices tels que publiés dans cet ouvrage et diffère quelque peu des exercices présentés à la page [python.guillod.org](https://python.guillod.org/)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aab2294d",
   "metadata": {},
   "source": [
    "# 3 Structures homogènes"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10f0b35e",
   "metadata": {},
   "source": [
    "<div id=\"ch:numpy\"></div>\n",
    "\n",
    "Les structures de données par défaut de Python permettent de gérer des données hétérogènes (par exemple des entiers et des chaînes de caractères). Cette particularité fait que les structures de données Python sont extrêmement flexibles, au détriment de la performance. En effet vu que des données hétérogènes doivent pouvoir être supportées, il n'est pas possible d'allouer une plage de mémoire fixe pour une structure de données, ce qui ralentit son utilisation.  Particulièrement en mathématiques, il apparaît très régulièrement des ensembles de données homogènes de tailles fixes (liste d'entiers, vecteurs réels ou complexes, matrices...). Le module NumPy définit le type `ndarray` qui est optimisé pour de telles structures de données homogènes de tailles fixes. La documentation de NumPy est disponible [ici](https://numpy.org/doc/stable/).\n",
    "\n",
    "Pour charger le module NumPy, il est d'usage de procéder ainsi:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "52857805",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da145de3",
   "metadata": {},
   "source": [
    "**Concepts abordés:**\n",
    "\n",
    "* tableau de données homogènes\n",
    "\n",
    "* slicing\n",
    "\n",
    "* opérations vectorielles\n",
    "\n",
    "* indexage et sélection"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28028a75",
   "metadata": {},
   "source": [
    "# Exercice 3.1: Introduction à NumPy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15ae1e20",
   "metadata": {},
   "source": [
    "**Création.**\n",
    "La taille et le type des éléments d'un tableau NumPy doivent être connus à l'avance. La première façon de créer un tableau NumPy est de construire un tableau rempli de zéros en spécifiant la taille et le type:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "dc6dd639",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "array0 = np.zeros(3, dtype=int) # vecteur de 3 entiers\n",
    "array1 = np.zeros((2,4), dtype=float) # tableau de flottants de taille 2x4\n",
    "array2 = np.zeros((2,2), dtype=complex) # matrice carrée complexe de taille 2x2\n",
    "array3 = np.zeros((5,6,4)) # tableau tridimensionnel de flottants"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56bac6ac",
   "metadata": {},
   "source": [
    "La seconde façon est de passer directement les données:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "aad5c9d5",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "array4 = np.array([1,4,5]) # vecteur d'entiers (1,4,5)\n",
    "array5 = np.array([[1.1,2.2,3.3,4.4],[1,2,3,4]]) # matrice de taille 2x4 de flottants\n",
    "array6 = np.array([[1+1j,0.4],[3,1.5]]) # matrice complexe de taille 2x2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "222a6468",
   "metadata": {},
   "source": [
    "NumPy va alors déterminer lui-même le type et la taille du tableau. À noter qu'il est possible de forcer le type:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cc0dfc9e",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "array0 = np.array([1,4,5], dtype=complex) # vecteur de complexes"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6b05321",
   "metadata": {},
   "source": [
    "Le type des éléments du tableau NumPy `array1` peut être déterminé par `array1.dtype`. La taille de ce tableau est donnée par `array1.shape`.\n",
    "Les commandes suivantes permettent d'accéder aux éléments des tableaux :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "ad52987e",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "array4[1] # retourne 4\n",
    "array5[1,3] # retourne 4.0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c6e4396",
   "metadata": {},
   "source": [
    "À noter que les indices commencent à 0 et non pas à 1.\n",
    "Les tableaux NumPy sont mutables dans le sens où les données peuvent être modifiées mais en conservant le même type et la même taille:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "27bd4aa8",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "array0[1] = 4\n",
    "array1[1,3] = 3.3\n",
    "array3[3,4,2] = 3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e06d4eb",
   "metadata": {},
   "source": [
    "**Slicing.**\n",
    "Le slicing permet d'accéder à certaines parties d'un tableau:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "f6aca5d8",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "array4[2:3] # retourne les éléments d'indices compris entre 2 et 3\n",
    "array1[0,:] # retourne la première ligne de array1\n",
    "array1[:,-1] # retourne la dernière colonne de array1\n",
    "array3[3,3:5,1:4] # retourne la sous-matrice correspondante"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4637ce2d",
   "metadata": {},
   "source": [
    "**Itération.**\n",
    "Il est possible d'itérer un tableau sur sa première dimension, par exemple pour retourner la somme des lignes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "2d4ee770",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "for i in array5:\n",
    "    print(np.sum(i))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad93e7c8",
   "metadata": {},
   "source": [
    "**a)**\n",
    "Étudier la documentation de la fonction `arange` et utiliser cette fonction pour générer les vecteurs $(5,6,7,8,9)$ et $(3,5,7,9)$s.\n",
    "\n",
    "**Indication.**\n",
    "La documentation de la fonction `arange` est disponible [ici](https://numpy.org/doc/stable/reference/generated/numpy.arange.html)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e031869c",
   "metadata": {},
   "source": [
    "**b)**\n",
    "Étudier la documentation de la fonction `linspace` et l'utiliser pour générer 10 points équidistribués dans l'intervalle $[2,5]$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73d695e7",
   "metadata": {},
   "source": [
    "**c)**\n",
    "Lire la documentation de la fonction `reshape` et effectuer successivement les transformations suivantes:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20c95873",
   "metadata": {},
   "source": [
    "$$\n",
    "(1,2,3,4,5,6)\\to\\begin{pmatrix}1 & 2\\\\ \n",
    "3 & 4\\\\ \n",
    "5 & 6\n",
    "\\end{pmatrix}\\to\\begin{pmatrix}1 & 2 & 3\\\\ \n",
    "4 & 5 & 6\n",
    "\\end{pmatrix}\\to\\begin{pmatrix}1 & 4\\\\ \n",
    "2 & 5\\\\ \n",
    "3 & 6\n",
    "\\end{pmatrix}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef3914a3",
   "metadata": {},
   "source": [
    "# Exercice 3.2: Opérations sur les tableaux"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f36a1cf",
   "metadata": {},
   "source": [
    "Les opérations arithmétiques de base sur les tableaux NumPy sont effectuées élément par élément:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "70ffa769",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "mat1 = np.array([[1,2.5,3],[5,6.1,8],[3,2,5]])\n",
    "mat2 = np.array([[1,0.5,0],[0,0.9,8],[2,0,0]])\n",
    "mat1 + mat2 # retourne la somme élément par élément\n",
    "mat1 * mat2 # retourne le produit élément par élément (pas le produit matriciel)\n",
    "10*mat1**2 # retourne 10 fois le carré des éléments de mat1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d41152fb",
   "metadata": {},
   "source": [
    "La plupart des fonctions mathématiques définies par NumPy (voir <https://numpy.org/doc/stable/reference/routines.math.html>) sont également effectuées élément par élément:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "c6722014",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "np.cos(mat1) # retourne le cosinus élément par élément de mat1\n",
    "np.exp(mat1) # retourne l'exponentielle élément par élément de mat1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c860a91",
   "metadata": {},
   "source": [
    "Le produit matriciel peut être effectué d'une des trois façons suivantes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "36a6afc6",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "np.dot(mat1,mat2)\n",
    "mat1.dot(mat2)\n",
    "mat1 @ mat2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "448a15bf",
   "metadata": {},
   "source": [
    "**a)**\n",
    "Donné un vecteur $(v_0,v_1,\\dots,v_{n-1})\\in\\mathbb{R}^n$ la dérivée discrète de ce vecteur est définie par le vecteur $(d_0,d_1,\\dots,d_{n-2})\\in\\mathbb{R}^{n-1}$ donné par $d_i = v_{i+1}-v_{i}$ pour $i=0,1,\\dots,n-2$.\n",
    "\n",
    "Écrire une fonction `diff_list` qui calcule la dérivée discrète d'une liste et une fonction `diff_np` qui fait la même opération mais sur des vecteurs NumPy en utilisant le slicing."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "01e627a6",
   "metadata": {},
   "source": [
    "**b)**\n",
    "Soit `a_list` et `a_np` respectivement une liste et un tableau de 1 000 éléments tirés au hasard dans l'intervalle $[0,1]$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "08915df4",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "a_list = [np.random.random() for _ in range(1000)]\n",
    "a_np = np.random.random(1000)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4639b817",
   "metadata": {},
   "source": [
    "Comparer le temps d'exécution de `diff_list(a_list)` et de `diff_np(a_np)`.\n",
    "\n",
    "**Indication.**\n",
    "Dans Jupyter Lab, il est très facile de déterminer le temps pris par une cellule pour s'évaluer, il suffit de commencer la cellule par `%%time`, par exemple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "a76d0175",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "result = diff_list(a_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2211d3cb",
   "metadata": {},
   "source": [
    "Pour évaluer la cellule à de multiples reprises et faire une moyenne sur le temps d'exécution afin d'obtenir un résultat plus précis, remplacer `%%time` par `%%timeit`. La documentation est disponible [ici](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit).\n",
    "\n",
    "**Réponse.**\n",
    "Le temps d'exécution avec les tableaux NumPy devrait être approximativement 50 à 100 fois plus rapide qu'avec les listes !"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "456c2814",
   "metadata": {},
   "source": [
    "# Exercice 3.3: Matrice de Vandermonde"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33a9f0bc",
   "metadata": {},
   "source": [
    "Pour $p, n\\in \\mathbb{N}^*$ et $\\boldsymbol{x}= (x_1, \\ldots, x_p)$ un vecteur de taille $p$, la matrice de Vandermonde correspondante est définie par:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58c6d3d6",
   "metadata": {},
   "source": [
    "$$\n",
    "V(\\boldsymbol{x},n)=\\begin{pmatrix}\n",
    "1 & x_1 & x_1^2 & \\cdots & x_1^{n-1} & x_1^n \\\\ \n",
    "1 & x_2 & x_2^2 & \\cdots & x_2^{n-1} & x_2^n \\\\ \n",
    "\\vdots & \\vdots& \\vdots & \\ddots & \\vdots & \\vdots\\\\ \n",
    "1 & x_{p-1} & x_{p-1}^2 & \\cdots & x_{p-1}^{n-1} & x_{p-1}^n \\\\ \n",
    "1 & x_p & x_p^2 & \\cdots & x_p^{n-1} & x_p^n\n",
    "\\end{pmatrix}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "222c12be",
   "metadata": {},
   "source": [
    "**a)**\n",
    "Écrire une fonction qui construit la matrice $V(\\boldsymbol{x},n)$ élément par élément à l'aide d'une double boucle."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e6b9272f",
   "metadata": {},
   "source": [
    "**b)**\n",
    "Après avoir établi une relation permettant d'écrire la $k$-ième colonne de $V(\\boldsymbol{x},n)$ uniquement en fonction de $\\boldsymbol{x}$ et de $k$, écrire une deuxième fonction qui construit la matrice $V(\\boldsymbol{x},n)$ colonne par colonne à l'aide de cette relation."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2419f641",
   "metadata": {},
   "source": [
    "**c)**\n",
    "Après avoir établi une relation entre la $k$-ième colonne de $V(\\boldsymbol{x},n)$, sa $(k-1)$-ième colonne et le vecteur $\\boldsymbol{x}$, écrire une troisième fonction qui construit la matrice $V(\\boldsymbol{x},n)$ colonne par colonne à l'aide de cette relation."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8741e4f3",
   "metadata": {},
   "source": [
    "**d)**\n",
    "Comparer les temps d'exécution de ces trois fonctions pour $n=150$, $p=100$ et $\\boldsymbol{x}$ généré aléatoirement."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "643219ee",
   "metadata": {},
   "source": [
    "# Exercice 3.4: <font color=\"red\">!</font> Indexage de tableaux"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aed77790",
   "metadata": {},
   "source": [
    "Le slicing permet de sélectionner des blocs dans un tableau, mais il est également possible de sélectionner des éléments disparates en utilisant un tableau comme indexage:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "4658e74d",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "a = np.arange(12)**2 # tableau des carrés parfaits\n",
    "i = np.array([1,3,8,5]) # tableau d'indices\n",
    "a[i] # tableau des éléments de a aux places i"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2105c159",
   "metadata": {},
   "source": [
    "À noter qu'il est également possible d'indexer par un tableau de dimension supérieure. Le résultat est alors un tableau de la même forme que l'index:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "18034450",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "j = np.array([[3,4],[9,7]]) # tableau bidimensionnel d'indices\n",
    "a[j] # sélectionne les éléments de a avec les indices j"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56f62ba3",
   "metadata": {},
   "source": [
    "Pour un tableau à plusieurs dimensions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "008a57a7",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "b = np.array([[0,1,2,3],[4,5,6,7],[8,9,10,11]])\n",
    "i = np.array([0,1,2,2]) # tableau des premiers indices\n",
    "j = np.array([1,0,3,1]) # tableau des seconds indices\n",
    "b[i,j] # sélectionne les éléments d'indices ij"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5a8fcbb",
   "metadata": {},
   "source": [
    "Enfin il est possible d'indexer un tableau par un tableau de booléens:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "d4081ac5",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "c = np.array([[0,1,2,3],[4,5,6,7],[8,9,10,11]])\n",
    "cond = (c >= 5) # tableau de booléens valant True si >= 5 et False sinon\n",
    "c[cond] = 5 # assigne la valeur 5 à toutes les entrées de c plus grandes que 5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67694577",
   "metadata": {},
   "source": [
    "Pour la suite, on considère les nombres:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "260dd706",
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "[0.9602, -0.99, 0.2837, 0.9602, 0.7539, -0.1455, -0.99, -0.9111, 0.9602, -0.1455, -0.99, 0.5403, -0.99, 0.9602, 0.2837, -0.99, 0.2837, 0.9602]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2177ba89",
   "metadata": {},
   "source": [
    "comme étant les résultats d'une mesure effectuée toutes les 0.1 seconde aux temps compris entre 2 et 3.7 secondes.\n",
    "\n",
    "**a)**\n",
    "Les mesures étant censées être positives, modifier les données pour mettre 0 lorsque les valeurs sont négatives."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26b4072b",
   "metadata": {},
   "source": [
    "**b)**\n",
    "Calculer les temps pour lesquels les mesures précédentes sont maximales."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a459e45",
   "metadata": {},
   "source": [
    "**c)**\n",
    "Pour chaque mesure maximale, retourner la mesure précédente, la mesure maximale et la mesure suivante. Si la mesure précédente ou la suivante n'existent pas, les remplacer par `np.nan`."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}