# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# # Remise à niveau Python
#
# Toutes les séances de notre module seront présentées dans des documents au format iPython notebook, associé à l'extension `.ipynb`.  
# Ce format permet de joindre dans le même document:
#
# - du texte dans un format à balises simple, le format [markdown](http://markdowntutorial.com/),
# - du code à exécuter en [Python](https://docs.python.org/3.7/) (ici la version 3.7),
# - le résultat des commandes Python à la suite des cellules, que ce soit un résultat textuel ou une image.
#
# ### À noter
# Les cellules en jaune (sur le modèle suivant) sont des exercices à faire!
# <div class="alert alert-warning" style="margin-top: 1em"><b>Exercice</b>: Faire les exercices</div>

# ## Quelques éléments de syntaxe Python
#
# Python est un langage typé dynamiquement, on n’est donc pas obligé de déclarer le type des variables.  
# On pourra par exemple exécuter le programme suivant en plaçant le curseur dans la cellule et en tapant `Maj+Enter`.

a = 3
b = 4.0
c = "a * b : "
print(c + str(a * b))

# On préférera cette notation
print("a * b : {}".format(a * b))

# Les opérateurs habituels sur les entiers et les flottants sont disponibles.  
# La boucle `for` permet de parcourir des objets itérables comme des listes, des tuples etc.  
# La fonction `range` permet de générer une séquence d’entiers :

for x in range(2, 7):  # le 7 est exclu
    print(x)

# <div class="alert alert-danger">
# <b>Important :</b> vous remarquerez ici que l’indentation est importante en Python. Elle permet de définir les blocs.  
# </div>
# La conditionnelle est définie classiquement :

if a == 3:
    print("a = 3")
else:
    print("a = something else...")


# Le mot clé pour définir une fonction est `def`.
# La documentation d'une fonction peut-être écrite au format `__doctest__` en utilisant les triples guillemets.
# Les exemples d'utilisation indiqués dans la documentation `__doctest__` servent de test unitaire.

def fact(n):
    """Renvoie la factorielle de n.
    
    >>> fact(6)
    720
    >>> [fact(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    
    Si n est négatif, une exception de type ValueError est levée.
    >>> fact(-1)
    Traceback (most recent call last):
        ...
    ValueError: n doit être un entier positif
    """
    res = 1
    if n < 0:
        raise ValueError("n doit être un entier positif")
    while n > 0:
        res = n * res
        n = n - 1
    return res



fact(6)

# Il est important de bien remplir la documentation, on pourrait en avoir besoin par la suite:

help(fact)

# La fonction `doctest.testmod()` permet de tester toutes les fonctions d'un module donné (ici `__main__`):

import doctest
doctest.testmod()

# Le format iPython notebook permet également de consulter la documentation dans une pop-up à part:

# ?fact

# **Note :**
#
# Si vous rencontrez des difficultés de syntaxe Python, vous pouvez consulter:
#
# - la documentation officielle https://docs.python.org/3.7/,
# - ce [site](https://www.google.fr) bien pratique, ou mieux, [celui-ci](https://duckduckgo.com/).

# ## Structures de données
#
# Python propose un certain nombres de structures de données de base munies de facilités syntaxiques et algorithmiques.
#
# <div class="alert alert-success" style="margin-top: 1em">
# <b>Règle no.1</b> Tout l'art de la programmation Python consiste à choisir (et adapter) les bonnes structures de données.
# </div>
#
# ### Chaînes de caractères
#
# Les chaînes de caractères sont écrites à l’aide de guillemets simples, doubles ou retriplées (multi-lignes).

"hel" + 'lo'

# +
a = """Hello
- dear students;
- dear all"""

print (a)
# -

a[0]

a[2:4]

a[-1]

a[-8:]

a = "heLLo"
(a + ' ') * 2

len(a)

a.capitalize()

" hello ".strip()

"hello y’all".split() 

# ### Listes

a = [1, "deux", 3.0]

a[0]

len(a)

a.append(1)

a

a.count(1)

3 in a

a[1] = 2

a

a.sort()

a

a[1:3]

# On peut utiliser le mécanisme de *compréhension de liste* pour construire une liste facilement.  
# Par exemple, pour
# construire une liste contenant les carrés des valeurs comprises entre 1 et 5 :

[i for i in range(5)]
# similar to list(i for i in range(5))
# similar to list(range(5))

[str(i) for i in range(5)]

[i**2 for i in range(5)]

[i**2 for i in range(5) if i&1 == 0] # smarter than i%2 == 0 

[x.upper() for x in "hello"] # even with strings

