{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Control Flow Tools"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Expressions booléenne\n",
    "\n",
    "Les expressions booléenne retourne `True` ou `False`.\n",
    "\n",
    "Elles peuvent être construites avec les opérations suivantes :\n",
    "```python\n",
    "True, False, and, or, not, ==, !=, >, >=, <, <=, is, is not, in\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = 1\n",
    "b = 2\n",
    "print(a == b)\n",
    "print(a < b)\n",
    "print(a >= b or a == 1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Les opérateurs `and` et `or` sont des opérateurs **court circuits**: les expressions restantes ne sont évaluées que si elles peuvent modifier le résultat."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = [1, 2, 3]\n",
    "print(a[3] == 1)   # Erreur, il n'y a que 3 élément, l'indice 3 n'existe pas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(len(a) > 3 and a[3] == 1)  # Ici, pas d'erreur car le résultat est forcément faux après le premier test len(a) > 3.\n",
    "                                 # False and (False|True) = False. a[3] == 1 n'est donc pas évalué."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "L'expression `X is Y` retourne `True` si et seulement si `X` et `Y` font référence au **même objet**.\n",
    "Ainsi, l'opérateur d'egalité `==` n'implique pas tout le temps que les objets sont les mêmes (opérateur `is`), mais seulement que les valeurs le sont."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = [1, 2, 3]\n",
    "b = a                         # b est une référence à a. a et b sont donc le même objet.\n",
    "\n",
    "b.append(4)                   # on modifie b\n",
    "print(a, b, a is b, a == b)\n",
    "\n",
    "b = [1, 2, 3, 4]              # b est maintenant un nouvel objet.\n",
    "print(a, b, a is b, a == b)\n",
    "\n",
    "b = a.copy()                  # Pareil ici, b est une copie de a, pas une référence.\n",
    "print(a, b, a is b, a == b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Certains objets en Python sont **immutable** (`True`, `False`, `None`, chaînes de caractères, tuples, ...). Dans ce cas, `is` et `==` font la même chose."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = \"abc\"\n",
    "b = \"abc\"\n",
    "print(a, b, a is b, a == b)                                       # avec des objets immutable\n",
    "print(list(a), list(b), list(a) is list(b), list(a) == list(b))   # pareil mais avec des listes, qui sont mutables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Usage classique de l'opérateur `is`\n",
    "a = None\n",
    "if a is not None:\n",
    "    print(a+1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En général, on veut utiliser l'opérateur `==` pour simplement vérifier que les valeurs sont les mêmes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Les instructions `if`, `elif`, `else`\n",
    "\n",
    "`if` teste une expression booléenne. Si celle-ci retourne `True`, les lignes indentées après le `if` sont exécutées.\n",
    "\n",
    "- Ne pas oublier le caractère \":\" après le test pour signifier le début d'un bloc de commandes.\n",
    "- Le corps du bloc `if` doit être **indenté**. On utilise en général un nombre fixe d'espace (4 par exemples).\n",
    "- Ce sont les indentations qui disent si une commande fait partie du `if` ou si on en est sorti."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "x = 42\n",
    "if x < 0:                         # si x est strictement négatif (bien noté les \":\")\n",
    "    x = 0                         # on met x à 0\n",
    "    print('Négatif changé en 0')  # et on affiche ce texte\n",
    "elif x == 0:                      # Sinon, si x est nul (ce test n'est exécuté que si le premier était `False`.\n",
    "    print('Zéro')                 # On affiche le texte\n",
    "elif x == 1:                      # Sinon, si x vaut 1 (ce test n'est exécuté que si les deux précédent sont `False`\n",
    "    print('Un seul')              # On affiche le texte\n",
    "else:                             # Sinon (si tous les tests précédent sont `False`, on entre ici\n",
    "    print('Plusieurs')            # On affiche le texte\n",
    "    \n",
    "print(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Attention** : Dans un même bloc, les lignes doivent être indentées de la même façon !"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if x > 0:\n",
    "    x = 0\n",
    "  print(\"x est mis à 0\")    # nombre d'espace différent ne correspondant à aucun bloc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if x > 0:\n",
    "    x = 0\n",
    "\tprint('x est mis à 0')    # ici, l'indentation est faite avec un caractère de tabulation et non des espaces"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## La boucle `while` (tant que)\n",
    "\n",
    "On exécute les commandes dans le bloc de la boucle `while` tant qu'une condition est `True`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "# Série de Fibonacci :\n",
    "# la somme de deux éléments définie le prochain\n",
    "a, b = 0, 1\n",
    "while b < 1000:                     # tant que b est strictement plus petit que 1000\n",
    "    a, b = b, a + b                 # on met à jour a et b\n",
    "    print(round(b/a, 3), end=\", \")  # on affiche b/a avec 3 chiffres après la virgule. \n",
    "                                    # end=\", \" ajoute une virgule suivi d'un espace après le print. Par défaut, c'est une nouvelle ligne."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### **Exercice** : [Conjecture de Collatz](https://fr.wikipedia.org/wiki/Conjecture_de_Syracuse)\n",
    "\n",
    "Étant donné un entier $n$ strictement positif, on considère les opérations suivantes :\n",
    "- Si le nombre est pair, on le divise par 2.\n",
    "- Si le nombre est impair, on le multiplie par 3 et on ajoute 1.\n",
    "\n",
    "On recommence ensuite avec le nouvel entier obtenu.\n",
    "\n",
    "La conjecture dit que, quelque soit la valeur de l'entier de départ, cette suite atteindra toujours 1.\n",
    "- Tester la conjecture de Collatz pour $n = 100000$.\n",
    "- Combien d'itérations sont nécessaire pour arriver à 1 ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/control_flow_tools/collatz.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## La boucle `for`\n",
    "\n",
    "La boucle `for` parcourt un objet et exécute les commandes de son bloc pour chacun des éléments de cet objets.\n",
    "On utilise la boucle `for` sur une liste par exemple afin de parcourir ses éléments et d'effectuer les mêmes opérations sur chacun d'eux."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "for c in \"python\":   # On parcourt la chaîne de caractère \"python\".\n",
    "    print(c)         # ici, c est un caractère de \"python\" à chaque itération."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [],
   "source": [
    "print(\"Python L1 21 mai 2024\".split(\" \"))    # split(\" \") coupe la chaîne de caractère chaque fois qu'il y a un espace\n",
    "for word in \"Python L1 21 mai 2024\".split(\" \"):\n",
    "    print(word, f\", {len(word)} caractères\")   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in [1, 2, 3, 4]:  # Parcourt des éléments d'une liste\n",
    "    print(i*i)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### **Exercice** : les nombres premiers\n",
    "\n",
    "En utilisant des boucles `while`, `for` et des `if`, trouver tous les nombres premier plus petit qu'un entier $N$ ($N = 100$ par exemple)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/control_flow_tools/primes.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## La fonction `range`\n",
    "\n",
    "- Elle génère des nombres entiers régulièrement espacés.\n",
    "- L'object retourné par `range` n'est pas une liste. Utiliser `list(range(...))` pour obtenir la liste des nombres générés."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r = range(5)\n",
    "r "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "list(r) # Par défaut, range démarre à 0 et s'arrête juste avant le nombre spécifié avec un incrément de 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "list(range(2, 5))  # Ici, on démarre de 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "list(range(-1, -5, -1))   # On démarre à -1 jusqu'à -5 (non inclu) avec un incrément de -1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "for i in range(5):       # range est très utilisé dans les boucles `for` !\n",
    "    print(i, end=' ')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### **Exercice** : exponentielle\n",
    "\n",
    "- Écrire un code qui calcule la valeur de $e \\simeq 2.718281828459045$ en utilisant la formule de Taylor au point $0$.\n",
    "\n",
    "$$ e \\simeq \\sum_{n=0}^{50} \\frac{1}{n!} $$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/control_flow_tools/exponential.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    },
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Les instructions `break`, `continue` et `else`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`break` permet de sortir de la boucle en cours (`while` ou `for`)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "random_list = random.choices(range(100), k=1000)      # tire 1000 nombres aléatoirement entre 0 et 99\n",
    "for i, r in enumerate(random_list):                   # enumerate retourne l'indice de l'élément (i) et l'élément (r)\n",
    "    if r == 1:\n",
    "        print(f\"random_list[{i}] = {random_list[i]}\")\n",
    "        break                                         # permet de sortir de la boucle `for` si l'élément r vaut 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`continue` permet de passer directement à l'itération suivante d'une boucle (`while` ou `for`) sans exécuter le reste des commandes du bloc."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for n in range(10):\n",
    "    if n % 2 == 0:\n",
    "        print(n, \"est pair\")\n",
    "        continue               # on passe directement à l'itération suivante, sans exécuter le print qui suit\n",
    "    print(n, \"est impair\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "L'instruction `else` après une boucle `for` ou `while` est exécuté si on sort \"normalement\" de la boucle (donc pas avec un `break`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for n in range(2, 10):\n",
    "    for x in range(2, n):\n",
    "        if n % x == 0:                         # on teste si x divise n\n",
    "            print(n, 'égale', x, '*', n//x)\n",
    "            break                              # on arrête si c'est le cas, inutile de tester les autres diviseurs\n",
    "    else:\n",
    "        # on sort de la boucle sans avoir trouvé de diviseur de n, c'est donc un nombre premier\n",
    "        print(n, 'est un nombre premier')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Exercice** : nombres premier (bis)\n",
    "\n",
    "Dans la solution de l'exercice sur les nombres premier, beaucoup de tests inutiles sont réalisés. Essayer de réduire leur nombre en utilisant l'instruction `break`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/control_flow_tools/primes2.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Compréhension de liste\n",
    "\n",
    "Les compréhension de liste permette de créer simplement et rapidement des listes dont les éléments suivent une expression mathématique."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "# Un exemple : étant donné une liste, créer une nouvelle liste dont les éléments sont le double de la liste donnée.\n",
    "lsingle = [1, 3, 9, 4]\n",
    "ldouble = []\n",
    "for k in lsingle:\n",
    "    ldouble.append(2*k)\n",
    "ldouble"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "# La même chose avec une compréhension de liste\n",
    "ldouble = [2*k for k in lsingle]  # 2*k pour k dans lsingle\n",
    "print(ldouble)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [],
   "source": [
    "[n*n for n in range(1,10)]  # n^2 pour n dans range(1, 10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "[n*n for n in range(1,10) if n % 2 == 1]  # n^2 pour n entier dans [1, 10[ et n impair"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "[n+1 if n % 2 == 1 else n//2 for n in range(1, 10)]  # n+1 si n est impair, n/2 sinon, pour n entier dans [1, 10["
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Les Functions\n",
    "\n",
    "## Définir une fonction : l'instruction `def`\n",
    "\n",
    "- Le corps de la fonction doit être **indenté**\n",
    "- Les fonctions peuvent retourner des variables avec l'instruction `return`.\n",
    "- Sans instruction `return`, les fonctions retourne la variable `None`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "def is_palindromic(s):\n",
    "    \"\"\"Retourne `True` si l'objet s passé en argument est un palindrome\"\"\"\n",
    "    return s == s[::-1]   # s == s dont les éléments ont été inversé\n",
    "\n",
    "print(is_palindromic(\"kayak\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On peut remarque qu'on ne donne jamais le type de `s`. On peut passer n'importe quelle variable à la fonction `is_palindromic`. Si le type supporte toutes les opérations de la fonction, pas de problème. Sinon, python donnera une erreur une fois arrivé à l'instruction qui pose problème."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "is_palindromic([1, 2, 3, 2, 1])  # pas de problème avec les listes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "is_palindromic(1)  # On ne peut pas indexé un nombre"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "def fib(n):\n",
    "    \"\"\"Affiche la suite de Fibonacci jusqu'à n\"\"\"\n",
    "    a, b = 0, 1\n",
    "    while a < n:\n",
    "         print(a, end=' ')\n",
    "         a, b = b, a+b\n",
    "    print(\"\\n\")\n",
    "     \n",
    "result = fib(2000)\n",
    "print(result)       # est None, car on n'a pas mis de mot-clé `return`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comment sont passé les arguments à une fonction ?\n",
    "\n",
    "Est-ce qu'on a une copie des arguments ? Une référence ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# La fonction `id` permet de retourner un identifiant unique d'une variable.\n",
    "# Si pour deux variables différentes on a le même identifiant, c'est que ce sont des références l'une de l'autre.\n",
    "\n",
    "def ref(x):\n",
    "    print(\"Dans ref, x =\", x, \", id =\", id(x))    # ici, x est une référence\n",
    "    x = 42                                        # on change sa valeur\n",
    "    print(\"Dans ref, x =\", x, \", id =\", id(x))    # x n'est plus une référence, on a recréé x avec le signe =\n",
    "\n",
    "x = 5\n",
    "print(\"Avant ref, x =\", x, \", id =\", id(x))\n",
    "ref(x)\n",
    "print(\"Après ref, x =\", x, \", id =\", id(x))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def func(l):\n",
    "    print(\"Dans func, l = \", l, \", id = \", id(l)) # l est une référence à u\n",
    "    l = [42, -3]                                  # on recréé l avec le signe =, c'est une nouvelle variable\n",
    "    print(\"Dans func, l = \", l, \", id = \", id(l)) \n",
    "\n",
    "u = [1, 2, 3, 4, 5]\n",
    "print(\"Avant func, u = \", u, \", id = \", id(u))\n",
    "func(u)\n",
    "print(\"Après func, u = \", u, \", id = \", id(u))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def func(l):\n",
    "    print(\"Dans func, l = \", l, \", id = \", id(l)) # l est une référence à u\n",
    "    l += [42, -3]                                 # on modifie l avec +=, on ne recréé pas l (pas de signe =).\n",
    "    print(\"Dans func, l = \", l, \", id = \", id(l)) # l est toujours la même référence\n",
    "\n",
    "u = [1, 2, 3, 4, 5]\n",
    "print(\"Avant func, u = \", u, \", id = \", id(u))\n",
    "func(u)\n",
    "print(\"Après func, u = \", u, \", id = \", id(u))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def func(l):\n",
    "    print(\"Dans func, l = \", l, \", id = \", id(l)) # l est une référence à u\n",
    "    l += (42, -3)                                 # on modifie l avec +=\n",
    "    print(\"Dans func, l = \", l, \", id = \", id(l)) # l est recréé dans la fonction car les tuples sont immutables !\n",
    "\n",
    "u = (1, 2, 3, 4, 5)  # avec un tuple\n",
    "print(\"Avant func, u = \", u, \", id = \", id(u))\n",
    "func(u)\n",
    "print(\"Après func, u = \", u, \", id = \", id(u))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Retour sur `+=` pour les listes\n",
    "u = [1, 2, 3]\n",
    "print(\"u = \", u, \", id = \", id(u))\n",
    "\n",
    "u.extend([6, 7])\n",
    "print(\"u = \", u, \", id = \", id(u))\n",
    "\n",
    "u += [4, 5] # équivalent à u.extend([4, 5])\n",
    "print(\"u = \", u, \", id = \", id(u))\n",
    "\n",
    "u = u + [6, 7] # !!! Réassignation avec le signe =, ce n'est plus la même liste !!!\n",
    "print(\"u = \", u, \", id = \", id(u))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pour les fonctions acceptant **plusieurs arguments**, il est possible de les donner dans l'ordre que l'on veut en spécifiant leur nom avant leur valeur."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def dummy(arg1, arg2, arg3):\n",
    "    print(arg1, arg2, arg3)\n",
    "    \n",
    "dummy(1, 2, 3)\n",
    "dummy(1, arg3=3, arg2=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Arguments avec valeur par défaut"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "def f(a, b=5):\n",
    "    return a + b\n",
    "\n",
    "print(f(1))\n",
    "print(f(1, 2))\n",
    "print(f(b = \"a\", a = \"bc\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "**Attention** : la valeur par défaut est évaluée une fois seulement et non à chaque appel de la fonction !"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "def f(a, L=[]):\n",
    "    L.append(a)\n",
    "    return L\n",
    "\n",
    "print(f(1))      # première appel, L est évalué à la liste vide [], valeur par défaut.\n",
    "print(f(2))      # second appel, L n'est pas réévaluée. L = [1] depuis l'appel précédent.\n",
    "print(f(100))    # troisième appel, L = [1, 2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "mylist = [1, 10, 100]\n",
    "f(-1, mylist)\n",
    "print(mylist)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "print(f(3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Retourner plusieurs variables\n",
    "\n",
    "Il est possible de retourner plusieurs variables en les mettant dans un tuple !"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def dummy(a):\n",
    "    return a+1, a+2    # on retourne le tuple avec les élément a+1 et a+2\n",
    "\n",
    "res = dummy(1)\n",
    "print(type(res))\n",
    "print(res[0], res[1])\n",
    "\n",
    "x, y = dummy(1)   # L'avantage de retourner un tuple est qu'on peut faire du tuple unpacking \n",
    "print(x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Exercice**: dichotomie\n",
    "\n",
    "La méthode de dichotomie est utilisée pour trouver des zéros de fonctions (des valeurs qui annulent la fonction). Elle basée sur le théorème des valeurs intermédiaires.\n",
    "Étant donné un intervalle $[a, b]$ de $\\mathbb{R}$, une fonction réelle continue $f : [a, b] \\longrightarrow \\mathbb{R}$, telle que $f(a)$ et $f(b)$ aient des signes opposés, et $\\epsilon > 0$, l'algorithme est le suivant :\n",
    "\n",
    "- Soit $a_0 = a$, $b_0 = b$ et $m$ le milieu de $[a_0, b_0]$.\n",
    "- Tant que la taille de $[a_i, b_i]$ supérieure à $\\epsilon$, faire :\n",
    "\n",
    "    - si $f(m)$ et $f(a)$ ont le même signe, poser $a_{i+1} = m$ et $b_{i+1} = b_i$. Sinon, poser $a_{i+1}= a_i$ et $b_{i+1} = m$.\n",
    "    - redéfinir $m$ comme le milieu de $[a_{i+1}, b_{i+1}]$.\n",
    "    \n",
    "- Retourner $m$, qui est une approximation d'un zéro de $f$ dans $[a, b]$.\n",
    "\n",
    "1. Définir une fonction `f(x)` qui retourne le sinus de `x`.\n",
    "2. Définir une fonction `dichotomie(f, a, b, eps)` qui implémente la méthode de dichotomie où `f` est une fonction à un seul argument, `a` et `b` les deux valeurs réelles $a$ et $b$ de l'énoncé, et `eps` la tolérance $\\epsilon$ de la méthode.\n",
    "3. Soit $a = -\\pi / 2$, $b = \\pi / 3$, $\\epsilon = 10^{-12}$ et $f$ la fonction sinus. Utiliser la méthode de dichotmie avec ces arguments pour trouver un zéro de $f$ sur $[a, b]$. Vérifier que le résultat est bon.\n",
    "4. Modifier la fonction `dichotomie` de manière à compter le nombre d'itération nécessaire et le retourner en plus du zéro."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Une fonction sinus ainsi que la valeur de pi se trouvent dans le module `math` (math.sin et math.pi)\n",
    "import math"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/control_flow_tools/dichotomie.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### **Exercice** : méthode de Newton\n",
    "\n",
    "C'est une autre méthode permettant de trouver des zéros de fonctions. Cette méthode utilise la fonction et sa dérivée pour construire une suite $x_n$ qui converge vers un zéro de la fonction.\n",
    "\n",
    "L'algorithme a besoin d'un point de départ $x_0$. Ensuite, étant donné un point $x_n$, on construit $x_{n+1}$ comme l'intersection entre la tangente à la la fonction $f$ en $x_n$ et l'axe des abscisse $y = 0$. L'algorithme s'arrête lorsque $x_{n+1}$ reste suffisamment proche de $x_n$.\n",
    "\n",
    "1. Écrire une fonction `f(x)` qui retourne le sinus de `x`.\n",
    "2. Écrire une fonction `df(x)` la dérivée de `f` au point `x`.\n",
    "3. Écrire une fonction `newton(f, df, x0, eps)` qui implémente la méthode de Newton à la fonction `f` avec la dérivée `df` et qui démarre au point `x0`. L'argument `eps` est la tolérance du critère d'arrêt. Les points $x_{n+1}$ et $x_n$ sont suffisamment proches si la distance entre les deux est plus petite que `eps`.\n",
    "4. Utiliser votre fonction `newton` avec la function `f`, le point de départ $-\\pi / 3$, et avec `eps` égal à $10^{-12}$.\n",
    "5. Vérifier le résultat.\n",
    "6. Modifier la fonction `newton` pour ajouter un argument `nmax` ayant pour valeur par défaut $100$. Cet argument permet de stopper la méthode de Newton si elle fait plus de `nmax` itérations. Modifier la fonction `newton` pour qu'elle retourne aussi le nombre d'itération nécessaire pour converger."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/control_flow_tools/newton.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Fonctions récursives\n",
    "\n",
    "C'est une fonction qui s'appelle elle même."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "def gcd(x, y): \n",
    "    \"\"\"Retourne le plus grand diviseur commun.\"\"\"\n",
    "    if x == 0: \n",
    "        return y                # On connaît le résultat si x = 0. On le retourne ce qui permet d'arrêter les appels de gcd.\n",
    "    else : \n",
    "        return gcd(y % x, x)    # La fonction gcd s'appelle elle même avec des argument différent. On retourne son résultat.\n",
    "\n",
    "gcd(12, 16)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### **Exercice** : calcul de la factorielle\n",
    "\n",
    "Écrire une fonction `factorial(n)` qui calcul la factorielle d'un entier $n$ de manière récursive."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load solutions/control_flow_tools/factorial.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Durée de vie des variables définies dans une fonction\n",
    "\n",
    "- Toutes les assignations de variable (symbole =) dans une fonction créer une variable locale à la fonction. Elle cessera d'exister une fois la fonction terminée.\n",
    "- Une variable globale (définie en dehors de la fonction) peut être utilisée dans une fonction tant qu'on ne lui assigne pas directement une valeur.\n",
    "- Pour assigner directement une valeur à une variable globale dans une fonction, il faut la déclarer comme étant globale dans la fonction."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "pi = 1.                        # pi est une variable globale\n",
    "def deg2rad(theta):\n",
    "    pi = 3.14                  # pi est redéfinie dans la fonction, c'est une variable locale. Le pi globale n'est plus accessible ici.\n",
    "    return theta * pi / 180.\n",
    "\n",
    "print(deg2rad(45))\n",
    "print(pi)                      # le pi globale n'a pas été modifié"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [],
   "source": [
    "def rad2deg(theta):\n",
    "    print(pi)                  # Affichage d'une variable pi qui n'est pas définie dans la fonction.\n",
    "                               # Il faut qu'elle soit définie au niveau globale, sinon il y aura une erreur.\n",
    "    return theta * 180 / pi\n",
    "\n",
    "print(rad2deg(0.785))\n",
    "pi = 3.14\n",
    "print(rad2deg(0.785))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "def deg2rad(theta):\n",
    "    global pi                 # On dit à Python d'utiliser la variable gloable pi\n",
    "    pi = 3.14                 # C'est donc bien elle qui est modifiée ici\n",
    "    return theta * pi / 180\n",
    "\n",
    "pi = 1\n",
    "print(deg2rad(45))\n",
    "print(pi)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Fonction retournant une fonction\n",
    "\n",
    "Une fonction en Python est une variable comme une autre. Il est possible de créer une fonction dans une fonction et de la retourner.\n",
    "\n",
    "Cela permet par exemple de retourner une fonction spécialisée avec moins de paramètre."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "def create_wave(freq=1, phase=0):\n",
    "    def wave(x):                                      # création d'une fonction wave dans create_wave\n",
    "        return math.sin(math.pi * (x*freq + phase))   # noter les indentations, cette ligne fait partie de la fonction wave\n",
    "    return wave                                       # on retourne la fonction wave\n",
    "\n",
    "w1 = create_wave()          # w1 est la fonction wave(x) construite avec freq=1 et phase=0\n",
    "w2 = create_wave(5, 0.2)    # w2 est la fonction wave(x) construite avec freq=5 et phase=0.2\n",
    "\n",
    "x_h = [0.1*i for i in range(11)]\n",
    "y_w1 = [w1(x) for x in x_h]\n",
    "y_w2 = [w2(x) for x in x_h]\n",
    "\n",
    "print(y_w1)\n",
    "print(y_w2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Instruction `lambda`\n",
    "\n",
    "l'instruction `lambda` permet de construire une fonction anonyme (elle n'a pas de nom). Cela peut être utile lorsqu'il faut passer une fonction simple en argument d'une autre fonction par exemple.\n",
    "\n",
    "La syntaxe est `lambda tuple_argument : expression`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "f = lambda x : 2 * x + 2   # on créer une fonction sans nom qui prend un argument x et retourne 2*x+2.\n",
    "f(3)                       # en écrivant f =, on lui donne un nom (f ici)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "taxicab_distance = lambda x_a, y_a, x_b, y_b: abs(x_b - x_a) + abs(y_b - y_a)  # plusieurs arguments\n",
    "print(taxicab_distance(3, 4, 7, 2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "def make_incrementor(n):\n",
    "    return lambda x: x + n   # la fonction lambda peut utiliser le n, pas de problème. Il est fixé au moment de la création.\n",
    "                             # on n'a pas nommé cette fonction lambda, on n'a pas besoin de connaître son nom puisqu'on la retourne.\n",
    "\n",
    "f = make_incrementor(42)\n",
    "f(0), f(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "pairs = [(2, 'two'), (1, 'one'), (3, 'three'), (4, 'four')]    # liste de tuple\n",
    "\n",
    "# La fonction `sort` tri par ordre lexicographique par défaut\n",
    "pairs.sort()\n",
    "print(pairs)\n",
    "\n",
    "# On peut spécifier l'argument \"key\" de la fonction sort.\n",
    "# Cet argument est une fonction qui retourne l'élément à comparer pour le tri\n",
    "pairs.sort(key=lambda pair: pair[1])\n",
    "print(pairs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {
    "height": "657px",
    "width": "488px"
   },
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "415px"
   },
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
