1ère Générale NSI

 

Term. Générale NSI

 

Terminale STI2D SIN

Bts Ccst

Technico-commercial 3.0

[[{"title":"Python - Programmation fonctionnelle","posi":0},{"text":"
On ne compte pas moins de 2500 langages de programmation créés depuis les années 50 jusqu’à aujourd’hui. 

Bien qu’il ne soit pas utile d'en donner une liste exhaustive, voici ci-dessous un petit sous-ensemble qui montre la variété des langages disponibles.

ABC Ada Algol AWK APL 
Basic 
C C++ C# CAML CLU
Cobol CPL CSS 
Dart Delphi 
Eiffel
Flow-Matic  Forth Fortran 
Go Hack Haskell
HTML ICon J Java JavaScript Julia
Kotlin Lisp 
Mainsail M ML Modula
Oberon Objective-C  OCaml 
Pascal Perl PHP
PL/I Postscript Prolog Python 
R Ruby
Rust 
Scade Scala Scheme Sh Simula
Smalltalk SQL Swift Tcl/TK  
TypeScript VBA

Si certains de ces langages ne sont que peu ou plus utilisés aujourd’hui, il en reste tout de même encore un très grand nombre qui sont le «langage préféré» de tout une communauté de développeurs.


Devant une telle profusion, on peut se demander pourquoi il existe autant de langages en informatique.

Quelles sont les différences entre ces langages?

Quels sont ceux qu’il est important de connaître?

Pourquoi utiliser un langage plutôt qu'un autre? 

Etc. 

Il est aussi important de remarquer que certains langages dans la liste ci-dessus n’existaient pas il y à dix ans et de même, il est fort probable que de nouveaux langages apparaîtront dans les dix prochaines années.


"},{"text":""}],[{"text":"
Pourquoi ne pas avoir un unique langage de programmation? 

La raison est qu’un langage est souvent conçu pour répondre à des besoins ou des problématiques spécifiques. 

Par exemple, pour écrire un driver de carte graphique, il est parfois nécessaire de programmer en assembleur
afin d’être au plus proche du fonctionnement de la machine. 

En revanche, un langage comme SQL est plus adapté pour réaliser des opérations avec une base de données. 

Il existe aussi des langages dits généralistes, c'est-à-dire conçus pour permettre de programmer le plus d’applications possibles. Python est un de ces langages. 

Néanmoins, même les langages appartenant à cette catégorie évoluent sans cesse. Année après année, version après version, ils proposent de nouvelles instructions ou de nouveaux concepts pour simplifier le développement de logiciels. 

Au final, cette abondance de langages est liée au fait que les critères pour les concevoir sont nombreux et quelquefois contradictoires. 

Voici une liste non exhaustive de tels critères :

 - expressivité, pour simplifier l'écriture des programmes grâce à des constructions de haut niveau ;

 - lisibilité, pour faciliter la lecture et le raisonnement sur les programmes ;

 - efficacité, pour écrire des programmes rapides en permettant une génération de code optimisé ;

 - portabilité, pour que les programmes soient facilement exécutables sur plusieurs systèmes d'exploitation ou architectures ;

 - sûreté, pour aider à l'écriture de programmes corrects, sans bugs ;

 - maintenabilité, pour faciliter la maintenance des programmes et des logiciels.


","title":" "},{"edit":"

"}],[{"text":"

","title":" "},{"edit":"
Bien qu’il en existe un très grand nombre, les langages peuvent néanmoins être rassemblés par familles, on dit aussi paradigmes de programmation, selon les concepts qu’ils mettent en œuvre.

Là encore, la liste de ces familles de langages est longue et nous donnons ci-dessous les principaux paradigmes.

ImpératifOrienté objetsFonctionnel
ConcurrentÉvénementielOrienté requêtes
Orienté contraintesSynchroneLogique

Par exemple, le paradigme impératif est celui des langages qui permettent de manipuler des structures de données modifiables (comme les variables, les tableaux, etc.) en utilisant notamment des boucles (while, for, etc.). 

Les idées sous-jacentes à ces langages sont donc bien connues des utilisateurs de Python. 

Les concepts du paradigme orienté objets, qui sont liés aux notions de classes, de méthodes et d’héritage, ont été présentés à travers le langage Python.

Il est important de noter qu’un langage de programmation peut appartenir à plusieurs familles. C’est le cas de Python qui, en plus des deux paradigmes précédents, propose par exemple des constructions pour la programmation concurrente, mais également des concepts du paradigme fonctionnel que nous allons présenter dans la suite de cette séquence. 

Mais ce mélange des genres n’est pas propre à Python. On
trouve en effet de plus en plus de langages généralistes (comme C++ ou Java) qui s’appuient sur plusieurs paradigmes de programmation.

Comme les langages de programmation utilisés aujourd’hui ne sont pas ceux d’hier ni peut-être ceux de demain, il nous semble plus important de maftriser les concepts des paradigmes de programmation, plutôt que de chercher à apprendre un très grand nombre de langages. Cependant, il est parfois nécessaire de découvrir de nouveaux langages pour apprendre à programmer
dans un certain paradigme. 

Par exemple, nous avons présenté les concepts de la programmation événementielle à travers le langage JavaScript. 

De même, le paradigme de la programmation orientée requêtes est présenté avec le langage SQL que nous verrons plus tard dans l'année.

Dans la suite de cette séquence, nous allons présenter le paradigme fonctionnel. Si, comme son nom l'indique, ce style de programmation met en avant la notion de fonction, il est aussi intimement lié à la manipulation de structures de données non modifiables.


"}],[{"text":"
Supposons que l’on écrive en Python une fonction pour trier les éléments d’un tableau. On peut par exemple écrire un tri par insertion sous la forme d’une fonction tri_par_insertion(t) qui reçoit un tableau t en argument et trie, en place, ses éléments par ordre croissant. 

Dans le code de cette fonction, donné dans le programme 15, on compare les éléments du tableau t à l’aide de l’opération > de Python. Cette opération possède un certain degré de généricité, dans le sens où elle permet de trier aussi bien un tableau d’entiers, de flottants, ou encore de chaînes de caractères. 
En effet, la comparaison fournie par une opération de Python comme > s'applique à plusieurs types de données. Mieux encore, elle s'applique en profondeur
à. des tvnes structurés nomme des n-uplets.

Programme 15 — Tri par insertion

def insere(t, i, v):
\"\"\"insère v dans t[0...i[ supposé trié\"\"\"
j=i
while j > 0 and t[j - 1] > v:
t[j] = t[j - 1]
j=j-1
t[j] = v

def tri_par_insertion(t):
\"\"\"trie Le tableau t dans l’ordre croissant\"\"\"
for i in range(1, len(t)):
insere(t, i, t[i])

#test de la fonction
t1 = [(9,3),(4,7),(2,10),(6,5),(2,7),(3,0),(4,2)]
print(t1)
tri_par_insertion(t1)
print(t1)


Ainsi si on trie un tableau de couples d’entiers, ils seront triés selon la première composante et, en cas
d'égalité, selon la seconde composante.

Mais supposons maintenant qu’on ne souhaite pas trier selon la relation d'ordre définie par l'opération >. 
On pourrait par exemple vouloir trier en ordre décroissant, ou selon une relation encore différente. Ainsi, on peut imaginer qu’on dispose d’un tableau contenant des élèves avec leur date de naissance, sous la forme de quadruplés,

eleves = [(\"Brian\", 1, 1, 1942),
               (\"Grace\", 9, 12, 1906),
               (\"Linus\", 28, 12, 1969),
               ....

et qu’on veuille trier ces élèves par année de naissance croissante. Avec notre fonction tri_par_insertion du programme 15, cela ne fonctionnera pas.
En effet, les prénoms vont être comparés en premier puis, à prénom égal, les jours vont être comparés, et ainsi de suite. Ce n’est pas ce que l’on souhaite.
Il nous faut donc écrire une autre fonction de tri.

Bien évidemment, il est facile de faire une copie de notre fonction tri_par_insertion puis de remplacer dans son code tout occurrence de la comparaison de deux éléments par la comparaison qui nous intéresse ici.
C'est même relativement simple, car dans le code du tri par insertion, la comparaison n'est faite qu’à un seul endroit. Mais c’est tout de même déplaisant. On comprend qu’on est parti pour faire une copie de la fonction de tri chaque fois que la relation d’ordre change, ce qui n’est clairement pas satisfaisant. Quand on programme, le copier-coller est en effet à proscrire, car toute erreur dans le code initial sera alors dupliquée et, si elle est corrigée un jour, elle ne le sera probablement que dans une copie.


","title":"Fonctions passées en arguments"},{"edit":""}],[{"text":"
Une bien meilleure solution consiste à passer la fonction de comparaison des éléments en argument de la fonction de tri. 
Ceci est fait dans le programme 16.

Programme 16 — Tri par insertion générique
def insere(inf,t, i, v):
\"\"\"insère v dans t[0...i[ supposé trié\"\"\"
j=i
while j > 0 and not inf(t[j - 1] , v):
t[j] = t[j - 1]
j=j-1
t[j] = v

def tri_par_insertion(inf,t):
\"\"\"trie Le tableau t dans l’ordre croissant
pour la relation inf\"\"\"
for i in range(1, len(t)):
insere(inf,t, i, t[i])


On à toujours le tableau t en argument mais on à maintenant un premier argument, appelé inf pour «inférieur ou égal», qui est une fonction. Lorsque le code du programme de tri veut comparer deux éléments x et y, il appelle inf(x, y). 
Le booléen renvoyé indique si x est inférieur ou égal à y. 
Dans le code, on a donc remplacé l'expression 
     t[j - 1] > v 
par l'expression 
     not inf(t[j - 1], v)

Le reste est inchangé.

Une fois que ce petit changement est fait, on à une fonction de tri qui nous permet de trier n'importe quel tableau pour n'importe quelle façon de comparer les éléments. 
Si par exemple on veut trier un tableau d’entier
par ordre croissant (comme auparavant), il suffit de passer à la fonction tri_par_insertion une fonction qui fait la même chose que <=. 
On le fait comme ceci :

def inf1(x, y):
return x <= y


#Test avev inf
t1 = [(9,3),(4,7),(2,10),(6,5),(2,7),(3,0),(4,2)]
print(t1)
tri_par_insertion(inf1,t1)
print(t1)


Mais surtout on peut maintenant trier notre tableau d’élèves par année de naissance, en passant en argument une fonction qui compare uniquement la
quatrième composante de chaque quadruplé.

def inf2(x, y):
return x[3] <= y[3]



Tester le code et vérifier que cela fonctionne bien avec le tableau élève ci-dess :

#Test avec inf pour les élèves
t1 = [('Blaise', 19, 6, 1623), ('Joseph Marie', 7, 7, 1752),
('Louis', 16, 5, 2003) ,('Grace', 9, 12, 1906)]
print(t1)
tri_par_insertion(inf2,t1)
print(t1)


Ainsi, on dispose maintenant d’un tri par insertion universel, que l’on peut utiliser à loisir pour trier n'importe quel tableau.


","title":" "},{"edit":""}],[{"text":"
On convient qu’il est un peu pénible d’écrire une fonction comme la fonction inf du premier cas, dont le code se réduit à l’utilisation de l'opération <=, à seule fin de pouvoir la passer en argument à la fonction tri_par_insertion. Pour y remédier, Python propose une notion de fonction anonyme, sous la forme d'une construction lambda. 

lambda x, y: x[3] <= y[3]

Ainsi, l'expression désigne une fonction, anonyme, qui prend deux arguments, appelés ici x et y, et qui renvoie le booléen x[3] <= y[3]. Outre l’absence de nom pour cette fonction, on note l'absence des parenthèses autour des paramètres x et y ainsi que l’absence du mot-clé return. 
Ainsi, on peut trier notre tableau d'élèves par année de naissance de la manière suivante :

#Test avec lambda
t1 = [('Blaise', 19, 6, 1623), ('Joseph Marie', 7, 7, 1752),
('Louis', 16, 5, 2003) ,('Grace', 9, 12, 1906)]
print(t1)
tri_par_insertion((lambda x, y: x[3] <= y[3]), t1)

print(t1)


Tester le code ci-dessous et conclure sur l'utilisation lambda.


","title":"Fonction anonyme"},{"edit":""}],[{"text":"
Supposons qu’un tableau Python contient n valeurs, x1,x2, .... , xn et que l’on souhaite calculer le résultat de

   x1@x2@....@xn

pour une certaine opération @. 

Ainsi, on peut imaginer que les valeurs sont des entiers et @ l'addition, ou que les valeurs sont des flottants et @ le maximum, ou encore que les valeurs sont des chaînes de caractères et @ la concaténation, etc. 

Pour écrire un tel programme une seule fois, on peut
le faire sous la forme d’une fonction calcul qui reçoit en argument une fonction op qui représente l'opération @. 

Le programme 17 contient une telle fonction. Pour simplifier un peu les choses, on suppose ici que le tableau t contient au moins une valeur. 
Dès lors, on peut calculer facilement la somme, le produit ou encore le maximum des éléments d’un tableau d’entiers.

Programme 17 — Calcul sur un tableau

def calcul(op, t):
\"\"\"calcule t[0] op t[1] op ... op t[n-1]
le tableau t est supposé non vide\"\"\"
v = t[0]
for i in range(1, len(t)):
v = op(v, t[i])
return v

t = [1, 2, 3, 4, 5]
print(calcul((lambda x, y: x + y), t))
print (calcul((lambda x, y: x * y), t))


Avec des objets. passer une fonction en argument à
tri_par_insertion n’est pas la seule solution pour avoir un tri universel. Dans un contexte de programmation orientée objet, on peut en imaginer au moins deux autres.

 - Les éléments du tableau à trier sont des objets qui fournissent tous une méthode inf. 
Dans le code de la fonction de tri, au lieu d'écrire inf(x,y) pour comparer deux éléments x et y, on écrit
x.inf(y)

C’est en fait déjà ce qui se passe dans le programme 15, car l'opération > de Python invoque la méthode appelée __gt__.
Dans le cas du tri de nos élèves, cependant, la méthode __gt__ ne réalise pas la comparaison que vous voulons et cette solution ne convient donc pas.

 - Plutôt que de passer une fonction en argument de
tri_par_insertion, on passe un objet, appelons-le c, qui
fournit une méthode inf qui prend en arguments les deux éléments à comparer. Dans ce cas, au lieu d'écrire 
inf(x, y) dans le programme de tri, on écrit 
c.inf(x, y)
Un tel objet c est appelé un comparateur. Contrairement à la solution précédente, on peut
ainsi trier de mêmes éléments de plusieurs façons différentes.

Et notre fonction calcul n’est même pas limitée à des entiers, dès lors que la fonction op est cohérente avec le contenu de t. 
","title":"Un autre exemple"},{"edit":"

"}],[{"title":"Fonctions renvoyées comme résultats"},{"text":"
En programmation fonctionnelle, les fonctions ne se contentent pas de pouvoir recevoir d’autres fonctions en argument. Elle peuvent également renvoyer une fonction comme résultat. 

Prenons l'exemple de la dérivée d’une fonction. Dans certaines méthodes numériques, comme par exemple la méthode de Newton pour approcher le zéro d’une fonction, il est nécessaire de savoir calculer la dérivée d’une fonction f en un point x. 
Lorsque la dérivée f' de f ne peut être calculée symboliquement, on peut approcher la valeur de f'(x) avec un taux d’accroissement 

f(x+h) - f(x)
          h
pour une valeur de h très petite. 

Du coup, on peut écrire une fonction Python derive qui reçoit une fonction f en argument et renvoie une fonction qui approche la dérivée de f.

def derive(f):
h = 1e-7
return lambda x: (f(x + h) - f(x)) / h

#Tester la fonction derive
from math import sin, pi

d = derive(sin)
print(d(0.))
print(d(pi / 2))


Tester le programme et conclure.

On peut constater que cela fonctionne plutôt bien sur une fonction (ici, la fonction sinus) dont on connaît la dérivée (en l'occurrence la fonction  cosinus).


Comme on le voit, la fonction renvoyée par derive, ici stockée dans une variable d, est ensuite utilisée comme n'importe quelle autre fonction Python.

Avec des objets, il est possible de réaliser quelque chose de comparable en utilisant des objets. En effet, on peut représenter une fonction par un objet (d’une certaine classe, peu importe son nom) qui fournit une méthode, appelons-la app, pour appliquer cette fonction. Ainsi, on pourrait avoir un tel objet représentant la fonction sinus dans une variable appelée sinus et obtenir la valeur de sin(x) en faisant sinus.app(pi) . 
Dès lors, on peut imaginer que chaque objet qui représente une fonction fournit en outre une seconde méthode, derive, qui renvoie un autre objet représentant sa dérivée. Ainsi, sin.derive() est un objet qui représente la dérivée de la fonction sinus (et donc la fonction cosinus).



"},{"edit":"

"}],[{"title":" Structures de données immuables"},{"text":"
Dans cette section, nous illustrons un autre aspect de la programmation fonctionnelle, à savoir l’utilisation de structures de données immuables. Une structure immuable est une structure de données, a priori quelconque (tableau, ensemble, dictionnaire, etc.), que l’on ne peut plus modifier une fois qu’elle est construite. 

Bien entendu, on peut néanmoins l'utiliser, pour en consulter le contenu ou pour construire d’autres structures de données. Avec Python, on est plutôt habitué pour l’instant à une programmation impérative où les structures de données sont au contraire mutables, c’est-à-dire que leur contenu est modifié par des opérations. Ainsi, on affecte de nouvelles valeurs aux cases d’un tableau, on ajoute des éléments dans un ensemble, de nouvelles entrées dans un dictionnaire, etc. 

Dans cette section, nous allons montrer l'intérêt des structures immuables sur un exemple concret. 

Supposons que l’on programme une structure de données pour réaliser des ensembles. En particulier, on fournit une opération pour construire un ensemble vide, des opérations pour ajouter ou supprimer un élément, une opération pour tester si un élément appartient à un ensemble, etc. 
La façon dont ces ensembles sont réalisés ne nous intéresse pas ici. En revanche, la façon dont ils se présentent à l'utilisateur, selon le paradigme de programmation choisi, n’est pas sans conséquence et c’est ce que nous allons montrer.
 
Si par exemple on a opté pour le paradigme impératif, avec des opérations qui modifient l’ensemble passé en argument, alors on va se retrouver à écrire quelque chose comme ceci.

s = creer_ensemble()
ajouter(s, 34)
ajouter(s, 55)

Ici, l'opération ajouter modifie l’ensemble s qu’elle a reçu en argument, pour lui ajouter un nouvel élément. Dans un paradigme objet, on aura quelque chose de très semblable, à quelques détails syntaxiques près.

s = Ensemble()
s.ajouter(34)
s.ajouter(55)

Là encore, l’ensemble s est modifié par l'opération ajouter. Dans le paradigme fonctionnel, en revanche, l’opération ajouter ne modifie pas l’ensemble s mais renvoie plutôt un nouvel ensemble. 

Du coup, on écrit quelque chose d’un rien différent comme ceci :

creer_ensemble()
ajouter(s, 34)
ajouter(s, 55)

À chaque appel à ajouter, un nouvel ensemble est renvoyé, ici stocké à chaque fois dans la même variable s. On pourrait aussi utiliser des variables différentes à chaque fois.


"},{"edit":"

"}],[{"text":"
De même, si on considère des opérations plus complexes, pour calculer par exemple l'union, l’intersection ou la différence de deux ensembles, il faut
décider si ces opérations modifient un de leur arguments ou au contraire renvoient un troisième ensemble. Si on opte pour le premier choix, il faut même décider lequel des deux ensembles est modifié. 

Ainsi, on pourrait choisir que l’opération 

union(a, b)

modifie l’ensemble a pour lui ajouter tous les éléments de l’ensemble b.

Cette dissymétrie est un peu déplaisante mais on n’a pas trop le choix dès lors qu’on a opté pour une version impérative de l'opération union. Sans grande différence, on écrirait a.union(b) avec le paradigme objet. Enfin,
dans le paradigme fonctionnel, on écrit plutôt quelque chose comme ceci 

c = union(a, b)

où la variable c reçoit le résultat de l'union, sans modifier les ensembles a et b. Tout cela fait finalement peu de différence. 

En première approximation, seule la syntaxe varie légèrement. 

Mais supposons maintenant que nous voulons tester notre bibliothèque d'ensembles. Par exemple, on peut vouloir tester que l’identité mathématique

AUB=(A\\B)U(B\\A)U(A∩B)  (1)

est toujours valable, où U désigne l'union, ∩, désigne l'intersection et \\ désigne la différence ensembliste. 

Par exemple, on peut générer aléatoirement de nombreuses paires d’ensembles A et B, et choisissant soigneusement leur taille et le domaine de leurs éléments, et vérifier l’identité ci-dessus à chaque fois

Construire un ensemble aléatoirement n’est pas  difficile. On peut par exemple ajouter successivement dans un ensemble des entiers tirés aléatoirement avec la fonction randint. En revanche, tester l'identité ci-dessus n’est pas chose aisée si les opérations d'union, d’intersection et de différence modifient leurs arguments. En effet, si on commence par calculer AUB, avec quelque chose comme union(a, b) qui modifie l’ensemble a pour lui ajouter les éléments de b, alors on ne peut plus calculer a\\b, b\\a et ab car l’ensemble à été modifié. Il faut réaliser une opération copie pour copier un ensemble puis on doit écrire quelque chose  qui ressemble à ce qui suit.

u1 = copie(a)
union(u1, b)
u2 = copie(a)
difference(u2, b)
b_a = copie(b)
difference(b_a, a)
union(u2, b_a)
i = copie(a)
intersection(i, b)
union(u2, i)
assert egal(u1, u2)


La situation ne serait pas vraiment différente si les ensembles étaient des objets avec des méthodes qui en modifient le contenu. Mais dans un paradigme fonctionnel, en revanche, où les opérations ne modifient pas leurs arguments, alors on peut écrire quelque chose de beaucoup plus simple :


assert egal(union(a, b),
union(union(difference(a, b), difference(b, a)),
intersection(a, b)))


C’est là une traduction littérale de l’identité (1), beaucoup plus agréable.

D'une manière générale, la programmation fonctionnelle se caractérise par l’idée que, comme en mathématiques, quand on applique deux fois la
même fonction aux mêmes arguments, alors on obtient le même résultat.
On appelle cela la transparence référentielle. Cette propriété exclut de fait tout effet de bord que la fonction pourrait avoir, comme la mutation d’une donnée.
","title":" "},{"edit":"


"}],[{"text":"
Écrire une fonction trouve(p, t) qui reçoit en arguments une fonction p (pour « propriété ») et un tableau t et renvoie le premier élément x de t tel que p(x) vaut True. Si aucun élément de t ne satisfait p, alors la fonction renvoie None. 

Tester votre fonction trouve avec le code suivant :

t = [1,3,5,7]
print(trouve(lambda x : x==5 ,t))

t = [1,3,6,7]
print(trouve(lambda x : x==5 ,t))

","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"

def trouve(p, t):
\"\"\"le premier élément æ de t tel que p(x) est vrai\"\"\"
for x in t:
if p(x):
return x
return None

"}],[{"text":"
Écrire une fonction applique(f, t) qui reçoit en arguments une fonction f et un tableau t et renvoie un nouveau tableau, de même taille, où la fonction f a été appliquée à chaque élément de t.
Le faire avec et sans la notation par compréhension.

Tester la fonction applique avec le code suivant :

t = [1,3,5,7]
print(applique(lambda x : x**3 + 2 ,t))

","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"
#Sans la notation par compréhension, on peut le faire
#par exemple comme ceci :

def applique(f, t):
r= []
for x in t :
r.append(f(x))
return r

#Avec la notation par compréhension, c’est beaucoup plus simple :
#def applique(f, t):
# return [f(x) for x in t]

"}],[{"text":"
Écrire une variante du programme 17 qui calcule le résultat de 
    f(x1)@f(x2)@....@f(xn)
sur les éléments x1,x2,...,xn d’un tableau, pour une fonction f et une opération quelconques @.

Tester la fonction calcul2 avec le code suivant :

t = [1,3,5,7]
print(calcul2(lambda x : x**2 + 4 , lambda x ,y: x+y , t))

 
Le résultat obtenu est 100.

Appliquer cette nouvelle fonction calcul pour obtenir la représentation du contenu d’un tableau sous la forme d’une chaîne de caractères de la forme \"1, 2, 3, 4\", c’est-à-dire où les éléments sont séparés par des virgules. 

","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"
def calcul2(f, op, t):
v = f(t[0])
for i in range(1, len(t)):
v = op(v, f(t[i]))
return v


"},{"solution":"
Pour construire la chaîne demandée, il suffit de faire


print(calcu12((lambda x: str(x)), (lambda x, y: x+\", \"+y), t))

"}],[{"text":"
Écrire une fonction double(f) qui reçoit une fonction f en argument et renvoie une fonction qui applique deux fois de suite la fonction f à son argument. 

Que vaut double(double(lambda x: x*x)) (2) ? 
Trouver le résultat de tête, puis lancer le calcul pour le vérifier..
","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"
def double(f):
return lambda x: f(f(x))


La réponse est 2^16 = 65536. En effet, le résultat de
double(lambda x: x*x) est la fonction x ++ (x^2)^2 = x^4 et donc le résultat de double (double(...)) est la fonction x ++ (x^4)^4 = x^16.

"}],[{"text":"
Écrire un code Python correspondant au test de l'identité (1) en utilisant les opérations non destructives des ensembles de Python, à savoir
les opérations | pour l’union, - pour la différence et & pour l'intersection.

","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"
Le test s'écrit aussi simplement que ceci :


assert à | b == (a - b) | (b - a) | (a & b)

"}],[{"text":"
Écrire une fonction repeter_delai(f, n, t) qui effectue n appels et affiche f(0),..., ,f(n - 1) où chaque appel à f est suivi d’une pause de t secondes. 
On pourra utiliser la fonction time.sleep(t) pour mettre le
programme en pause pendant t secondes. 

Tester la fonction avec le code suivant :
repeter_delai(lambda x : x**2 + 5,11,1)


Résultats affichés toutes les 1 secondes :
5
6
9
14
21
30
41
54
69
86
105

","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"
from time import sleep

def repeter_delai(f, n, t):
for i in range(n):
print(f(i))
sleep(t)
"}],[{"text":"
Écrire une fonction temps_d_execution(f, x) qui prend en arguments une fonction f et une valeur x et qui renvoie une paire formée du résultat de f(x) et du temps mis par ce calcul. Pour mesurer ce temps, on pourra utiliser la fonction time.perf_counter() ou la fonction time.time() . Mesurer le temps pris par la fonction boucle_inutile sur l’argument 10000000.

def boucle_inutile(n):
k=0
for i in range(n):
k=k+1




","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"
from time import perf_counter

def temps_d_execution(f, n):
t1 = perf_counter()
r = f(n)
t2 = perf_counter()
return (r, t2 - t1)

def boucle_inutile(n):
k = 0
for i in range(n):
k=k+i

print (temps_d_execution(boucle_inutile, 10000000))

"}],[{"text":"
La fonction temps_d_execution de l'exercice 43 n'accepte en argument que des fonctions à un argument, ainsi que l'argument sur lequel appeler la fonction. 

Soit la fonction

def boucle_inutile2(n, m):
k=0
for i in range(n):
for j in range(m):
k=k+1


Indiquer comment utiliser la fonction temps_d_execution pour mesurer le temps de l’appel boucle_inutile2(1000, 10000), sans modifier aucune
des deux fonctions. 


","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"
On peut utiliser une fonction anonyme :

temps_d_execution(lambda m: boucle_inutile2(1000, m), 10000)

Ici le lambda contient une « application partielle » de la fonction boucle_inutile2 où le paramètre 1000 est déjà passé.

"}],[{"text":"
La paradigme de la programmation événementielle consiste à associer du code pour qu'il soit exécuté lorsque survient un évènement.

Le programme de première présente ce paradigme dans le cadre de la programmation d’interfaces graphiques. C'est une application naturelle de la programmation fonctionnelle. 

Le code associé aux événements est tout simplement une fonction stockée dans une structure
de données du système et appelée au moment opportun.

Compléter le code Python ci-dessous pour qu’à chaque clic de bouton, le nombre affiché sur celui-ci soit incrémenté.

import tkinter as tk

fenetre = tk.Tk()

b = tk.Button(fenetre, text=\"0\")
b.pack()

# ... insérer ici le code demandé ...

fenetre.title(\"Cliquer sur le bouton\")
fenetre.geometry(\"800x400\")
fenetre.mainloop()

","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"
On définit une fonction gerer_clic passé en argu-
ment à la méthode bind du bouton b.

compteur = 0

def gerer_clic(ev):
global compteur
compteur = compteur + 1
ev.widget.configure(text=str (compteur))

b.bind(\"<Button-1>\", gerer_clic)




"}],[{"text":"
Soit le code suivant, qui définit une étiquette et 10 boutons.

Compléter la fonction changer_chiffre pour que le chiffre passé en argument soit utilisé comme texte pour l'étiquette.

Attention : on prendra garde à la façon dont cette fonction est appelée dans la boucle for.

import tkinter as tk

fenetre = tk.Tk()
chiffre = tk.Label(fenetre)
chiffre.grid(row=1, column=1, columnspan=10)

def changer_chiffre(c):
\"\"\"... compléter ici ...\"\"\"
x=0

for i in range(10):
c = str(i)
b = tk.Button(fenetre, text=c)
b.grid(row=2, column=i+1)
b.bind(\"<Button-1>\", changer_chiffre(c))

fenetre.mainloop()



","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"
La méthode bind attend en second argument une
fonction. Il faut donc que change_chiffre(c) renvoie la fonction que le système va stocker et appeler plus tard.

def changer _chiffre(c):
return lambda e: chiffre.configure(text=c)


Attention, appeler directement chiffre.configure(text=c) dans le corps
"}],[{"text":"
Considérons une petite bibliothèque permettant de manipuler des points dans le plan.

Partie 1. Une première implémentation utilise des n-uplets, qui en Python sont des séquences immuables.

# fichier point_imm.py

def point(x, y):
\"\"\"construire un point\"\"\"
return(x, y)

def deplacer(p, dx, dy):
\"\"\"translation d’un point\"\"\"
return point(p[0]+dx, p[1]+dy)

def triangle(p1, p2, p3):
\"\"\"construction d’un triangle\"\"\"
return (p1, p2, p3)

# une constante utile origine = point(O, 0)


1. Écrire du code Python utilisant cette implémentation pour créer 4 points a, b, c et d de coordonnées respectives (0, 0) , (1, 2) , (-5, 18) , (42, 37).

2. Écrire une fonction deplacer _triangle(t, dx, dy) qui renvoie un nouveau triangle dont les trois points sont déplacés chacun de dx et dy.

Tester votre travail avec le code suivant :
t1 = (a,b,c)
print(t1)
print(deplacer_triangle(t1,2,3))

Résultat :
((0, 0), (1, 2), (-5, 18))
((2, 3), (3, 5), (-3, 21))

3. Écrire du code Python qui crée deux triangles t1 et t2 constitués respectivement des points a, b, c et b , c, d.

4. Créer un triangle t3 obtenu en déplaçant t1 de (-1,-1) et t4 obtenu en déplaçant t2 de (2,3).

Tester avec le cpde suivant :
print(t1)
print(t2)
print(t3)
print(t4)


Résultat attendu :
((0, 0), (1, 2), (-5, 18))
((1, 2), (-5, 18), (42, 37))
((-1, -1), (0, 1), (-6, 17))
((3, 5), (-3, 21), (44, 40))

Partie 2. On considère maintenant une implémentation utilisant des objets et des modifications en place des attributs.

class point:
def __init__(self, x, y):
self.x = x
self.y = y

def deplacer(self, dx, dy):
self.x += dx
self.v += dv
class triangle:
def __init__(self, p1, p2, p3) :
self.p1 = p1
self.p2 = p2
self.p3 = p3

def deplacer(self, dx, dy):
self.p1.deplacer(dx, dy)
self.p2.deplacer(dx, dy)
self.p3.deplacer(dx, dy)


1. Est-il pertinent de rajouter à cette implémentation une variable globale origine contenant le point (0,0)?

2. Comme précédemment, créer 4 points a, b, c et d.

3. Créer deux triangles t1 et t2 constitué de a, b, c et b, c, d.

4. Déplacer t1 de (-1,-1) et t2 de (2,3). Obtient-on des triangles équivalents aux triangles t3 et t4 de la Partie 1 ?

5. Proposer une modification de l'implémentation mutable qui corrige le problème.
","title":"Exercice"},{"edit":"

Mettre le résultat ici (code et figure).

"},{"solution":"
#Partie 1.

# Question 1.1

a = point(0, 0)
b = point(1, 2)
c = point(-5, 18)
d = point(42, 37)

"},{"solution":"
# Question 1.2

def deplacer_triangle(t, dx, dy):
p0 = deplacer(t[0], dx, dy)
p1 = deplacer(t[1], dx, dy)
p2 = deplacer(t[2], dx, dy)
return triangle(p0, p1, p2)

"},{"solution":"
# Question 1.3

t1 = triangle(a,b,c)
t2 = triangle(b,c,d)

"},{"solution":"
# Question 1.4
t3 = deplacer_triangle(t1, -1, -1)
t4 = deplacer_triangle(t2, 2, 3)
"},{"solution":"
Partie 2. A priori, si l’on crée un objet origine = point(0,0) mutable, alors on pourra modifier l’origine en faisant origine.deplacer(1,1), ce qui n’est pas souhaitable. À chaque fois que l’on voudra utiliser l'origine, il faudra recréer le point avec point (0,0). 
"},{"solution":"Question 2.2
a = point(0, 0)
b = point(1, 2)
c = point(-5, 18)
d = point(42, 37)

"},{"solution":"
# Question 2.3
t1 = triangle(a, b, c)
t2 = triangle(b, c, d)

"},{"solution":"
# Question 2.4
ti.deplacer(-1, -1)
t2.deplacer(2, 3)

"},{"solution":"

#Question 2.5
class point:
# constructeur et deplacer inchangés

def clone(self):
return point(self.x, self.y)

class triangle:
def __init__(self, pi, p2, p3):
self.pi = p1.clone()
self.p2 = p2.clone()
self.p3 = p3.clone()

# deplacer inchangé



Avec les modifications ci-dessus, la classe triangle stocke une copie des points qui lui sont passés en argument. Elle peut donc les modifier sans impacter les points initiaux. Il faudra cependant être vigilant, à chaque fois que l'on «donne» un tel point mutable à une fonction dont on ne connaît pas le code, car il existe un risque que ce point soit modifié. Il conviendra
donc de faire des copies au moment opportun. Le style fonctionnel utilisé en Partie 1 permet d'écrire une code plus lisible (il n’est pas encombré d'opérations de copies) et dans lequel les erreurs causées par le partage sont inexistantes.

"}]]

En poursuivant votre navigation sur mon site, vous acceptez l’utilisation des Cookies et autres traceurs  pour réaliser des statistiques de visites et enregistrer sur votre machine vos activités pédagogiques. En savoir plus.