#!/usr/bin/env python
# coding: utf-8
# # Python : Révision des outils de base grâce à Jupyter notebook
#
#
#
# Ce notebook est proposé dans le cadre d'une préparation au ** Cours de mathématiques & informatique ** en B.C.P.S.T.2 - Externat des enfants nantais proposé par [J. Laurentin](). On pourra, si on le souhaite, trouver des compléments notamment sur le programme officiel en visitant le site de la classe [mathsinfo-bcpst2-eden.fr](http://mathsinfo-bcpst2-eden.fr/)
# ## 1. Prise en main
# Le Notebook d'IPython est un environnement intéractif pour écrire et faire tourner du code Python.
# Des cellules de code, précédées d'un `In []` permettent de rentrer des lignes de codes comme vous avez pu le faire jusqu'à présent dans la fenêtre de commande de Pyzo.
# A titre d'exemple, placez votre curseur sur la ligne ci-dessous et appuyez sur la touche ``. Vérifiez que l'instruction n'a pas été exécutée mais en revanche un saut de ligne a été inséré...
# Pour **exécuter le code** Python d'une cellule, il vous faut, au choix, taper `` ou cliquer sur le bouton [lecture] de la barre des tâches ci-dessus (ou encore utiliser le menu `Cell>Run and Select Below...`).
# Il suffit d'essayer :
# In[1]:
a,b = 1,5
print('a*b vaut',a*b)
# Il est toujours possible d'effacer une cellule grâce aux ciseaux du bandeau situé en haut de page et en créer une nouvelle grâce au symbole '+'.
# Mais c'est le plus souvent inutile car il est possible de relancer une ligne autant de fois qu'on le souhaite après l'avoir modifiée.
# Par exemple, modifiez la ligne de code `In [1]` pour initialiser la variable `b` à 5 et afficher la valeur de `a+b`.
# vous devriez voir afficher 'a+b vaut 6'.
# D'un point de vue pratique, on notera que toutes les fonctionnalités d'Ipython sont ici opérationnelles.
# On peut par exemple retrouver le chemin du dossier dans lequel on travaille :
# In[2]:
pwd
# On peut aussi importer les bibliothèques de son choix.
# Par exemple :
# In[3]:
from math import *
import numpy as np
# On peut désormais faire :
# In[4]:
x = 2
y = exp(x)-1
# In[5]:
print(x,'|',y)
# In[6]:
X = np.linspace(0,1,10)
# In[7]:
print(X)
# Il est aussi possible d'écrire des fonctions plus complexes.
# Par exemple pour la suite de Fibonacci définie par $u_0=1=u_1$ et $u_{n+2}=u_{n+1}+u_n$, $\forall n\geq 0$
# on calculera et on affichera ses dix premiers termes en écrivant :
# In[8]:
a,b=1,1 # initialisation de deux premiers termes
print(a,b,end = " ")
n=1
while n<11 :
a,b = b,a+b
print(b,end = " ")
n = n+1
# ** Attention ** Vous noterez que tous les termes de la suite ont été affichés sur une seule ligne.
# Ce résultat est obtenu grâce à l'argument `end = " "` fourni à la fonction `print()`.
# Supprimez cet argument et vérifiez que les termes sont affichés les uns en dessous des autres.
# ## 2. Quelques éléments sur Python, langage *Orienté Objet*
# Avec Python, tout est objet : données, fonctions, modules...
# > Un **objet** :
# - possède une *identité* (à savoir une adresse mémoire);
# - possède un *type* : un objet est l’instanciation d’une classe qui définit son type
# (par exemple : int, float, str);
# - contient des *données* (par exemple, si c'est un objet numérique, cette donnée est sa *valeur*).
#
# > Un objet est référencé par un **identificateur** :
# - on trouvera également "référence", "nom", "étiquette", comme synonymes d'indentificateur;
# In[9]:
a = 5 # a est l'identificateur et 5 est une expression évaluée comme objet.
# In[10]:
# il est possible d'afficher l'identité et le type de l'objet référencé :
print(id(a)) # identité
print(type(a)) # type
# In[11]:
b = a # deuxième nom (étiquette ou référence) pour l'entier 5
print(id(b))
print(type(b))
# In[12]:
c = 5.0 # nouvel identificateur qui pointe cette fois vers 5.0
# In[13]:
type(c)
# In[14]:
print(id(a),id(b),id(c))
# In[15]:
a == b # égalité des valeurs des deux objets
# In[16]:
a == c # égalité des valeurs
# In[17]:
a is c # test l'égalité des identités
# In[18]:
a is b # test l'égalité des identités
# On retiendra que a, b et c ont même valeur mais que c se distingue de a et b car son identité et son type son distincts.
# ** Attention ! ** Tous les objets ne sont pas modifiables !
# > Un objet dont le contenu peut être changé est dit ** mutable ** (non mutable dans le cas contraire).
# A titre d'exemple une chaîne de caractère est un objet non mutable tandis qu'une liste est un objet mutable.
# In[19]:
T = 'Feci est un texte non modifiable'
print(T)
# In[20]:
T[0]='C'
# In[21]:
L = [12,-5.0,'oups'];L
# In[22]:
L[2]=3;L
# In[23]:
print(L)
# ** Remarque importante ** sur les copies d'objets qui peuvent donner lieu à des erreurs difficiles à déceler :
# In[24]:
M = L
print(M)
# In[25]:
M[1]=60 # modification de la liste M
print(M)
# La question est de savoir si la liste `L` aussi a été modifiée...
# In[26]:
L
# In[27]:
L is M # L et M ont la même identité. Ils pointent vers le même objet et il est normal que L soit modifiée également.
# Il existe deux solutions pour faire la copie d'un objet : La copie * superficielle * ou la copie * profonde *.
# Nous verrons ça à la fin du paragraphe 3.2 * Modification d'une liste *
# ## 3. Manipulation des listes :
# Les **listes** représentent un type de données composées extrêment utiles sous Python.
# On peut les voir comme une *collection d'éléments séparés par des virgules, l'ensemble étant enfermé dans des crochets*.
# Les éléments peuvent être de type très différents. Par exemple :
# In[28]:
L1 = [1,2,3.5,'7',-2.4,'test']
# In[29]:
print(L1)
# ### 3.1 Accès aux éléments d'une liste :
# Il est possible d'accéder à n'importe quel élément d'une liste en indiquant entre crochets l'index numérique qui correspond à la position de l'élément.
# Essayez successivement dans la ligne ci-dessous : `L1[2]`, `L1[0]`, `L1[5]`, `L1[-1]`, `L1[-1][1]`
# In[30]:
# Obtenir un élément de la liste :
L1[2]
# On obtient la **longueur** d'une liste ou d'une chaîne en faisant appel à la fonction `len()` :
# In[31]:
n1 = len(L1)
print('la longueur de L1 vaut : ',n1)
print('la longueur de la chaîne de caractère "test" vaut : ',len(L1[-1]))
# Il est aussi possible de savoir si un élément est dans une liste :
# In[32]:
3 in L1
# In[33]:
u = 3.5
if u in L1:
print(u,' est dans L1')
# #### On notera qu'une liste peut donc est formée de plusieurs types d'éléments.
# 1. Les **type integer** (par exemple `L1[0]` et `L1[1]` sont des entiers).
# 2. Les **type float** pour *floating point number* (`L1[2]` est un "nombre réel" ou "nombre à virgule flottante).
# 3. Les **type string** (`L1[3]` et `L1[5]` sont des chaînes de caractères).
#
# On obiendra le type d'un élement en faisant appel à la fonction `type()`.
# Par exemple on essaira ci-dessous `type(L1)`, `type(L1[0])`, `type(L1[2])` ou encore `type(L1[3])` :
# In[34]:
type(L1)
# *Remarque :* Dans le cas de `L1[3]` on vérifiera qu'il s'agit bien d'un caractère et non d'un entier !
#
# Cela étant, il est possible d'accéder à bien mieux qu'à un seul élément d'une liste.
# Il est en effet possible d'extraire n'importe quelle sous-liste et ce dans l'ordre de votre choix.
# Commençons par extraire les trois premiers éléments de `L1` :
# In[35]:
print(L1[0:3])
print(L1[:3])
# In[36]:
print('L1 vaut : ',L1)
print(L1[1:])
print(L1[1:-1])
print(L1[1:4:2]) # index de 1 à 4 (exclus) par pas de 2.
print(L1[4:1:-1]) # index de 4 à 1 (exclus) par pas de -1.
print(L1[::-1]) # Affichage inversé de tout L1.
# A vous de jouer en créant à la suite de ce texte une nouvelle ligne de code (cliquer sur le symbole '+' dans le bandeau en haut de page) au sein de laquelle vous initialisez une liste `L2` de votre choix et en s'entraînant à accéder à n'importe laquelle des sous-listes possibles.
#
# ** Attention ** : Respecter l'affectation de ce nom (`L2`) à la liste que vous allez créer, sans quoi les indications du paragraphe 3.2 ne coincideront plus avec la liste `L1` sur laquelle nous continuons de travailler !
# Souvenez-vous qu'il est toujours possible de supprimer une ou des cellules qui ont été créées en utilisant 'Edit -> Delete Cells' ou bien la 'paire de ciseaux' des icônes ci-dessus.
# Il est aussi possible de réinitialiser l'ensemble des cellules, en allant dans l'onglet 'Kernel' et en exécutant 'Restart & Clear Output'.
# ### 3.2. Modification d'une liste :
# Nous rappelons que les listes sont des objets * mutables *.
# Si on connaît l'index d'un élément, il est facile de le changer ou de le remplacer.
# Par exemple :
# In[37]:
print(L1)
L1[1] = L1[1]+12
print(L1)
# Vous avez dû obtenir en deuxième ligne : [1, 14, 3.5, '7', -2.4, 'test']
# Il est aussi possible de modifier le ** type ** des éléments de la liste grâce à des "built-in function", qu'on appellera aussi des ** *méthodes* ** ou *fonction primitives*. On trouvera parmis celles-ci `int()`, `float()`, `complex()`, `str()` ou encore `list()`, `set()`.
# Par exemple, pour remplacer le caractère `"7"` par l'entier "7" on fera :
# In[38]:
L1[3]=int(L1[3]) # on écrira float(L1[3]) pour le transformer en type float.
print(L1)
# In[39]:
type(L1[3])
# **Exemple d'application** de ces méthodes de changement de type (d'après *Programmation en Python pour les mathématiques*, par A. Casamayou-Boucau, P. Chauvin et G. Connan, (Dunod, 2012), p.38) :
# Calculer $u_n=\cfrac{C_n}{n(n+1)}$ où $C_n$ désigne le nombre de chiffres intervant dans l'écriture décimale de $n$.
# In[40]:
n = 53
print('u_',str(n),' vaut :',len(str(n))/(n*(n+1)))
# Plus généralement, il est particulièrement important de savoir manipuler une liste pour insérer, supprimer un élement, ou encore concaténer des listes.
# Utilisez la ligne de code ci-dessous pour tester des *méthodes* particulièrement efficaces dont voici quelques exemples :
# In[41]:
L1.append(14) # ajout de 6 en fin de liste
print(L1)
L1 = L1+[14] # autre méthode possible...
print(L1)
# In[42]:
L1.insert(3,54) # insertion de x=54 à la position i=3
print(L1)
# In[43]:
L1.index(54) # retourne l'index de la première occurence de x=54
# In[44]:
L1.count(14) # dénombre les x=14 présents dans L1 - essayer avec d'autres valeurs...
# In[45]:
del L1[6:] # suppression des termes indexés à partir de 7
print(L1)
# In[46]:
L1.sort() # tri croissant de L1
print(L1)
L1.sort(reverse = True)
print(L1) # tri décroissant
# **Attention** On rappelle que les listes sont des objets *mutables*. En particulier toute méthode appliquée à une liste ne créé pas un objet nouveau mais la modifie. Vos données d'origine sont perdues !
# Aussi, dans le cas du tri d'une liste, si on ne souhaite pas perdre la liste de départ, on préfèrera L1_triee = sorted(L1) qui fait une copie triée de L1 sans la modifier.
# Voyons maintenant les moyens de faire la ** copie ** d'une liste - la copie * superficielle * puis la copie * profonde * :
# In[47]:
L = [1,[2,3]]
M = list(L) # copie superficielle: M est un nouvel objet `list` initialisé par L
M[0]=10
print(L,M)
M[1][0]=20
print(L,M)
# Si tout se passe bien pour `M[0]`, le problème pour `M[1][0]` vient du fait que M[1] pointe vers une même liste que L[1]...
# Reprenons :
# In[48]:
L = [1,[2,3]]
from copy import deepcopy
M = deepcopy(L) # copie profonde : M est une copie complète de l'objet référencé par L
M[0]=10
print(L,M)
M[1][0]=20
print(L,M)
# ### 3.3 Création de listes particulières :
# Création d'une liste de nombres à l'aide de la fonction `range()` :
# Cette fonction génère par défaut une séquence de nombres entiers de valeurs croissantes à partir de 0.
# Cette séquence peut s'utiliser directement (par exemple pour une boucle `POUR`) ou bien être transformée en *liste* :
# In[49]:
list(range(10))
# In[50]:
for k in range(10):
print(k**2,end = " ")
# Mais bien d'autres possibilités s'offrent à vous :
# In[51]:
list(range(2,12))
# In[52]:
list(range(2,12,2))
# In[53]:
list(range(7,-5,-1))
# In[54]:
[0]*10
# In[55]:
[4,8,'bip']*3
# In[56]:
list(range(5))*2
# On peut aussi créer des listes "en compréhension" :
# In[57]:
L = [k**2 for k in range(11)]
print(L)
# In[58]:
L2 = [k**2 for k in range(11) if k%2 == 0]
print(L2)
L3 = [k**2 for k in range(11) if (k > 4) and (k%2 == 0)]
print(L3)
# Et même créer des listes de listes, utiles pour constituer des tableaux ou des matrices :
# In[ ]:
L4 = [[i**2+j for i in range(3)] for j in range(2)]
print(L4)
# In[ ]:
print('la première ligne vaut : ',L4[0])
print('la seconde ligne vaut : ',L4[1])
print('le deuxième terme de la première ligne vaut : ',L4[0][1])
# Si on possède deux listes de même longueur, il est possible de les associer pour former des couples (appelés aussi `tupples`) :
# In[ ]:
Couleur = ['carreau','coeur','pique','treffle']
Hauteur = ['Roi','Dame','Valet','As']
for (c,h) in zip(Couleur,Hauteur):
print(h,' de ',c)
# ou encore, si on souhaite faire le produit scalaire de deux vecteurs $u$ et $v$ :
# In[ ]:
u = [1,0,2]
v = [-1,1,3]
print(sum(x*y for x,y in zip(u,v)))