# Rappelons également le constructeur `sorted` qui crée une liste triée à partir d'une structure itérable ou d'un générateur:

sorted(i**2 for i in range(-5, 5))

# ### Ensembles

a = set()
a.add(1)
a.add(2)
a.add(3)
a.add(1)
a

a.intersection({2, 3, 4}) # set theory 

a.isdisjoint({4, 5})

[i == 2 for i in a] # list comprehension 

set([1, 2, 4, 1]) # conversion 

# ### Dictionnaires

d = dict()
d[1] = ('Ain', "Bourg-en-Bresse")
d[2] = ('Aisne', "Laon")
d

d[1]

d['1']

d

d.keys()

d.values()

d.items()

e = {}
for key, value in d.items():
    e[("%02d" % key)] = value
e # No order in dictionary in Python 3.5 (but ok in 3.6)

{("%02d" % key):  value for key, value in d.items()}

# <div class="alert alert-warning">
# <b>Exercice</b>: Construire <b>et documenter</b> une fonction qui calcule la liste des nombres premiers inférieurs ou égaux à n.
# </div>

# write your code here!


# # %load solutions/prime.py


", ".join(str(i) for i in prime(100))

#
# <div class="alert alert-warning">
# <b>Exercice:</b> Écrire un programme qui lit le fichier <code>00-lorem-ipsum.txt</code> et y compte le nombre d’occurrences de chaque mot.
# </div>
#
# On pourra partir du modèle suivant :
# ```python
# with open("data/00-lorem-ipsum.txt") as f:
#     print(f.readlines())
# ```

# write your code here!


# # %load solutions/lorem_ipsum.py


# # %load solutions/lorem_ipsum_alt.py


# # %load solutions/lorem_ipsum_alt2.py


#
# <div class="alert alert-success" style="margin-top: 1em">
# <b>Rappel</b> Tout l'art de la programmation Python consiste à choisir les bonnes structures de données. (voir par exemple la <a href=https://docs.python.org/fr/3.7/library/collections.html>page sur les collections</a>)
# </div>
#
#
#
# ## La bibliothèque `numpy` de Python
#
# `numpy` est une extension du langage Python qui permet de manipuler des tableaux multi-dimensionnels et/ou des matrices.  
# Elle est souvent utilisée conjointement à l'extension `scipy` qui contient des outils relatifs:
#
# - aux intégrateurs numériques (`scipy.integrate`);
# - à l'algèbre linéaire (`scipy.linalg`);
# - etc.
#
# Des fonctionnalités simples illustrant le fonctionnement de `numpy` sont présentées ci-dessous.  
# En cas de souci, n'hésitez pas à vous référer à:
#
# - la documentation de `numpy` [ici](https://docs.scipy.org/doc/numpy/reference/);
# - la documentation de `scipy` [ici](https://docs.scipy.org/doc/scipy/reference/) pour ce que vous ne trouvez pas dans `numpy`.

import numpy as np

# ### Types des données embarquées
#
# On peut créer un tableau `numpy` à partir d'une structure itérable (tableau, tuple, liste) Python.  
# La puissance de `numpy` vient du fait que tous les éléments du tableau sont forcés au même type (le moins disant).

# +
# Définition d'un tableau à partir d'une liste
tableau = [2, 7.3, 4]
print('>>> Liste Python: type %s' % type(tableau))
for l in tableau:
    print('{%s, %s}' % (l, type(l)), end=" ")
print()
print()

# Création d'un tableau numpy
tableau = np.array(tableau)
print('>>> Tableau numpy: type %s' % type(tableau))
for l in tableau:
    print('{%s, %s}' % (l, type(l)), end=" ")
print()

print('On retrouve alors le type de chaque élément dans dtype: %s' % tableau.dtype)
# -

# ### Performance
#
# On reproche souvent à Python d'être lent à l'exécution. C'est dû à de nombreux paramètres, notamment la flexibilité du langage, les nombreuses vérifications faites à notre insu (Python ne présume de rien sur vos données), et surtout au **typage dynamique**.  
# Avec `numpy`, on connaît désormais une fois pour toute le type de chaque élément du tableau; de plus les opérations mathématiques sur ces tableaux sont alors codées en C (rapide!)
#
# Observez plutôt:

tableau = [i for i in range(1, 10000000)]
array = np.array(tableau)

# %timeit double = [x * 2 for x in tableau]

# %timeit double = array * 2

# ### Vues sur des sous-ensembles du tableau
#
# Il est possible avec `numpy` de travailler sur des vues d'un tableau à $n$ dimensions qu'on aura construit.  
# On emploie ici le mot **vue** parce qu'une modification des données dans la vue modifie les données dans le tableau d'origine.
#
# Observons plutôt:

tableau = np.array([[i+2*j for i in range(5)] for j in range(4)])
print(tableau)

# On affiche les lignes d'indices 0 à 1 (< 2), colonnes d'indices 2 à 3 (< 4)
sub = tableau[0:2, 2:4]
print(sub)

# L'absence d'indice signifie "début" ou "fin"
sub = tableau[:3, 2:]
print(sub)

# On modifie sub
sub *= 0
print(sub)

# <div class="alert alert-danger">
# <b>Attention</b>: voici pourquoi on parlait de vue !
# </div>

print(tableau)

# ### Opérations matricielles
#
# `numpy` donne accès aux opérations matricielles de base.

# +
a = np.array([[4,6,7,6]])
b = np.array([[i+j for i in range(5)] for j in range(4)])

print(a.shape, a, sep="\n")
print()
print(b.shape, b, sep="\n")
# -

# Produit matriciel (ou vectoriel)
print(np.dot (a, b))

# <div class="alert alert-danger">
# <b>Attention</b>: Contrairement à Matlab, les opérateurs arithmétiques +, -, * sont des opérations terme à terme.
# </div>
#
# Pour bien comprendre la différence:

# +
import numpy.linalg

a = np.array([[abs(i-j) for i in range(5)] for j in range(5)])
inv_a = numpy.linalg.inv(a) # L'inverse
print(a)
print(inv_a)
# -

print(inv_a * a)
print("\nDiantre!!")

print(np.dot(inv_a, a))
print("\nC'est si facile de se faire avoir...")

# <div class="alert alert-success">
# <b>Note</b>: Depuis Python 3.5, l'opérateur @ est utilisable pour la multiplication de matrice.
# </div>

print(inv_a @ a)

# # La bibliothèque `matplotlib` de Python
#
# `matplotlib` propose un ensemble de commandes qui permettent d'afficher des données de manière graphique, d'afficher des lignes, de remplir des zones avec des couleurs, d'ajouter du texte, etc.
#
# L'instruction `%matplotlib inline` avant l'import permet de rediriger la sortie graphique vers le notebook.

# %matplotlib inline
import matplotlib.pyplot as plt

# L'instruction `plot` prend une série de points en abscisses et une série de points en ordonnées:

plt.plot([1, 2, 3, 4], [1, 4, 9, 16])

# Il y a un style par défaut qui est choisi de manière automatique, mais il est possible de sélectionner:
#     
# - les couleurs;
# - le style des points de données;
# - la longueur des axes;
# - etc.

plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'ro-')
plt.xlim(0, 6)
plt.ylim(0, 20)
plt.xlabel("Temps")
plt.ylabel("Argent")

# Il est recommandé d'utiliser `matplotlib` avec des tableaux `numpy`.

# +
# échantillon à 200ms d'intervalle
t = np.arange(0., 5., 0.2)

# red dashes, blue squares and green triangles
plt.plot(t, t, 'r--', t, t**2, 'bs', t, t**3, 'g^')
# -

# Enfin il est possible d'afficher plusieurs graphes côte à côte.  
# Notez que l'on peut également gérer la taille de la figure (bitmap) produite.

# +
fig, ax = plt.subplots(ncols=2, nrows=2, figsize=(10, 10))

# Vous pouvez choisir des palettes de couleurs « jolies » avec des sites comme celui-ci :
# http://paletton.com/#uid=7000u0kllllaFw0g0qFqFg0w0aF

ax[0,0].plot(t, np.sin(t), '#aa3939')
ax[0,1].plot(t, np.cos(t), '#aa6c39')
ax[1,0].plot(t, np.tan(t), '#226666')
ax[1,1].plot(t, np.sqrt(t), '#2d882d')
# -

# Un bon réflexe semble être de commencer tous les plots par:
#
# ```python
# fig, ax = plt.subplots()
# ```
#

# <div class="alert alert-warning">
# <b>Exercice:</b> Tracer le graphe de la fonction $t \mapsto e^{-t} \cdot \cos(2\,\pi\,t)$ pour $t\in[0,5]$
# </div>



# <div class="alert alert-warning">
# <b>Exercice:</b> À partir des coordonnées polaires, produire les coordonnées $(x,y)$ pour la fonction $r=\sin(5\,\theta)$, puis les tracer.
# </div>
#
# <b>Consigne</b> : n'utiliser que des tableaux et des fonctions `numpy` pour produire les données à tracer.



StarcoderdataJupyterScriptsDedupFiltered