[[{"title":"DB : Requêtes SQL et mises à jour","posi":0},{"text":"
"},{"text":""}],[{"text":"
"}],[{"text":"
","title":"Clause WHERE"},{"edit":"
"}],[{"text":"
","title":" Clause SELECT"},{"edit":"
"}],[{"text":"
","title":"Tri et suppression des doublons"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Jointure"},{"edit":"
"}],[{"text":"
","title":"Nouvelle » syntaxe SQL pour les jointures"},{"edit":"
"}],[{"text":"
","title":"Modification des données"},{"edit":"
"}],[{"text":"
","title":"Suppression de lignes"},{"edit":"
"}],[{"text":"
"}],[{"text":"
","title":"Copie de table"},{"edit":"
"}],[{"text":"
","title":"Requêtes imbriquées"},{"edit":"
"}],[{"text":"
","title":"Requêtes de groupe"},{"edit":"
"}],[{"text":"
"},{"solution":""}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":""},{"solution":"
"},{"solution":"
"},{"solution":"
"},{"solution":"
"},{"solution":"
"},{"solution":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"","title":"Exercice"},{"edit":"
"},{"solution":""}]]
Nous avons vu dans la séquence précédente comment créer des tables et les remplir.
Nous avons maintenant une base de données, c’est-à-dire un ensemble de tables, contenant des données cohérentes vis à vis de nos contraintes d’intégrité.
Nous allons maintenant voir deux autres utilisations d’un SGBD :
- la sélection de données ;
- la mise à jour des données.
La sélection va consister en l’écriture de requêtes SQL permettant de trouver toutes les données de la base vérifiant un certain critère. Le premier rôle du programmeur de bases de données va donc être celui qui consiste à traduire des questions que l’on se pose sur les données du langage naturel au langage
SQL, afin que le SGBD puisse y répondre.
Reprenons la bascs de données de la médiathèque municipale.
Quelles questions peut-on poser à la base de données? Il semble naturel que le SGBD puisse répondre aux questions suivantes, nécessaire au bon fonctionnement
de la médiathèque.
- Étant donné un code barre, quels sont les livres empruntés par l’utilisateur correspondant?
- Étant donné un ISBN. le livre correspondant est-il emprunté?
- Quels sont les utilisateurs en retard, c'est-à-dire ceux dont la date de retour est inférieure à une date donnée ?
- Quels sont tous les livres écrits par Voltaire qui ne sont pas empruntés?
- Quel est le nombre total de livres empruntés?
Une autre fonction importante du SGBD est la mise à jour des données.
Elle peut consister en une modification d’une ligne existante (par exemple, pour changer l'adresse d’un utilisateur ayant déménagé, sans modifier son
code barre, son nom où son e-mail) ou une suppression (par exemple lorsqu'un utilisateur rend un livre, il faut supprimer la ligne correspondante dans la table emprunt).
Pour les exemples plus parlants, nous utilisons dans la suite le SGBD libre PostgreSQL, avec une base de données de médiathèque fic-
tive. Les fichiers permettant de créer cette base sont disponibles librement
sur le site https://www.nsi-terminale.fr/. Ils utilisent la syntaxe SQL
standard et sont donc compatibles avec n'importe quel SGBD relationnel
respectant la norme SQL (et ont été testés avec un certain nombre d’entre
eux, libres et propriétaires).
Requête sur une table
Commençons par une requête simple. On considère la table livre, créée par l’ordre suivant :
CREATE TABLE livre (titre VARCHAR(300) NOT NULL, editeur VARCHAR(9O) NOT NULL,
annee INT NOT NULL,
isbn CHAR(14) PRIMARY KEY);
On souhaite trouver les titres de tous les livres publiés après 1990 dans la base de données de la médiathèque.
Une telle requête peut s’écrire en SQL :
SELECT titre FROM livre WHERE annee >= 1990;
Sélectionner la base de donnée dans phpmyadmin et exécuter la requête SQL. Mettre le résultat ci-dessous et conclure.
Dans cette requête, la partie « FROM livre » indique la table sur laquelle porte la requête.
La partie « WHERE ... » indique que l’on ne sélectionne
que les lignes de la table Livre pour lesquelles la valeur de l’attribut annee est plus grande que 1990.
Enfin, la partie SELECT titre indique qu'on ne veut renvoyer que les valeurs de l’attribut titre des lignes trouvées. On remarque que le résultat d'une requête «SELECT ... » est une table, ici possédant une unique colonne titre.
","title":"Sélection de données"},{"edit":"Mettre le résultat ici de la requête sql.
L'expression se trouvant dans la partie WHERE doit être une expression booléenne.
Elle peut être construite à partir d'opérateurs de comparaison (<, <=, >, >=, = et <>), d'opérateurs arithmétiques (+, -, *, /, %), de constantes, de noms d’attributs, d'opérateurs logiques (AND, OR et NOT) et d'opérateurs spéciaux tels que l'opérateur de comparaison de textes LIKE.
Par exemple, si l’on souhaite afficher les titres de tous les livres publiés par Dargaud entre 1970 et 1980, on pourra écrire :
SELECT titre FROM livre WHERE annee >= 1970
AND annee <= 1980
AND editeur = 'Dargaud';
Tester la requête et mettre le résultat ci-dessous.
Si l’utilisation de l'égalité « = » est appropriée ici, on pourrait vouloir faire une requête approchée.
Par exemple, trouver les titres des livres qui contiennent le mot « Astérix » dans le titre.
Une telle requête s’écrira
SELECT titre FROM livre WHERE titre LIKE '%Astérix%';
Tester la requête et mettre le résultat ci-dessous.
La chaîne de caractères « '%Astérix%' » est un motif. L'opération s LIKE m s’évalue à vrai si et seulement si la chaîne de caractères s correspond au motif m.
Dans un motif, le symbole « % » est un joker et peut être substitué par n'importe quelle chaîne.
Le symbole «_» quant à lui représente n'importe quel caractère.
Ainsi, le motif « '%Astérix%' » correspond à n'importe quelle chaîne dans laquelle les caractères Astérix sont précédés ou suivis de caractères quelconques.
Mettre le résultat ici (code et figure).
La clause SELECT peut prendre trois formes ;
La première est celle où l’on liste explicitement les
attributs que l’on désire renvoyer. Si on veut renvoyer les titres et l’'ISBN des livres publiés après 1990, on écrira ceci :
SELECT titre, isbn FROM livre WHERE annee >= 1990;
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Le résultat est de nouveau une table, mais cette fois avec les colonnes (ou attributs) titre et isbn.
Il est possible de renommer les colonnes au moyen du mot clé AS.
SELECT titre AS le_titre, isbn AS nom serie
FROM livre WHERE annee >= 1990;
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Ainsi, si la clause WHERE d’une requête permet de restreindre les lignes de la table que l’on renvoie (en ne gardant que celle vérifiant la condition), la clause SELECT permet de restreindre la liste des colonnes.
La seconde forme de la clause SELECT est celle que l’on utilise lorsqu'on veut conserver toutes les colonnes. En effet, il serait fastidieux de récrire toutes les colonnes d’une table. On peut utiliser à cette fin le symbole « * » qui signifie « toutes les colonnes de la table ».
SELECT * FROM livre WHERE annee >= 1990;
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
La troisième utilisation de la clause SELECT est celle permettant d'appeler des fonctions d’agrégation. Ces dernières permettent d'appliquer une fonction à l’ensemble des valeurs d’une colonne et de renvoyer le résultat comme une table ayant une seule case (une ligne et une colonne).
Nous présentons quelques unes de ces fonctions :
- COUNT qui permet d'obtenir le nombre de résultats,
- AVG (pour l’anglais average) qui permet de calculer la moyenne d'une colonne,
- SUM qui permet d’en faire la somme,
- MIN et MAX qui permettent de trouver respectivement le minimum et maximum d’une colonne.
Si on souhaite savoir combien de livres contiennent la chaîne « Astérix » dans leur titre (plutôt que de renvoyer ces titres), on écrira la requête suivante :
SELECT COUNT(titre) AS total FROM livre
WHERE titre LIKE ’%Astérix%';
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Notons que nous avons choisi de renommer la colonne. En effet, le résultat n'étant pas directement une colonne d’une table existante, les SGBD choisissent un nom arbitraire, souvent peu parlant.
La fonction « COUNT » ne faisant que compter la taille de la colonne, elle peut s’appliquer à n'importe quel nom de colonne et même au symbole « * ». Il est donc courant d'écrire une requête de la forme
SELECT COUNT(*) AS total FROM livre
WHERE titre LIKE '%Astérix%';
qui donnera le même résultat que précédemment.
Les fonctions AVG et SUM ne peuvent s'appliquer qu’à des colonnes dont le domaine est un nombre
On peut écrire par exemple
SELECT SUM(annee) AS somme FROM livre;
SELECT AVG(annee) AS moyenne FROM livre;
Tester les 2 requêtes dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Ici, en l’absence de clause WHERE, toutes les lignes sont sélectionnées. La somme et la moyenne des années sont calculées et renvoyées comme une
table.
Enfin, les fonctions MIN() et MAX() peuvent s'appliquer sur n'importe quelle colonne et ls comparaison pour son type sera utilisée pour déterminer le plus petit ou le plus grand élément.
SELECT MIN(annee) ÀS inf FROM livre;
SELECT MAX(annee}) AS sup FROM livre;
Tester les 2 requêtes dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Mettre le résultat ici.
Comme nous avons pu l'observer lors de nos premières requêtes, les résultats sont affichés par le SGBD dans un ordre a priori quelconque. La situation est même plus complexe. En effet, en fonction de certains paramètres, le SGBD peut choisir entre différentes
façons de calculer la requête.
L'ordre peut donc être modifié entre deux exécutions de la même requête. Si l’on désire obtenir les résultats dans un ordre particulier, on peut utiliser la clause ORDER BY en fin de requête.
SELECT titre FROM livre
WHERE annee >= 1990
ORDER BY titre ASC
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Ici, on demande au SGBD de trier les résultats par titre croissants (ASC pour l’anglais ascending).
Si on souhaite trier par valeurs décroissantes il suffit d'utiliser le mot clé DESC (pour l'anglais descending) à la place de ASC.
Supposons maintenant que l’on souhaite connaître toutes les années dans lesquelles un livre à été publié.
Nous envoyons la requête suivante :
SELECT annee FROM livre;
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Vous constatez que toutes les années, mais la même année peut apparaître plusieurs fois. Si on souhaite retirer les doublons d’un résultat, le mot clé DISTINCT
peut être ajouté à la clause SELECT.
SELECT DISTINCT annee FROM livre;
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Attention cependant, chaque ligne entière de résultat est considérée lors de la comparaison.
SELECT DISTINCT annee, isbn FROM livre;
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
C’est pourquoi, la requête ci-dessus continuera d'afficher plusieurs fois la même année. En effet, comme l’isbn est unique pour chaque ligne, tous les couples annee, isbn de la table sont différents deux à deux (ils diffèrent par leur isbn même s'ils ont la même
année). Le mot clé DISTINCT n'aura donc ici aucun effet.
Mettre le résultat ici (code et figure).
De façon très simple, on peut modéliser l'annuaire
de la manière suivante :
Annuaire(nom : String, prénom : String, tel :String)
On n’oubliera pas de préciser que le numéro, par définition unique, est une clé primaire. Son domaine peut être String afin d’éviter les problèmes de 0 en première position ou de permettre de saisir des caractères non numériques
comme +.
*****
Les requêtes que nous avons vues nous permettent assez facilement de déterminer les livres qui ont été empruntés. Ces derniers sont simplement ceux dont l'ISBN est présent dans la table emprunt.
SELECT * FROM emprunt;
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Cette réponse n’est cependant pas très satisfaisante. En effet, il serait plus naturel de pouvoir afficher les titres de ces livres plutôt que leur ISBN. Le problème est que les titres des livres sont présents uniquement dans la table livre.
L'opération de jointure de deux tables apporte une réponse à ce problème. Elle a déjà été étudiée en première dans le cadre du traitement de données en tables.
Étant données deux tables A et B, la jointure consiste à créer toutes combinaisons de lignes de A et de B
ayant un attribut de même valeur. Ici, on souhaiterait obtenir une « grande table » dont les colonnes sont celles de la table emprunt et celle de la table livre, en réunissant les lignes ayant ie même isbn.
Cela peut être fait au moyen de la directive JOIN.
SELECT * FRÜM emprunt
JOIN livre ON emprunt.isbn = livre.isbn;
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Cette requête crée la jointure des deux tables.
Comme on peut le voir, toutes les colonnes des deux tables ont été recopiées dans la sortie.
Chaque ligne est le résultat de la fusion de deux lignes ayant le même ISBN. Le choix de ces lignes est donné
par la condition de jointure indiquée par le mot clé ON.
La condition indique au SGBD dans quel cas deux lignes doivent être fusionnées.
Ici, on joint les lignes pour lesquelles les ISBN sont égaux.
On écri donc l'expression booléenne
« emprunt.isbn = livre.isbn ».
La notation portant le même nom.
La jointure peut être combinée avec les clauses SELECT et WHERE.
Par exemple, si on souhaite afficher uniquement les titres et les dates des livres empruntés qui sont à rendre avant le 1er février 2020, on peut écrire la requête suivante :
SELECT livre.titre, emprunt. retour
FROM emprunt
JOIN livre ON emprunt.isbn = livre.isbn
WHERE emprunt retour < '2020-02-01';
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
Même s’il n’y a pas d’ambiguïté ici, une bonne pratique consiste à préfixer les noms d’attributs par leur table dès que l’on utilise plus d’une table dans la requête. On n’est évidemment pas limité à une seule jointure.
Si on souhaite afficher les noms et prénoms des utilisateurs ayant emprunté ces livres, il suffit de joindre la table usager, en rajoutant une nouvelle clause JOIN ON, cette fois sur le code_barre de l'usager.
SELECT usager.nom, usager.prenom, livre.titre, emprunt retour
FROM emprunt
JOIN livre ON emprunt.isbn = livre.isbn
JOIN usager ON usager.code barre = emprunt.code_barre
WHERE emprunt.retour <'2020-02-01';
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
La requête ci-dessus fonctionne parfaitement mais est un peu fastidieuse à écrire. Il est possible de créer dans une requête un alias pour un nom de table au moyen du mot clé AS, comme pour le renommage de colonne.
La requête peut donc être réécrite de la manière suivante :
SELECT u.nom, u.prenom, l.titre, e.retour
FROM emprunt AS e
JOIN livre AS l ON e.isbn = l.isbn
JOIN usager AS u ON u.code_barre = e.code_barre
WHERE e.retour < '2020-02-01';
Tester la requête dans phpmyadmin et mettre le résultat ci-dessous et conclure.
La jointure est une opération fondamentale des bases de données relationnelles. En effet, comme nous l'avons vu, la modélisation relationnelle des données impose parfois un découpage des données.
Les re
******
contraintes de référence.
La jointure permet de reconstituer ce lien, en construisant « à la volée » de grandes tables contenant toutes les informations liées.
Mettre le résultat ici (code et figure).
Considérons la requête
SELECT livre.titre, emprunt.retour
FROM emprunt
JOIN livre ON emprunt.isbn = livre.isbn
WHERE emprunt.retour < '2020-02-01';
Puisque l’on peut mettre des conditions arbitraires dans la clause WHERE, une manière alternative d'écrire la requête est la suivante :
SELECT livre.titre, emprunt.retour
FROM emprunt, livre
WHERE emprunt.isbn = livre.isbn
AND emprunt.retour < '2020-02-01';
Pour n'importe quel SGBD, ces deux requêtes sont équivalentes, tant du point de vue du résultat que des performances.
La seconde version est « l’ancienne » syntaxe SQL, utilisée avant l'introduction du mot clé JOIN dans la version du standard de 1992. La bonne pratique consiste à privilégier l’utilisation du mot clé JOIN. En effet, il rend plus lisible les grandes requêtes en séparant clairement prédicats de jointure et filtres
sur les données. Il permet aussi de communiquer clairement l'intention d'effectuer une jointure. Enfin, lors d’une utilisation avancée, il permet de changer la méthode de jointure (QUTER JOIN, LEFT OUTER JOIN, RIGHT OUTER JOIN, etc.) tout en conservant la même syntaxe.
Mettre le résultat ici (code et figure).
Les données stockées dans un SGBD ne sont a priori pas figées et peuvent être modifiées au cours du temps. Nous allons montrer deux types de modifications pouvant être faites sur les tables :
- la suppression d’un ensemble de lignes et
- la mise à jour de certains attributs d’un ensemble de lignes.
L'ordre DELETE FROM t WHERE c permet de supprimer de la table t toutes les lignes vérifiant la condition c.
Dans l'exemple de notre médiathèque, supposons que l'utilisateur Sébastien Petit, dont le code_barre est '934701281931582', ait rendu ses livres. Il faut supprimer de la table emprunt toutes les lignes pour lesquelles le code barre vaut '934701281931582', ce qui donne l’ordre suivant :
DELETE FROM emprunt WHERE code_barre = '934701281931582' ;
Après exécution de cet ordre, la recherche dans la table emprunt ne donne plus de résultats :
SELECT COUNT(*) AS total
FROM emprunt
WHERE code _barre = r934701281631582r;
Attention, au même titre qu’une requête SELECT sans clause WHERE sélectionne toutes les lignes, un ordre DELETE sans clause WHERE efface toutes les lignes de la table. Il ne faut pas confondre « DELETE FROM » et « DROP TABLE + ». La première opération vide une table de son contenu, mais ne supprime pas la table.
Il est donc possible d’y ajouter de nouveau des données au moyen de l'instruction INSERT.
La seconde opération détruit la table (et ses données). La table ne peut donc plus être référencée.
Comme nous l'avons dit précédemment, les contraintes sont vérifiées à chaque mise à jour.
Essayons de supprimer le livre Hacker's delight de la
table livre, sachant que l’'ISBN de ce dernier est '978-0201914658'.
DELETE FROM livre WHERE isbn = '978-0201914658' ;
Ici, le SGBD nous indique que supprimer ce livre (et donc supprimer sa clé primaire de la table livre) violerait la contrainte de clé étrangère dans la table auteur_de.
Comme pour la destruction d’une table, il faut donc
supprimer en premier les lignes dont les attributs sont déclarés comme clés étrangères avant de supprimer celles contenant les clés primaires correspondantes.
Du point de vue de leur exécution, les ordres de modification de table sont soit entièrement exécutés, soit entièrement annulés.
Considérons la requête suivante :
DELETE FROM usager WHERE cp = '75001' OR cp = '75002' ;
qui efface de la table usager toutes les personnes dont le code postal est 75001 ou 75002. Si aucune de ces personnes n'apparaît dans la table emprunt, alors les suppressions peuvent être effectuées sans erreur.
Supposons maintenant que certaines de ces personnes ont emprunté un livre. Même si la SGDB rencontre en premier des personnes sans emprunt et les supprime, il lèvera une erreur dès qu’il rencontrera un usager référencé dans la table emprunt.
Dans ce cas, foutes les modifications déjà faites seront annulées et la table se trouvera dans l'état qu’elle avait avant la tentative d’exécution.
Les exécutions sont donc de type « tout où rien ».
Mettre le résultat ici (code et figure).
Le second type de modification est la mise à jour. Elle consiste à remplacer certains attributs d'un ensemble de lignes par de nouvelles valeurs.
La syntaxe est la suivante :
UPDATE t SET a1 = e1, ..., SET an = en WHERE c
Cette dernière signifie « sélectionne dans la table t toutes les lignes vérifiant la condition c et pour chacune de ces lignes, remplace la valeur courante de
l'attribut a par la valeur de l'expression e ».
Par exemple, si l'utilisateur Sébastien Petit souhaite mettre à jour son adresse email, on écrit ceci :
UPDATE utilisateur SET email = ’Cette adresse e-mail est protégée contre les robots spammeurs. Vous devez activer le JavaScript pour la visualiser. ’
WHERE code_barre = '934701281931582';
Les expressions de mise à jour peuvent mentionner des noms d’attributs.
Ces derniers sont alors remplacés par la valeur courante (avant mise à jour) de ces attributs.
Supposons par exemple que la médiathèque soit fermée au mois d'avril. On souhaite que tous les emprunts dont la date de rendu était en avril soient prolongés de 30 jours.
UPDATE emprunt SET retour = retour + 30
WHERE retour >= '2020-04-01' ;
Dans la mise à jour précédente, la clause « SET retour = retour + 30 » est similaire à la modification d’une variable dans un langage de programmation comme Python, c’est-à-dire prendre la valeur courante de retour, y ajouter 30 et écrire la nouvelle valeur dans retour.
","title":"Mise à jour"},{"edit":"Mettre le résultat ici (code et figure).
******
Toute modification (création de table, suppression de table, mise à jour, suppression de ligne, insertion de ligne) qui ne viole pas de contrainte est définitive.
En cas de suppression ou de mise à jour, les anciennes données sont perdues. Il faut donc être particulièrement vigilant lors de la conception d’un programme effectuant des mises à jour dans une base de données.
Les bonnes pratiques recommandent l'utilisation de plusieurs « copies » de la base de données.
Une copie de test, utilisée pour le développement et une copie de « production » utilisée pour faire fonctionner le logiciel, une fois que ce dernier à été testé rigoureusement. La base de production doit par ailleurs être sauvegardée fréquemment pour éviter les risques liés à de mauvaises manipulations ou à des défaillances logicielles ou matérielles.
Comme nous l'avons vu, l’ordre « SELECT ... FROM » renvoie une table comme résultat. Il est possible de nommer cette dernière grâce au mot-clé INTO :
SELECT * INTO usager_paris FROM usager WHERE cp LIKE '75%';
Cet ordre crée une nouvelle table nommée usager_paris contenant le résultat de la requête.
Cette dernière a le même schéma que la table usager
car toutes les colonnes ont été copiées. En revanche, elle ne contiendra que les lignes pour lesquelles le code postal commence par 75.
La table usager_paris est ensuite utilisable comme n'importe quelle autre table. La clause WHERE permet de choisir les lignes à conserver. Par exemple,
SELECT * INTO usager2 FROM usager;
crée une copie conforme de usager alors que
SELECT * INTO usager3 FROM usager WHERE 1 = 0;
crée une table vide ayant le même schéma que usager (car la condition « 1 = 0») est toujours fausse.
Attention cependant, l'opération SELECT ... INTO ne copie pas les contraintes. Ainsi, dans la table usager3 ci-dessus, la colonne code_barre, bien qu’elle existe, n'est pas déclarée en tant que clé primaire. L'opération
SELECT INTO servira donc plutôt à sauvegarder un résultat temporaire de requête plutôt que créer une véritable copie.
Créer une copie conforme d’une table peut se faire en utilisant deux variations sur des opérations que nous connaissons bien.
La première est l'opération CREATE, utilsée comme ceci :
CREATE TABLE usager3 (LIKE usager INCLUDING ALL);
Cette syntaxe indique de créer la table usager3 comme une table de même schéma que usager, en incluant les contraintes (clés primaires, étrangères, CHECK, NOT NULL, ...).
L’inconvénient est que la table usager3 est vide. On peut cependant la remplir en utilisant une variation de l'order INSERT :
INSERT INTO usager3 (SELECT * FROM usager);
Ici, on dit d’insérer dans usager3 toutes les lignes renvoyées par la requête mise entre parenthèses. Cette dernière peut être arbitraire, mais doit renvoyer des lignes du même schéma que celles attendues pour la table usager3
********
Mettre le résultat ici (code et figure).
L'opération « SELECT ... INTO » permet de sauver le résultat d’une requête sous un certain nom de table. Il est donc possible d'effectuer sur ce résultat une nouvelle requête.
Cependant, cette opération va occuper de l’espace de stockage. Il ne faudra donc pas oublier de supprimer la table ainsi créée. Il est possible de créer une table de manière temporaire et d'exécuter une requête sur cette table en imbriquant la première requête dans la clause « FROM » de la seconde ou dans une clause « JOIN ... ON »:
SELECT * FROM (SELECT * FROM livre
WHERE annee >= 1990) AS tmp
WHERE tmp.annee <= 2000;
La requête ci-dessus calcule d’abord une table intermédiaire nommée tmp qui liste les livres publiés après 1990. Suite à quoi, la table tmp est refiltrée pour
ne garder que les livres pour lesquels l’année est inférieure à 2000.
Attention, il ne s’agit ici que d’une explication de « haut niveau ». En pratique, n'importe quel SGBD moderne évaluera cette deux requêtes imbriquées comme la requête équivalente :
SELECT * FROM livre
WHERE annee >= 1990
AND annee <= 2000;
Une autre manière d’imbriquer les requêtes consiste à utiliser une sous-requête dans la clause WHERE. En effet, le langage SQL identifie les valeurs scalaires et les tables à une seule « case » telles que celles renvoyées par les fonctions d’agrégation.
Par exemple, si on souhaite afficher les titres des livres dont l’année est la plus ancienne dans la base, on pourra écrire :
SELECT titre FROM livre
WHERE annee = (SELECT MIN(annee) FROM livre);
Ici, la sous-requête calcule l’année minimum de la table livre (1933 dans notre base), puis affiche tous les titres de livres dont l’année vaut 1933.
Attention, la sous-requête ne doit pas nécessairement comporter une fonction d’agrégation. Il suffit qu'elle renvoie une table contenant une seule valeur
.
Ainsi, si nous voulons afficher les titres des livres publiés la même année que Moby Dick (sans connaître cette année), nous pouvons écrire :
SELECT titre FROM livre WHERE annee =
(SELECT annee FROM livre
WHERE titre = ’Moby Dick’);
Mais attention, si la sous-requête renvoie plusieurs résultats, le SGBD renverra une erreur :
# SELECT titre FROM livre WHERE annee =
(SELECT annee FROM livre WHERE titre LIKE ?{Astérix#’);
ERROR: more than one row returned by a subquery
used as an expression
Un opérateur utilisant la puissance des requêtes imbriquées est l'opérateur IN. L'expression e IN (g) renvoie vrai si et seulement si la valeur résultant de l'évaluation de e est l’une des lignes renvoyées par la requête g. Ainsi, pour exprimer la requête « afficher les titres des livres qui ont été publiés la même année qu'un livre dont le titre contient Astérix », on pourra écrire :
SELECT titre FROM livres
WHERE annee IN
(SELECT annee FROM livres
WHERE titre LIKE Astérix’)
Mettre le résultat ici (code et figure).
. Les requêtes de groupe sont explicitement hors
programme. Leur compréhension peut cependant aider lors de la création d'exercices, en particulier pour déterminer si la requête demandée est réalisable dans le fragment de SQL du programme de terminale.
Ces requêtes s'expriment au moyen de l'opérateur GROUP BY (éventuellement. accompagné de l'opérateur HAVING). Intuitivement, ces requêtes
permettent, de répondre à la question « donner le f de x pour chaque g distinct de la table t » où f est une fonction d’agrégation, x un attribut de la table et g un autre attribut appelé clé de groupe.
Cette requête s’écrira alors
SELECT g, f(x) FROM t GROUP BY g
Par exemple, si on souhaite connaître le nombre (f) de Livres (t) publiés pour chaque année (g) de la base, on écrira :
SELECT annee, COUNT(*) FROM livre GROUP BY annee;
Mettre le résultat ici (code et figure).
Avant de commencer les exercices, vous devez réaliser les étapes suivantes pour mettre à jour votre base sur :
http://217.182.207.90/phpmyadmin/
Etape 1 : Exporter votre base de données.
Etape 2 : Effacer toutes les tables.
Etape 3 : Importer la base mediathèque.
A partir de cette base donner et tester le code SQL de chacune des requêtes ci-dessous.
Les mots en police fixe donnent une indication sur les attributs et les tables à utiliser dans la requête.
1. Tous les titres de livre.
2. Tous les noms d'usager.
3. Tous les noms d'usager en retirant les doublons.
4. Les titres des livres publiés avant 1980.
5. Les titres des livres dont le titre contient la lettre « À ».
6. Les isbn des livres à rendre pour le 01/01/2020.
7. Les noms d'auteurs triés par ordre alphabétique.
8. Les noms d'usagers vivant dans le 12ème ou 13ème arrondissement de Paris (codes postaux 75012 et 75013).
9. Les noms et adresses des usagers n’habitant pas dans une rue. (la chaîne « Rue » ne doit pas apparaître dans l’adresse).
10. Les années et titres des livres publiés lors d’une année bissextile.
On rappelle que ce sont les années divisibles par 4, mais pas celles divisibles par 100 sauf si elles sont divisibles par 400.
","title":"Exercice : requêtes simples, sans jointure ni imbrication"},{"edit":"Mettre le résultat ici (code et figure).
***** 9 et 10 pb sol
Exporter votre base de données.
Effacer toutes les tables.
Importer la base mediathèque.
En utilisant cette base, donner et tester le code SQL
de chacune des requêtes ci-dessous.
1. Le titre des livres empruntés.
2. Le titre des livres empruntés à rendre avant le 31/03/2020.
3.
4. Le nom et le prenom des usagers ayant emprunté des livres, sans dou-
Le nom et prenom de l’auteur du livre 1984”.
blons (ie. si un usager à emprunté plusieurs livres, il ne doit apparaître
qu'une fois dans le résultat).
Même requête que précédemment, avec les noms triés par ordre alpha-
bétique.
Les titre des livres publiés strictement avant Dune”.
7. Les noms et prenoms des auteurs des livres trouvés à la question
précédente,
8. Comme la question précédente, en retirant les doublons.
9. Le nombre de résultats trouvés à la question précédente.
Mettre le résultat ici (code et figure).
1.
SELECT titre FROM livre;
"},{"solution":"2.SELECT nom FROM usager;
3.
SELECT DISTINCT nom FROM usager;
"},{"solution":"4.
SELECT titre FROM livre WHERE annee <= 1980;
SELECT titre FROM livre WHERE annee <= 1980;
5.
SELECT titre FROM livre WHERE titre LIKE ’#A4’;
6.
SELECT isbn FROM emprunt WHERE retour = '2020-01-01';
SELECT isbn FROM emprunt WHERE retour = '2020-01-01';
7.
SELECT nom FROM auteur ORDER BY nom ASC;
SELECT nom FROM auteur ORDER BY nom ASC;
8.
SELECT nom FROM usager WHERE cp = '75012' OR cp = '75013';
SELECT nom FROM usager WHERE cp = '75012' OR cp = '75013';
9.
SELECT nom, adresse FROM usager
SELECT nom, adresse FROM usager
WHERE NOT (adresse LIKE ?’#Rue%');
=
10. SELECT annee, titre FROM livre WHERE annee # 4 = O0
AND (annee % 100 <> O OR annee % 400 = O0);
Dans ce dernier cas, on fera attention à l’utilisation des parenthèses, l’opé-
rateur AND étant prioritaire le OR (comme en Python).
Exercice 156 Soit la base de données de la médiathèque définie au cha-
pitre 18. Formuler simplement en francais les requêtes SQL suivantes.
L
2.
3.
SELECT * FROM livre WHERE titre LIKE ’#Robot{’;
SELECT nom, prenom FROM usager WHERE ville = ?Guingamp’;
SELECT u.nom, u.prenom
FROM usager AS u
JOIN emprunt AS e ON u.code_ barre = e.code_ barre
WHERE retour < 2020-04-02;
. SELECT l.titre
FROM livre ÀS l
WHERE l.isbn IN (SELECT isbn FROM livres
WHERE annee > 1990);
Réécrire la requête 4 de façon à utiliser une seule clause SELECT.
Solution page 493 0
Mettre le résultat ici (code et figure).
Exercice 157 Soit la base de données de la médiathèque définie au cha-
nitra 19 folonlor tone lac auteure avant rallaharé eur nn nnwrona at lacrenvoyer sous la forme (n1,p1,n2, pa, t) où les n; sont les noms des auteurs, Pi leur prénoms et # le titre du livre sur lequel ils ont collaboré. Si trois
auteurs ont collaboré sur le même livre, on souhaite avoir trois lignes de ré-
sultats (auteuri/auteurs, auteur2/auteurs et auteur Jauteurs) et non pas
les trois sur la même ligne. Pour ne pas afficher deux fois le même couple, on demande en plus que n1 < n2. Solution page 493 ©
Mettre le résultat ici (code et figure).
Exercice 158 On considère les trois tables décrites à la figure 19.1. Pour
chacune des requêtes SQL ci-dessous, caleuler son résultat (à la main).
1. SELECT * FROM x WHERE b > 3;
2. SELECT DISTINCT e FROM z
WHERE e > 10 AND e < 50;
3. SELECT * FROM y WHERE c % 2 = O ORDER BY d ASC;
4. SELECT x.a,x.b FROM x
JOIN z ON z.a = x.a
WHERE 2.e < 9;
5. SELECT DISTINCT x.b,y.d FROM x
JOIN z ON z.a = x.a
CREATE TABLE x (a INT PRIMARY KEY, b INT, CHECK (b >= O));
CREATE TABLE y (c INT PRIMARY KEY, d INT, CHECK (d <= 30));
CREATE TABLE z (a INT REFERENCES X(a),
© INT REFERENCES Y(c), e INT, UNIQUE (a,c));
11 | 30
141 9
15 1
1713
10 | 50
onwwv ei
15 | 15
17,19
16 | 12
10 | 20
11 | 30
l4 |
9 |12
1
2
5
7
1
2
2
3
4
5
2
7
7
Figure 19.1 — Trois tables x, y, z.JOIN y ON y.c = z.c;
Solution page 494 D
Exercice 159 On considère les trois tables décrites à la figure 19.1. Pour cha-
eune des modifications ci-dessous, indiquer si elle réussit ou si elle échoue. Si
elle réussit, indiquer comment la table est modifiée. Si elle échoue, expliqner
pourquoi. Les questions sont indépendantes, c'est-à-dire que chacune repart
des tables de la figure 19.1 entre chaque question.
1, UPDATE x SET b = b +a;
2. UPDATE x SET b = b - 2;
3. INSERT INTO z VALUES (1, 17, 1);
4. INSERT INTO z VALUES (1, 18, 1};
5. INSERT INTO z VALUES (1, 10, 1);
6. DELETE FROM y WHERE c >= 12 AND c <= 13;
7. DELETE FROM y WHERE c >= 12 AND c <= 14;
8. INSERT INTO y VALUES (40, 20);
9. INSERT INTO y VALUES (20, 40);
10. DELETE FROM z WHERE a 4 2=0OŒRc%2=00Re%2=0;
Mettre le résultat ici (code et figure).
Mettre le résultat ici (code et figure).
Mettre le résultat ici (code et figure).
Nous avons créé les tables suivantes ;
CREATE TABLE joueur (jid INT PRIMARY KEY,
nom VARCHAR(100) NOT NULL);
CREATE TABLE partie (j1 INT REFERENCES joueur(jid),
j2 INT REFERENCES joueur(jid),
score1 INT NOT NULL,
score2 INT NOT NULL,
CHECK ((j1 <> j2));
Modifier les ordres de création des tables en prenant en compte les modifications suivantes :
- La table partie contient en plus une colonne jour non nulle, indiquant la date à laquelle la partie à eu lieu.
- Les scores ne peuvent pas être négatifs.
- Deux joueurs ne peuvent pas jouer plusieurs fois le même jour.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
La table joueur est inchangée. On modifie la table partie.
CREATE TABLE partie (j1 INT REFERENCES joueur(jid),
j2 INT REFERENCES joueur(jid),
score1 INT NOT NULL,
score2 INT NOT NULL,
jour DATE NOT NULL,
UNIQUE (ji, j2, jour),
CHECK ((j1 < j2) AND
(score1 >= 0) AND
(score2 >= 0)));
En particulier, pour interdire de saisir pour le même jour une partie entre les joueurs 1 et 2 et une autre entre les joueurs 2 et 1, on force le jid du premier joueur à être strictement inférieur. La contrainte UNIQUE permet
ensuite de s'assurer que un triplet (j1, j2, jour) n’apparaît qu’une seule fois.
Exercice 153 Écrire un programme Python qui lit un fichier CSV infos.csv au format suivant :
- les champs sont séparés par des « ; »
- le fichier contient 4 colonnes nom, prenom, annee naissance, taille, représentant le nom, prénom, l'année de naissance et la taille (en cm) de personnes.
Le programme doit écrire sur sa sortie standard un script SQL (i.e. un ensemble d'ordres) qui crée une table permettant de stocker ces informations ainsi qu'un identifiant unique (entier) servant de clé primaire et remplit la table avec les données du fichier CSV.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
import csv
print (\"\"\"CREATE TABLE personne
(pid INT PRIMARY KEY,
nom VARCHAR (100),
prenom VARCHAR (100),
annee_naiss INT,
taille INT,
CHECK (taille >= O AND annee_naiss >= 0));\"\"\")
def q({s):
return s.replace(\"?\", \"2\")
with open (\"infos.csv\") as f:
pid = 0
for 1 in csv.DictReader(f, delimiter=”;°):
print (\"\"\"INSERT INTO personne
VALUES (Hd, °Hs’, ’#s’, Ha, Ha);\"\"\" 4 \\
(pid, q(1[\"nom\"]), q(1[\"prenom\"]), \\
int (1[\"annee_naiss\"]), int (1[\"taille\"])))
pid = pid +1
La fonction auxiliaire q prend en argument une chaîne de caractères et
échappe le caractère ? s’il est présent en le doublant (règle d'échappement
pour les chaînes SQL).
SOLUTIONSSELECT titre FROM livre;
Mettre le résultat ici (code et figure).
Mettre le résultat ici (code et figure).
Mettre le résultat ici (code et figure).
Mettre le résultat ici (code et figure).
- Détails
- Écrit par : Richard GAUTHIER
- Clics : 1325
[[{"title":"SQL - Bases de données relationnelles","posi":0},{"text":"
"},{"text":""}],[{"text":"
","title":"SQL : un langage de définition de données"},{"edit":"
"}],[{"text":"
","title":" "},{"edit":"
"}],[{"text":"
","title":" Vocabulaire"},{"edit":"
"}],[{"text":"
","title":"Types de données en SQL"},{"edit":"
"}],[{"text":"
","title":""},{"edit":"
"}],[{"text":"
"}],[{"text":"
","title":""},{"edit":"
"}],[{"text":"
","title":""},{"edit":"
"}],[{"text":"
","title":"Spécification des contraintes d'intégrité"},{"edit":"
"}],[{"text":"
"}],[{"text":"
"}],[{"text":"
","title":""},{"edit":"
"}],[{"text":"
","title":"Suppression de tables"},{"edit":"
"}],[{"text":"
","title":"Insertion dans une table"},{"edit":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
"},{"solution":""}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}]]
Le modèle relationnel introduit à la séquence précédente est un modèle mathématique permettant de raisonner sur des données tabulées. Il est mis en œuvre par un logiciel particulier, le Système de Gestion
de Bases de Données (SGBD en abrégé).
Un SGBD relationnel est un SGBD utilisant le modèle relationnel pour la représentation des données!. L'écrasante majorité des SGBD relationnels utilisent le langage SQL (Séructured Query Language, langage de requête structuré). Ce dernier permet d'envoyer
des ordres au SGDB.
Les ordres peuvent être de deux natures. Les mises à
jour permettent la création de relations, l’ajout d’entité dans ces dernières, leur modification et leur suppression.
Les requêtes permettent de récupérer les données répondant à des critères particuliers.
Directement inspiré du modèle relationnel introduit par E. Codd, le langage SQL permet la définition de relations ou tables dans une base de données relationnelle. Ce langage est standardisé par PISO sous la référence ISO/IEC 9075.
La dernière version du standard date de 2016.
Le langage SQL permet de créer des tables en spécifiant leur nom, leurs attributs, les types de ces derniers et les contraintes associées à la table.
Vous allez utiliserer comme SGBD pour travailler avec le langage SQL :
MariaDB avec l'application phpmyadmin
Pour cela, il faut aller sur le serveur privé (Vps) à l'adresse suivante:
et tapez les paramètres données par votre professeur.
Vous sélectionnez la base de donnée et cliquez sur l'onglet SQL pour exécuter vos expressions SQL.
Maintenant vous allez créer les tables correspondant à la modélisation finale de la médiathèque obtenue à la fin de la séquence précédente.
On saisit les ordres suivants :
CREATE TABLE usager (nom VARCHAR(90) NOT NULL, prenom VARCHAR(90) NOT NULL,
adresse VARCHAR(300) NOT NULL, cp VARCHAR(5) NOT NULL,
ville VARCHAR(60) NOT NULL,
email VARCHAR(60) NOT NULL,
code_barre CHAR(15) PRIMARY KEY);
CREATE TABLE livre (titre VARCHAR(300) NOT NULL,
editeur VARCHAR(90) NOT NULL,
annee INT NOT NULL,
isbn CHAR(14) PRIMARY KEY);
CREATE TABLE auteur (a_id INT PRIMARY KEY,
nom VARCHAR(90) NOT NULL,
prenom VARCHAR(90) NOT NULL);
CREATE TABLE auteur_de (a_id INT REFERENCES Auteur(a_id),
isbn CHAR(14)
REFERENCES Livre(isbn),
PRIMARY KEY (a_id, isbn));
CREATE TABLE emprunt (code_barre CHAR(15)
REFERENCES Usager(code_barre),
isbn CHAR(14) PRIMARY KEY
REFERENCES Livre(isbn),
retour DATE NOT NULL);
Vous devez obtenir ceci :
Vous voyez que l’interaction avec un SGBD se fait par l'envoi d’une suite d'ordres SQL.
Un ordre peut s'étendre sur plusieurs lignes.
Les blancs et l’indentation ne sont pas significatifs mais améliorent la lisibilité dans certains cas.
Un ordre se termine par un « ; ».
Les cinq ordres donnés ci-dessus créent les cinq
tables composant notre base de données.
Une première remarque de syntaxe est que SQL est insensible à la casse. On aurait ainsi pu écrire « create
table livre ... » ou encore « CReATE TablE liVrE ... ». Nous faisons le choix d'utiliser des capitales pour les mots clés du langage SQL (CREATE, TABLE, PRIMARY, etc.) et des minuscules pour les noms d’attributs (a_id,
prenom, etc.) et de tables.
Ces derniers ne pouvant pas contenir d’espace,
nous utilisons le caractère « _» comme séparateur de mots.
Enfin, nous utilisons la convention, généralement considérée comme une bonne pratique, d'utiliser des noms de tables au singulier.
Une autre remarque de syntaxe est que le langage SQL peut sembler « verbeux », à l'inverse d’un langage
de programmation comme Python.
Les ordres ressemblent à du langage naturel (en anglais). La notation est aussi semblable aux schémas du modèle relationnel.
Mettre le résultat ici.
La syntaxe générale d'un ordre CREATE TABLE est la suivante :
CREATE TABLE nom_table (att1 dom1 contri1? ,
.......... ,
attn domn contrin? ,
contr_glob1,
......... ,
contr_globn ) ;
On donne, à la suite des mots clés CREATE TABLE, un nom de table, suivi de la liste des définitions d’attributs entre parenthèses et séparées par des virgules.
Une définition d’attribut consiste en un nom d'attribut, un type d’attribut (ou domaine) qui sont tous les deux obligatoires et optionnellement des contraintes sur cet attribut, Si des contraintes portent sur plusieurs attributs à la fois (par exemple pour spécifier que plusieurs attributs forment une clé primaire) on peut placer ces contraintes en fin de liste, avant la parenthèse fermante.
Attention : Le système lèvera une erreur si la table existe déjà.
Strictement parlant, une relation du modèle relationnel et une table SQL ne sont pas des concepts équivalents. En particulier, une table peut contenir des doublons. En effet, il n'est pas obligatoire de spécifier une clé primaire lors de la création d’une table SQL. Sans clé primaire, une table peut contenir plusieurs copies du même n-uplet, sans que cela pose problème, chose qui n’est a priori pas autorisé pour les ensembles du modèle relationnel (car ce sont des ensembles).
Nous ignorerons ces différences dans le cadre du programme de terminale et utiliserons les termes «tables» et «relations» indistinctement.
De façon analogue, en SQL les attributs d’une relation sont appelées des colonnes et les entités des lignes.
Mettre le résultat ici.
Les domaines abstraits du modèle relationnel correspondent à des types de données du langage SQL.
Nous avons comme type :
Types numériques.
nom du type | exact ou approché | description |
SMALLINT | exact | entier 16 bits signé |
INTEGER | exact | entier 32 bits signé |
INT | exact | alias pour INTEGER |
BIGINT | exact | entier 64 bits signé |
DECIMAL(t, f) | exact | décimal signé de t chiffres dont f après la virgule |
REAL | approché | flottant 32 bits |
DOUBLE PRECISION | approché | flottant 64 bits |
Le standard SQL définit plusieurs types numériques.
Ces derniers sont soit des types numériques exacts, soit des types numériques approchés.
La table ci-dessous détaille les différents types numériques. La plupart de ces types représentent fidèlement les entiers où flottants «machine» manipulables directement par le processeur. Une exception notable est le type DECIMAL(E, f) qui permet de représenter de manière exacte un nombre à virgule d'une taille donnée. Ce type est particulièremnent important, car il permet par exemple de représenter des sommes d'argent sans erreurs d'arrondis. Ainsi, le type
DECIMAL(5,2) permet de stocker des valeurs décimales de 5 chiffres, dont deux après la virgule, soit des valeurs entre -999, 99 et 999,99. Une utilisation de ce type permettra de réaliser une contrainte de domaine
sur un attribut numérique. Le standard ne supporte que les nombres en base 10, sans autoriser des notations hexadécimales ou octales comme dans les
autres langages.
Mettre le résultat ici (code et figure).
Types textes
nom du type | description |
CHAR(n) | Chaîne d’exactement n caractères. Les caractères manquant sont complétés par des espaces. |
TEXT Chaîne de taille quelconque. | Chaîne d’au plus n caractères. |
TEXT | Chaîne de taille quelconque. |
Le standard SQL définit plusieurs types permettant de stocker des chaînes de caractères. Malheureusement, ces derniers sont supportés
de manière inégale dans les divers SGBD. On se limitera aux plus communs.
Le type CHAR(n) permet de définir des chaînes de caractères de taille exactement n. La taille maximale acceptée pour n dépend des différents systèmes, mais ils supportent tous au moins 8000 caractères. La taille minimale est 1. Ce type est approprié lorsque l’on veut stocker des exactement 14 caractères, de la forme *XXX-XXXXXXXXXX’ (trois chiffres, un tiret, dix chiffres). Attention, si l’on stocke une chaîne de taille inférieure à n, cette dernière est complétée par la droite avec des espaces. Ainsi, la chaîne \"hello’ stockée dans une colonne de type CHAR(10) sera convertie en ’hello_____' (où le caractère « _ » représente un espace).
Le type VARCHAR(n) permet quant à lui de définir des chaînes de taille au plus n. La valeur de n suit les mêmes règles que pour le type CHAR.
Enfin, le type TEXT permet de stocker des chaînes de caractères de taille variable, sans fixer de taille maximale à priori. En pratique, il est équivalent à VARCHAR(n) pour la plus grande valeur de n supportée par le système. Les chaînes de caractères littérales sont délimitées par des guillemets simples « ' ».
Le caractère guillemet peut être échappé en le doublant,
Par exemple, la chaîne « c'est moi » s’écrira ’c' 'est moi'.
Les autres caractères n’ont pas besoin
d'être échappés. Une chaîne peut en particulier contenir un retour chariot (et donc être écrite sur plusieurs lignes).
Type booléen
Le type BOULEAN est inégalement supporté par les différents systèmes, qu’ils soient commerciaux ou libres. Cela est dû au fait que le standard SQL laisse ce type comme optionnel.
Les SGBD sont donc libres de ne pas le proposer. Une alternative possible est d'utiliser CHAR(1) et de se
servir de deux caractères distincts (par exemple 'T et 'F') pour représenter des booléens.
Une autre alternative est d'utiliser un type numérique exact et de considérer la valeur 0 comme fausse et les autres valeurs comme vraies.
","title":""},{"edit":"Mettre le résultat ici (code et figure).
Type des dates, durées et instants
nom du type | description |
DATE | une date au format 'AAAA-MM-JJ' |
TIME | une heure au format ’hh:mm:ss' |
TIMESTAMP | un instant (date et heure) au format 'AAAA-MM-JJ hh:mm:ss’ |
À première vue anodine, la gestion des dates et du temps est un problème excessivement complexe, source de nombreux bugs. Le standard SQL propose donc de nombreux types temporels permettant de représenter des dates, des heures et des durées.
Quelques types sont donnés dans la table ci-dessus. Les valeurs de ces types s’écrivent comme de simples chaînes de caractères. Une fonctionnalité intéressante est la possibilité d'utiliser l'addition pour ajouter des jours à une valeur de type DATE.
Si d est une expression de type DATE, alors d + 10 représente la date + 10 jours après d. Cette opération produit une valeur de type DATE et donc prend correctement en compte les changements de mois, année et les années bissextiles.
Nous ne détaillons pas plus ces types de données très
complexes (qui savent par exemple prendre en compte les fuseaux horaires).
Mettre le résultat ici (code et figure).
Valeur NULL
Une valeur notée NULL existe en SQL. Elle représente une absence de valeur et peut donc être utilisée à la place de n’importe quelle autre valeur, quel que soit le type attendu.
Son utilisation est similaire à la constante None du langage Python, mais son comportement est complexe et peut être source d’erreurs.
Il est déconseillé de l’utiliser dans le cadre d’une initiation aux bases de données. En particulier, SQL interdit l’utilisation de NULL comme valeur pour une clé primaire. En revanche, elle est autorisée pour les clés étrangères. La valeur NULL permet donc de violer la contrainte de référence.
La seule chose que l’on fera donc avec des attributs potentiellement NULL est de les tester au moyen des expression
- e IS NULL
- ou e IS NOT NULL.
Attention cependant, le test e = NULL ne produit pas le résultat booléen comme s’y attendrait mais renvoie toujours NULL quelle que soit la valeur de e .
Mettre le résultat ici (code et figure).
Les contraintes d’intégrité jouant un rôle fondamental dans le modèle relationnel, il est naturel de pouvoir les spécifier en SQL.
Nous montrons ici comment définir les quatre types de contraintes d’intégrité étudiés au précédent :
Clé primaire. Les mots clés PRIMARY KEY permettent d'indiquer qu'un attribut est une clé primaire.
Voici un exemple :
CREATE TABLE personne (id INT PRIMARY KEY,
nom VARCHAR(99),
prenom VARCHAR (99));
Si l’on souhaite utiliser plusieurs attributs comme clé primaire, on peut spécifier la contrainte après les attributs :
CREATE TABLE point (x INT,
y INT,
couleur VARCHAR(30),
PRIMARY KEY (x. y)):
Mettre le résultat ici (code et figure).
Clé étrangère. Un attribut peut être qualifié de clé étrangère en utilisant le mot clé REFERENCES suivi de la table où se trouve la clé primaire et de son nom.
CREATE TABLE employe (emp INT REFERENCES personne(id),
dept VARCHAR (90),
suph INT REFERENCES personne(id));
La table employe associe un employé (attribut emp}, qui est une personne, à son département dans l’entreprise (ressources humaines, support informatique, comptabilité, etc.) et à son supérieur hiérarchique (attribut suph), qui est aussi une personne. Ce lien est matérialisé par le fait que emp et suph sont des clés étrangères. Il est à noter que la plupart des SGBD ne supportent pas l’utilisation de clés étrangères composites.
L'utilité des clés primaires composites se trouve donc amoindrie en pratique.
","title":""},{"edit":"Mettre le résultat ici (code et figure).
Unicité, non nullité.
Il peut être intéressant de spécifier qu'un groupe d’attributs est unique, sans pour autant en faire une clé primaire. Cette information permet au SGBD plus de vérifications sur les données (cohérence
des données) et parfois de traiter ces dernières de façon plus efficace.
Cela peut être spécifié au moyen du mot clé UNIQUE.
Une autre bonne pratique consiste à déclarer qu’un attribut ne peut pas être NULL. Cette valeur spéciale ne pourra donc jamais être utilisée pour remplir des valeurs de la colonne correspondante.
Cela peut être fait au moyen du mot clé NOT NULL.
Notons que PRIMARY KEY implique obligatoirement NOT NULL.
En reprenant l'exemple des utilisateurs de la bibliothèque, on pourrait raffiner notre définition de table de la manière suivante :
CREATE TABLE usager (nom VARCHAR(90) NOT NULL,
prenom VARCHAR(90) NOT NULL,
adresse VARCHAR(300) NOT NULL,
cp VARCHAR(S5) NOT NULL,
ville VARCHAR(60) NOT NULL,
email VARCHAR(60) NOT NULL UNIQUE,
code_barre CHAR(15) PRIMARY KEY);
On spécifie de cette façon qu'aucun des attributs n’est optionnel et de plus que email doit être UNIQUE dans la table (même s’il n’est pas une clé primaire).
","title":""},{"edit":"Mettre le résultat ici (code et figure).
Contraintes utilisateur.
Il est possible de spécifier des contraintes arbitraires sur les attributs d’une même ligne au moyen du mot clé CHECK, suivi d'une formule booléenne. Cette contrainte est placée obligatoirement en fn de déclaration avant la parenthèse fermante (et non pas au niveau d’un attribut).
Nous donnons ici quelques exemples et reviendrons dessus dans la séquence suivante sur la syntaxe des expressions.NETS
CREATE TABLE produit (id INT PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
quantite INT NOT NULL,
prix DECIMAL(10,2) NOT NULL,
CHECK (quantite >= 0 AND prix >= 0));
Nous définissons ici une table des produits vendus dans un magasin. Ces derniers ont un identifiant (qui est la clé primaire), un nom, une quantité et un prix.
Ceux-ci ne peuvent jamais être négatifs (ce qui n’est pas exprimable uniquement au moyen des types INT ou DECIMAL). On ajoute donc une contrainte CHECK.
Mettre le résultat ici (code et figure).
Une fois qu’une table est créée, il n'est pas possible d'en créer une autre avec le même nom. Si on souhaite recréer la table, par exemple avec un schéma différent, il faut d'abord supprimer celle portant le même nom.
C’est le but de l'instruction DROP TABLE :
DROP TABLE auteur_de;
Cette dernière supprime la table et donc toutes les données qui y sont stockées.
Un point important est qu'il n'est pas possible de supprimer une table si elle sert de référence pour une clé étrangère d’une autre table, car cela violerait une contrainte de référence :
# DROP TABLE usager;
ERROR: cannot drop table utilisateurs because other objects
depend on it
DETAIL: constraint emprunt code barre_fkey on table
emprunt depends on table usager
Nos reproduisons ci-dessus une interaction avec le SGBD PostgreSQL, mais tous les autres systèmes relationnels auront un comportement semblable.
Le système indique que la table usager est mentionnée par une contrainte (ici celle de la table emprunt). Le système refuse donc de supprimer la table et renvoie une erreur.
Il convient alors de supprimer les tables dans le bon ordre, c'est-à-dire d'abord les tables contenant les clés étrangères, avant les tables contenant les clés primaires référencées.
DROP TABLE emprunt;
DROP TABLE auteur_de;
DROP TABLE auteur;
DROP TABLE livre;
Certains SGBD permettent de spécifier que la suppression d’une table doit détruire automatiquement toutes les tables qui dépendent d’elle. Cette fonctionnalité est commode mais dangereuse et de toute façon inégalement supportée. Il est donc conseillé de spécifier explicitement les commandes de
suppression, dans l’ordre adéquat.
Le standard SQL en pratique. Bien que très volumineux {le standard ISO/IEC 9075 qui définit la norme SQL se décompose en 14 parties distinctes et fait plus de 5000 pages). Le standard SQL comporte beaucoup de parties optionnelles. En pratique, même si la plupart des SGBD commerciaux et libres en implémentent une partie, leur support est inégal.
Il n’est donc pas rare de devoir écrire du code SQL « propriétaire », c'est-à-dire utilisant des extensions propres à SGBD particulier, dès que l’on souhaite faire des traitements un tant soit peu complexes. Cette
pratique nuit malheureusement à la portabilité.
Mettre le résultat ici (code et figure).
Nous pouvons enfin aborder l'insertion de nouvelles valeurs dans une table.
Cette action s'effectue au moyen de l’ordre INSERT INTO
INSERT INTO auteur VALUES (97, 'Ritchie','Dennis');
On spécifie le nom de la table (ici auteur) suivi d'une suite de n-uplets, chacun entre parenthèses.
Chaque n-uplet représente une nouvelle ligne de
la table. Les valeurs des attributs sont supposés être dans le même ordre que lors du « CREATE TABLE ». Si on souhaite les passer dans un ordre différent, on peut spécifier l’ordre des attributs avant le mot clé VALUES
INSERT INTO auteur (prenom, a_id, nom)
VALUES (’Jean-Jacques’, 200, ’Rousseau’);
Un point important est que les contraintes d’intégrités sont vérifiées au moment de l'insertion.
Une instruction INSERT violant ces contraintes conduira donc à une erreur et les données correspondantes ne seront pas ajoutées à la table.
# INSERT INTO auteur (97, ’Dumas’, ’Alexandre”’);
ERROR: duplicate key value violates unique constraint
“auteur _pkey\"
TDETATT Vaner Pa SANLÉOTN Smandiu nvicte
Ici, on essaye d’ajouter un auteur avec la même clé primaire qu’un auteur
déjà existant.
# INSERT INTO produit VALUES (1, ’ordinateur’, 10, -200);
ERROR: new row for relation \"produit\" violates check
coustraint \"produit _check\"
DETAIL: Failing row contains (1, ordinateur, 10, -200.00).
Ici, l'insertion viole la contrainte CHECK de la table produit définie plus haut.
Mettre le résultat ici (code et figure).
Regrouper ensemble les termes synonymes : colonne, entité, domaine, attribut, ligne, schéma, base de données, type, column, row.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
(colonne, attribut, column), (ligne, entité, row),
(domaine, type), (schéma), (base de données).
Nous avons le modéliser l'annuaire de la manière suivante :
Annuaire(nom String, prénom String, tel String)
Les contraintes sur le modèle sont les suivantes :
- le numéro de téléphone est unique;
- le numéro de téléphone est une clé primaire.
- les attributs nom et le prenom ne peuvent pas être vides.
Donner un ordre SQL permettant de créer la table correspondante,
avec un maximum de contrainte d'intégrité.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
CREATE TABLE annuaire( nom VARCHAR(100) NOT NULL,
prenom VARCHAR(100) NOT NULL,
tel VARCHAR(20) PRIMARY KEY);
Nous avons le modèle relationnel suivant :
Eleve(nom String, prénom String, num String)
Matiere(intitule String, m_id INT)
Note(num String, m_id INT, note Float)
Contrainte : attribut note compris entre 0 et 20.
Donner les ordres SQL permettant de créer les tables correspondantes, avec un maximum de contrainte d'intégrité.
Donner les ordres SQL permettant de supprimer ces tables une fois qu’elles existent.
Mettre le résultat ici (code et figure).
CREATE TABLE eleve (nom VARCHAR(100) NOT NULL,
prenom VARCHAR(100) NOT NULL,
num VARCHAR(20) PRIMARY KEY);
CREATE TABLE matiere ( intitule VARCHAR(100) NOT NULL,
m_id INT PRIMARY KEY);
CREATE TABLE note ( num VARCHAR(20) REFERENCES eleve(num),
m_id INT REFERENCES matiere(m_id),
note DECIMAL(4,2) NOT NULL,
PRIMARY KEY (num, m_id),
CHECK (note >= O AND note <= 20));
DROP TABLE note;
DROP TABLE matiere;
DROP TABLE eleve;
Pour chacune des séquences d'ordres SQL suivantes, dire quelle instruction provoque une erreur.
On suppose que la base de données ne contient aucune table au début de chaque séquence,
1. DROP TABLE client;
CREATE TABLE client (cid INT PRIMARY KEY,
nom VARCHAR (100),
prenom VARCHAR (100),
points _fidelite INT NOT NULL,
CHECK (points_fidelite >= 0));
2. CREATE TABLE client (cid INT PRIMARY KEY,
nom VARCHAR (100),
prenom VARCHAR(100),
points_fidelite INT NOT NULL,
CHECK(points_fidelite >= 0));
CREATE TABLE commande (cid INT REFERENCES client (cid),
pid INT REFERENCES produit (pid),
date DATE NOT NULL) ;
CREATE TABLE produit (pid INT PRIMARY KEY,
nom VARCHAR (100),
prix DECIMAL(10,2));
3.
CREATE TABLE client (cid INT PRIMARY KEY,
nom VARCHAR(100),
prenom VARCHAR(100),
points _fidelite INT NOT NULL,
CHECK (points_fidelite >= 0));
CREATE TABLE produit (pid INT PRIMARY KEY,
nom VARCHAR (100),
prix DECIMAL(10,2));
CREATE TABLE commande (cid INT REFERENCES client(cid),
nomp VARCHAR(100)
REFERENCES produit (nom),
date DATE NOT NULL) ;
4.
CREATE TABLE client (cid INT PRIMARY KEY,
nom VARCHAR (100),
prenom VARCHAR(100),
points_fidelite INT NOT NULL,
CHECK (points _fidelite >= 0));
CREATE TABLE produit (pid INT PRIMARY KEY,
nom VARCHAR (100),
prix DECIMAL(10,2));
CREATE TABLE commande (cid INT REFERENCES client(cid),
pid INT REFERENCES produit (pid),
date DATE NOT NULL);
INSERT INTO commande VALUES (0, 0, 2020-03-02);
Mettre le résultat ici (code et figure).
1. L'ordre DROP échoue car la table client n'existe pas.
2. L'ordre CREATE TABLE commande ... échoue car la table produits n'existe pas encore.
3. L'ordre CREATE TABLE commande ... échoue car la table nomp ne référence pas une clé primaire.
4. L'ordre INSERT échoue, les valeurs 0 et O ne font pas référence à des clés primaires.
On considère les deux tables suivantes :
CREATE TABLE joueur (jid INT PRIMARY KEY,
nom VARCHAR(100) NOT NULL);
CREATE TABLE partie (j1 INT REFERENCES joueur(jid),
j2 INT REFERENCES joueur(jid),
score1 INT NOT NULL,
score2 INT NOT NULL,
CHECK ((j1 <> j2));
Ces tables stockent des résultats de parties entre des joueurs. Lister toutes les contraintes d’intégrité et pour chacune donner des ordres SQL violant ces contraintes.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
On donne la liste des contraintes et à chaque fois
une suite d’ordres incompatibles avec celle-ci.
1. jid est une clé primaire (contrainte d’entité)
INSERT INTO joueur VALUES (1, 'TOTO’);
INSERT INTO joueur VALUES (1, 'TOTO’);
2. nom doit être non NULL (contrainte de domaine)
INSERT INTO joueur VALUES (1, NULL);
Requêtes similaires pour score1 et score2.
3. j1 et j2 sont des clé étrangère (contraintes de référence)
INSERT INTO partie VALUES (42, 43, 10, 10);
(on suppose qu'aucun jid n’a la valeur 42 ou 43)
4. j1et j2 doivent avoir des valeurs distinctes (car une personne ne peut pas jouer contre elle-même, contrainte utilisateur).
INSERT INTO joueur VALUES (1, ’Toto”);
INSERT INTO partie VALUES (1, 1, 10, 10);
5. Toutes les contraintes de domaine peuvent être violées en insérant une valeur d’un type différent de celui attendu.
Nous avons créé les tables suivantes ;
CREATE TABLE joueur (jid INT PRIMARY KEY,
nom VARCHAR(100) NOT NULL);
CREATE TABLE partie (j1 INT REFERENCES joueur(jid),
j2 INT REFERENCES joueur(jid),
score1 INT NOT NULL,
score2 INT NOT NULL,
CHECK ((j1 <> j2));
Modifier les ordres de création des tables en prenant en compte les modifications suivantes :
- La table partie contient en plus une colonne jour non nulle, indiquant la date à laquelle la partie à eu lieu.
- Les scores ne peuvent pas être négatifs.
- Deux joueurs ne peuvent pas jouer plusieurs fois le même jour.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
La table joueur est inchangée. On modifie la table partie.
CREATE TABLE partie (j1 INT REFERENCES joueur(jid),
j2 INT REFERENCES joueur(jid),
score1 INT NOT NULL,
score2 INT NOT NULL,
jour DATE NOT NULL,
UNIQUE (ji, j2, jour),
CHECK ((j1 < j2) AND
(score1 >= 0) AND
(score2 >= 0)));
En particulier, pour interdire de saisir pour le même jour une partie entre les joueurs 1 et 2 et une autre entre les joueurs 2 et 1, on force le jid du premier joueur à être strictement inférieur. La contrainte UNIQUE permet
ensuite de s'assurer que un triplet (j1, j2, jour) n’apparaît qu’une seule fois.
Écrire un programme Python qui lit un fichier CSV infos.csv au format suivant :
- les champs sont séparés par des « ; »
- le fichier contient 4 colonnes nom, prenom, annee naissance, taille, représentant le nom, prénom, l'année de naissance et la taille (en cm) de personnes.
Le programme doit écrire sur sa sortie standard un script SQL (i.e. un ensemble d'ordres) qui crée une table permettant de stocker ces informations ainsi qu'un identifiant unique (entier) servant de clé primaire et remplit la table avec les données du fichier CSV.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
import csv
print (\"\"\"CREATE TABLE personne
(pid INT PRIMARY KEY,
nom VARCHAR (100),
prenom VARCHAR (100),
annee_naiss INT,
taille INT,
CHECK (taille >= O AND annee_naissance >= 0));\"\"\")
def q(s):
return s.replace(\"'\", \"''\")
with open (\"infos.csv\") as f:
pid = 0
for l in csv.DictReader(f, delimiter=\";\"):
print (\"\"\"INSERT INTO personne
VALUES (%d, '%s', '%s', %d, %d);\"\"\" % \\
(pid, q(l[\"nom\"]), q(l[\"prenom\"]), \\
int(l[\"annee_naissance\"]), int(l[\"taille\"])))
pid = pid +1
La fonction auxiliaire q prend en argument une chaîne de caractères et échappe le caractère \" s’il est présent en le doublant ''(règle d'échappement pour les chaînes SQL).
SOLUTIONSSELECT titre FROM livre;
- Détails
- Écrit par : Richard GAUTHIER
- Clics : 1400
[[{"title":"Python - Modèle relationnel","posi":0},{"text":"
"},{"text":""}],[{"text":"
","title":"Contrainte de référence."},{"edit":"
"}],[{"text":"
","title":"Contraintes utilisateurs"},{"edit":"
"}],[{"text":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":""}],[{"text":"
"},{"solution":""}],[{"text":"
"},{"solution":"
"}]]
....
Considérons une activité : l'emprunt d’un livre dans une médiathèque municipale.
Alice trouve le livre qu’elle souhaite emprunter et approche d’une borne d'emprunt automatique. Elle scanne le code barre de sa carte de bibliothèque et des informations la concernant apparaissent :
« Alice, aucun livre en cours de prêt ».
Elle peut ensuite scanner le code barre apposé sur le livre. Les informations sont alors mises à jour pour afficher la liste des livres empruntés :
« La Programmation en pratique, B. Kernighan et R. Pike, Ed. Vuibert, 2017, ISBN 978-2711786701 ».
Sur la borne d’à côté, Basile vient rendre ses livres. Il scanne lui aussi sa carte et les informations le concernant apparaissent à l'écran :
« Basile, un livre en cours de prêt ».
Basile peut alors scanner le code barre du livre qu’il rapporte et ce dernier est supprimé de la liste.
Cette simple activité nous permet d'illustrer les caractéristiques d’un système d’information, c’est-à-dire d'un système technique (ici informatique) et humain permettant de gérer de l'information.
Que pouvons-nous dire sur le système informatique sous-jacent ?
En premier lieu, il contient une description d’objets (les livres), de personnes physiques (les usagers) et des
processus (l'emprunt et la restitution de livres).
Le système ne contient cependant qu’une «approximation» de la réalité :
En effet, il ne retient pour Alice et Basile que leur nom et prénom, éventuellement leur date d’inscription et
les livres en cours d'emprunt ;
Nous pourrions rajouter d’autres informations telle que :
- leur taille,
- la couleur de leurs yeux
- ou leur plat préféré ne sont pas connus du système.
******
De même pour les livres seuls le titre, les auteurs, l'éditeur et l'ISRN [le
Les clés primaires ne permettent pas seulement
de distinguer les entités de manière unique.
Elles permettent aussi de servir de référence dans une autre relation.
Intéressons nous maintenant à la relation Emprunt. L'exemple que l’on a donné en introduction est naïf, car
il stocke dans cette table le prénom de l'utilisateur, le titre du livre et la date de rendu.
On pourra plutôt définir la relation Emprunt avec le schéma suivant :
Emprunt(code_barre ; String, isbn : String, retour : Date)
Ici, code_barre et isbn sont des clés étrangères.
Cela signifie que la valeur de l’attribut code _barre dans la relation Emprunt doit être l’une des valeurs existantes pour cet attribut dans la relation Usager.
De même, isbn doit correspondre à un isbn existant dans la relation Livre. Cette contrainte permet de garantir que la relation Emprunt ne mentionne que des livres et des usagers connus de la base de données.
Elle permet d'éviter de rajouter des valeurs fictives ne correspondant à aucun utilisateur ou à aucun livre.
De manière plus importante, elle empêche aussi de supprimer des entités des relations Livre et Usager. En particulier, si la relation Emprunt contient l’entité
(\"123456789\", \"978-2340033641\", 07/03/2020),
alors retirer l’utilisateur dont le code barre est \"23456789\" de la relation Usager est une violation de la contrainte de référence, car la valeur \"123456789\" ne
serait plus celle d’une clé primaire dans la relation Usager (et d'un point de vue pratique, cela permet de s'assurer qu'avant de désinscrire une personne, cette dernière a rendu tous ses livres).
Une autre remarque que l’on peut faire sur le schéma Emprunt est que l’isbn est déclaré comme clé primaire de la relation. Cela implique qu’un même livre ne peut apparaître dans deux entités distinctes. Là encore, on
voit que la contrainte nous permet de garantir la cohérence des données :
il n’est pas possible qu’un même livre soit emprunté par deux usagers en même temps.
Une dernière observation importante sur l’utilisation des clés étrangères est qu'elle permet de créer des associations multiples entre entités de différentes relations.
Ici, on peut associer au même utilisateur plusieurs livres
en cours d'emprunt. En effet, code_barre n'étant qu’une clé étrangère, il n’a pas à être unique dans la relation Emprunt :
un même utilisateur a le droit d’emprunter plusieurs livres. En d’autres termes, nous pouvons par ce moyen associer à un utilisateur une liste de livres.
Nous pouvons utiliser cette technique pour pallier un défaut de conception de la relation Livre.
En effet, dans cette dernière, nous avons représenté les auteurs du livre comme un chaîne de caractères dans laquelle est écrite la liste des auteurs.
Bien que cette modélisation fonctionne, elle ne permet pas d'exprimer certaines contraintes sur les données, par exemple qu’un même auteur n’apparaît pas deux fois pour le même livre.
En effet. les chaînes de caractères étant arbitraires, on peut y saisir n'importe quoi. L'utilisation de clés étrangères va nous permettre de remédier à ce problème. On simplifie dans un premier temps le schéma de la relation Livre pour ne plus mentionner les auteurs :
Livre(titre : String, éditeur : String, année : Int, isbn : String)
On peut ensuite créer une nouvelle relation, Auteurs ayant le schéma suivant :
Auteur(a_id : Int, nom : String, prénom : String)
Ici, l’attribut a_id est un identifiant d’auteur unique, associé à l’auteur lorsque l'employé de la médiathèque rajoute un nouvel auteur dans la base.
La relation Auteur pourrait par exemple être la suivante :
{ (0, ’Goscinny”,’René’), (10, ’Kernighan’,’Brian’),
(2, ’Conchon’,’Sylvain’), (42, ’Filliâtre’, Jean-Christophe’), (4, ’Pike’,’Rob”’), (19, ’Balabonski”,’Thibaut’), (23, ’Uderzo”,’Albert’), (77, Nguyen’, ’Kim’), ... }
La seule contrainte sur les identifiants est qu’ils doivent être uniques.
Il n’y a en particulier aucune notion d'ordre. Munis des relations Livre et Auteur, nous pouvons associer des auteurs à des livres au moyen de la relation Auteur_ de :
Auteur_de(a_id : Int, isbn : String)
Cette relation associe des auteurs à des livres. Les attributs a_id et isbn sont des clés étrangères faisant référence aux relations Auteur et Livre respectivement.
Le couple de ces deux attributs forment la clé primaire de la table Auteur_de. Cela empêche qu’un même auteur et un même livre apparaissent deux fois dans la relation et donc qu’un même auteur soit mentionné deux fois pour le même ouvrage.
En revanche, rien n'empêche qu’un même auteur apparaisse plusieurs fois pour des ouvrages différents, ou que différents auteurs apparaissent pour le même ouvrage.
Attention cependant, si un isbn n'apparaît pas dans cette relation, c’est que le livre correspondant n’a pas
d'auteur. Les contraintes de clé primaires et étrangères ne permettent pas d'empêcher ce cas de figure.
Nous terminons ce tour d’horizon des contraintes
d’intégrité par les contraintes utilisateurs (parfois appelées contraintes métier).
Ces dernières sont toutes les contraintes d’une relation qu’on ne peut exprimer par les trois précédentes. Un exemple de contrainte utilisateur est qu’un âge de personne doit être positif et inférieur à 200. Pour notre médiathèque, une autre contrainte utilisateur pourrait être que la chaîne de caractères représentant l'e-mail contienne un et un seul caractère « @ ».
Ces contraintes, liées à l’utilisation que l’on veut faire de la base de données,sont importantes mais difficilement exprimables dans la syntaxe très simple
des schémas.
On veillera donc à en faire une description précise en français.
Nous verrons dans la séquence suivant que les systèmes de gestion de bases de données proposent une syntaxe pour écrire certaines de ces contraintes.
Le modèle relationnel est un modèle dans lequel
les données sont représentées par des ensembles de n-uplets appelés des relations.
Un élément d’une relation est appelé une entité. Il représente généralement un objet,
une action, une personne du monde réel.
Chaque entité possède des propriétés appelées des attributs.
On spécifie une relation en donnant son schéma, c’est-à-dire son nom, la liste de ses attributs avec leur domaine, c’est-à-dire l’ensemble des valeurs que peuvent prendre un attribut.
Une base de données est un ensemble de relations et le schéma d’une base est l’ensemble des schémas des
relations qui la compose.
La cohérence des données au sein d’une base est assurée par des
contraintes d’intégrité. Ces dernières sont des invariants, c’est-à-dire
des propriétés logiques que les données doivent vérifier à tout instant.
On distingue parmi ces contraintes :
Les contraintes d’entité qui garantissent que chaque entité d’une relation est unique. Une clé primaire est un ensemble d’attributs qui identifie chaque entité de la relation de manière unique et garantit la contrainte d’entité.
Les contraintes de référence qui créent des associations entre deux relations. Elle permettent de garantir qu’une entité d’une relation B mentionne une entité existante dans une relation A.
Une clé étrangère est un ensemble d’attributs d’une table qui sont une clé primaire dans une autre table.
Les contraintes de domaines qui restreignent les valeurs d’un attribut à celles du domaine et évitent que l’on puisse donner à un attribut une valeur illégale.
Les contraintes utilisateurs qui restreignent encore plus les valeurs d’un ou de plusieurs attributs et sont guidées par la nature des données que l’on souhaite stocker dans la base.
Ces contraintes doivent être utilisées pour assurer la qualité des données :
elles permettent de s'assurer que les données sont « conformes » aux entités du monde réel qu’elles représentent.
","title":" "},{"edit":"Mettre le résultat ici.
On souhaite modéliser un annuaire téléphonique simple dans lequel chaque personne (identifiée par son nom et son prénom) est associée
à son numéro de téléphone.
Proposer une modélisation relationnelle de cet
annuaire.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
De façon très simple, on peut modéliser l'annuaire
de la manière suivante :
Annuaire(nom : String, prénom : String, tel :String)
On n’oubliera pas de préciser que le numéro, par définition unique, est une clé primaire. Son domaine peut être String afin d’éviter les problèmes de 0 en première position ou de permettre de saisir des caractères non numériques
comme +.
Donner la modélisation relationnelle d’un bulletin scolaire.
Cette dernière doit permettre de mentionner :
- des élèves, possédants un numéro d'étudiant alphanumérique unique;
- un ensemble de matières fixées, mais qui ne sont pas données;
- au plus une note sur 20, par matière et par élève.
On prendra soin de préciser toutes les contraintes utilisateurs qui ne peuvent êtres inscrites dans les schémas des relations.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
La modélisation consiste en trois relations
Eleve(nom String, prénom String, num String)
Matiere(intitule String, m_id INT)
Note(num String, m_id INT, note Float)
On n’a pas moyen de forcer que la valeur de la note soit comprise entre 0 et
20. On à fait le choix de donner un indentifiant numérique à la matière.
On considère la solution donnée pour l'exercice précédent sur l'annuaire.
Dire si chacun des ensembles est une relation valide pour le schéma Annuaire.
Annuaire(nom : String, prénom : String, tel :String)
1. { }
2. {('Titi', 'Toto', '0123456789' )}
3. {( 'Titi', 'Toto’, '0123456789'), (’Doe”, 'John’, '0123466789’ )}
4.{('Titi','Toto', '0123456789'), ('Titi', 'Toto', '987654343210' )}
5. {('Titi', 'Toto', '0123466789'), (’Doe’,’John’)}
6. {('Titi',’Toto’, 42)}
Mettre le résultat ici (code et figure).
1. Oui, la relation vide est un ensemble valide
2. Oui, la relation ne contient qu’un triplet bien formé
3. Non, les deux triplets ont la même valeur pour l’attribut tel qui est une clé primaire.
4. Oui, les deux clés primaires des deux entités sont différentes.
5. Non, l’ensemble contient un couple, qui n’est pas une entité bien formée pour le schéma Annuaire
6. Non, l’ensemble contient un triplet dont la clé primaire est un nombre et non pas une chaîne
On considère la solution donnée pour l'exercice 141. Dire si
chacun des ensembles est une relation valide pour le schéma de la base de
données du bulletin de notes,
1 + Eleve={}
e Matiere = {}
e Note={}
2. e Eleve= {(°Titi’, Toto’, ’AB56789°),}
e Matiere = {(?N51°,0),(’Sport’,1)}}
e Note = {(°AB56789,1,17)}
3 + Eleve = {(’Titi’,’Toto”,’AB56789°),}
e Matiere = {(°NSI’,0)}
+ Note = {(’AB56789°,1,17)}4 + Eleve= {(°Titi’, Toto’, ’AB56789?), }
+ Matiere = {(°NSI’,0),}
+ Note = {(’AB56789°,0,17), (*AB56789°,0,18)}
ox
.
Eleve = {(°Titi’,’Toto?, ’AB56789),}
« Maticre = {(°NSI?,0), (’Sport”’,1}}
+ Note = {(*AB56789°,0,17), (’AB56789?,1,17)}
Solution page 488 0
Mettre le résultat ici (code et figure).
Exercice 144 Modéliser des informations sur les départements français.
Pour chaque département on veut pouvoir stocker son nom, son code, son
chef-lieu et la liste de tous les départements voisins. Attention, les codes de
département sont tous des nombres, sauf la Corse du Sud et la Haute Corse
qui ont les codes 2A et 2B respectivement, Les départements d'Outre-Mer
ont un code sur trois chiffres (de 971 à 976). Proposer une contrainte uti-
lisateur permettant d'éviter la redondance d'information dans la liste des
voisins. Solution page 488 D
Mettre le résultat ici (code et figure).
Exercice 145 Proposer une modélisation pour un réseau de bus. Cette der-
nière doit être suffisamment riche pour permettre de générer, pour chaque
arrêt de bus du réseau, une fiche horaire avec tous les horaires de passage
de toutes les lignes de bus qui desservent l'arrêt.
Indication : ici, plus qu'une simple traduction du français vers le modèle
relationnel, on essayera de déterminer dans un premier temps quelles in-
formations sont pertinentes et comment les représenter. On pourra ensuite
procéder à la modélisation sous forme de relations. Solution page 488 ©
Mettre le résultat ici (code et figure).
Exercice 146 On considère deux relations R(a Int,b Int,c Int) et
S(a Int,e Int) où l’attribut a de S est une clé étrangère faisant référence à
a de À. Dire si les affirmations suivantes sont vraies ou fausses, en justifiant.
1. Les a de À sont tous deux à deux distincts.
Les b de R sont tous deux à deux distincts.
Les a de S sont tous deux à deux distincts.
Les e de S sont tous deux à deux distincts.
S peut être vide alors que R est non vide.
Sp gœ
R peut être vide alors que $ est non vide
Solution page 489 0
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
Ecrire une fonction qui donne le nombre de jour de chaque mois. On utilisera un tableau pour stocké le nombre de jours de chaque mois.
Exemple :
print(nbjoursmois(2020, 2))
print(nbjoursmois(2020, 4))
Résultat :
29
30
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
On fait un cas particulier pour le mois de février, puis
on utilise un tableau comme suggéré.
def nbjoursmois(a, m):
if m == 2 and a%4==0 :
return 29
t = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
return t[m - 1]
- Détails
- Écrit par : Richard GAUTHIER
- Clics : 1295
[[{"title":"Python - Les Tableaux","posi":0},{"text":"
"},{"text":""}],[{"text":"
","title":" Problème : la pyramide des âges"},{"edit":"
"}],[{"text":"
","title":" Notion de tableau"},{"edit":"
0 1 2
"}],[{"text":"
","title":" "},{"edit":"
"}],[{"text":"
Tester le code dans le file de l'IDLE python.
"}],[{"text":"
","title":"Modification du contenu d’un tableau"},{"edit":""}],[{"text":"
","title":" "},{"edit":"
"}],[{"title":"Parcours d’un tableau"},{"text":"
"},{"edit":"
"}],[{"title":"Python et les autres langages."},{"text":"
"},{"edit":"
"}],[{"text":"
","title":" "},{"edit":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"}],[{"text":"
"}],[{"text":"
","title":"Construire de grands tableaux"},{"edit":"
"}],[{"text":"
"}],[{"text":"
","title":"Tableaux et chaînes de caractères"},{"edit":"
"}],[{"text":"
"}],[{"text":"
","title":"Tableaux et fonctions"},{"edit":"
"}],[{"text":"
","title":" "},{"edit":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}]]
La mémoire de nos ordinateurs est vaste. Dans la mémoire d'un seul ordinateur, on peut stocker sans difficulté les noms et prénoms de tous les français où encore l'intégralité des œuvres de Jules Verne. Pour donner un ordre de grandeur, le texte du Tour du monde en quatre-vingts jours contient un demi-million de caractères alors que là mémoire d’un ordinateur d'au-jourd'hui peut en contenir plusieurs milliards et donc plusieurs milliers de romans.
Pour autant, nous avons jusqu'à présent utilisé une infime partic de cette mémoire gigantesque, avec seulement quelques variables. Bien sûr, rien ne nous empêche d'avoir des programmes avec un très grand nombre de variables mais cela atteint vite ses limites. Pour stocker de grandes quantités d'information, il faut se tourner vers d'autres solutions et le tableau est la
plus simple d'entre elles.
Considérons un programme qui stocke la pyramide des âges des français, c'est-à-dire la répartition de la population française par âge. el permet à l'utilisateur de la consulter, Par exemple, on peut saisir un âge et le programme affiche le nombre de français avant cet âge-là, où on saisit deux âges et le programme affiche le nombre de français ayant un âge compris entre
ces deux valeurs, etc.
Pour stocker la pyramide des âges dans le programme,
on pourrait utiliser autant de variables qu'il v a d'âges différents dans la pyramide :
age0 = 691165 # moins de 1 an
agel = 710534 # entre 1 et 2 ans
age2 = 728579 # entre 2 et 3 ans
et ainsi de suite jusqu'à l’âge maximum.
Après tout, cela ne fait qu'un peu plus de cent variables. Là où les choses deviennent vraiment pénibles, c'est lorsque l'on veut demander à l'utilisateur du programme de saisir un âge, pour afficher ensuite le nombre de français ayant cet âge-là. On peut le faire,
mais au prix d'une interminable succession de comparaisons.
a = int(input(\"quel âge : \"))
if a == 0:
print (age0)
elif a == 1:
print (agei)
elif ...
Ce n'est pas raisonnable.
D'une part, il faudrait recommencer une telle série de comparaisons pour tout autre calcul (par exemple, pour calculer le nombre de français ayant un âge compris entre deux valeurs données par l'utilisateur). D'autre part, si on avait maintenant plusieurs milliers, voire
plusieurs millions d'informations, il deviendrait humainement impossjble de procéder ainsi.
Il faut une meilleure solution.
Un tableau permet de stocker plusieurs valeurs dans une seule variable et d'y accéder ensuite facilement.
En Python, on construit un tableau en énumérant ses valeurs entre crochets et séparées par des virgules.
>>> t = [2, 3, 5]
Dans le script IDLE Python taper l'instruction ci-dessous.
Ici, on à déclaré une variable t contenant un tableau. Ce tableau contient trois entiers. Les valeurs contenues dans un tableau sont ordonnée :
- la première valeur contenue dans le tableau est 2,
- la deuxième 3
- et la troisième 5.
On peut se représenter un tableau comme dos cases consécutives contenant des valeurs.
2 | 3 | 5 |
C‘est en effet ainsi qu'un tableau est organisé dans la mémoire de l’ordinateur ; ses valeurs y sont rangées consécutivement.
Pour accéder à une valeur contenue dans le tableau t, il faut utiliser la notation t[i] où i désigne le numéro de la case à laquelle on veut accéder.
Les cases sont numérotées à partir de zéro ( t[0] ). Ainsi, la valeur contenue dans là première case cst t[0].
>>> t[0]
Tester l'instruction ci-dessous.
On dit que 0 est l'indice de la preinière case.
1 l'indice de la deuxième case
ete.
La taille d’un tableau est son nombre de cases. On obtient la taille du tableau t avec l'opération len(t).
>>> len(t)
Les indices d'un tableau t prennent donc des valeurs entre 0 et len(t)-1.
On peut se représenter le tableau mentalement avec l'indice de chaque case indiqué juste au-dessus.
2 | 3 | 5 |
Mais il faut comprendre que seules les valeurs sont stockées dans la mémoire de l'ordinateur. Les indices, eux, n'ont pas besoin d’être matérialisés.
En effet, chaque case du tableau occupe une taille identique et on peut donc calculer facilement où se trouve la case dans la mémoire par une opération arithmétique.
Erreurs : Si on cherche à accéder à une case en dehors des limites du tableau, on obtient une erreur.
Tester l'instruction ci-dessous et indiquer le type d'erreur.
>>> t[3]
Mettre le résultat ici.
Si on reprend notre exemple de Ia pyramide des âges, on peut la représenter très par un tableau pda.
pda=[706382,716159,729139,749142,770897,795049,801336,818973,824266,844412,836610,841774,833484,
847250,828874,828224,825535,824243,830859,832135,778595,767419,738255,741493,731720,709814,
710229,747365,762740,783278,793756,805709,809462,824388,823154,817616,809113,860183,868514,
876362,830619,812560,815529,795012,818506,859407,905508,925828,921091,900389,888940,878137,
872944,891913,893796,901416,889289,857860,858184,852627,845836,827046,818270,809103,799407,
795066,776073,784280,760998,783527,766434,759622,739203,692884,518955,502516,483835,443448,
389310,397453,408011,390052,372609,362050,336284,325338,293641,280250,250255,226053,186015,160562,
132403,110466,89330,69801,53201,39728,29030,20035,21860]
Dans la case 0 du tableau vcus avez le nombre de personnes née en 2019.
Donc la case i du tableau pda. on trouve le nombre de français ayant exactement l'âge i+1 .
Maintenant, nous pouvons demander un âge à l'utilisateur et afficher le nombre de français ayant cet âge-là.
age = int(input(\"quel âge : \"))
age = age - 1 # nous sommes en 2020 ey le tableau commence à patyir de 2019
print(\"il y a\", pda[age], \"personnes ayant\", age, ans\")
Tester le code dans le file de l'IDLE python.
Tester le cpde pohr votre age et 50 ans.
","title":" "},{"edit":"Mettre le résultat ici.
Le contenu d'un tableau peut être modifié. Pour cela, on utilise une affectation, exactement comme on le ferait avec unc variable. Ainsi, on peut modifier le contenu de la seconde case du tableau t pour y remplacer la valeur 3 par la valeur 17.
t = [2, 3, 5]
t[1] = 17
On pout observer que cette modification a bien été effectuée en demandant à Python d'afficher le tableau t.
t = [2, 3, 5]
print(t)
t[1] = 17
print(t)
Comme on le voit, un tableau est affiché avec la même forme que celle qui permet. de le définir.
Si on reprend notre exemple de la pyramide des âges, on peut traduire une naissance en ajoutant 1 à la première case du tableau, c'est-à-dire à la
case d'indice 0.
pda=[706382,716159,729139,749142,770897,795049,801336,818973,824266,844412,836610,841774,833484,
847250,828874,828224,825535,824243,830859,832135,778595,767419,738255,741493,731720,709814,
710229,747365,762740,783278,793756,805709,809462,824388,823154,817616,809113,860183,868514,
876362,830619,812560,815529,795012,818506,859407,905508,925828,921091,900389,888940,878137,
872944,891913,893796,901416,889289,857860,858184,852627,845836,827046,818270,809103,799407,
795066,776073,784280,760998,783527,766434,759622,739203,692884,518955,502516,483835,443448,
389310,397453,408011,390052,372609,362050,336284,325338,293641,280250,250255,226053,186015,160562,
132403,110466,89330,69801,53201,39728,29030,20035,21860]
print(pda)
#on ajoute une naissance à l'année 0
pda[0] = pda[0] + 1
print(pda)
En première approximation, on peut donc voir les cases d'un tableau comme autant de variables qu'on peut modifier à loisir, qui seraient appclées ici pdal0], pdal1], etc.
Mais avec une différence essentielle : l'indice peut être
le résultat d'un calcul.
Toujours sur notre exemple de la pyramide des âges, supposons qne nous voulions calculer le nombre de français ayant entre 10 et 20 ans. On obtient ce nombre en additionnant dix valeurs contenues dans le tableau.
total = pda[9] + pda[10] + pda[11] + pda[12] + pda[13] + pda[14] \\
+ pda[15] + pda[16] + pda[17] + pda[18]
print(total)
C'est un peu long à écrire, mais cela reste faisable. Mais cela deviendrait vraiment pénible si la plage était plus grande ou encore saisie par l'utilisateur.
Les tableaux de Python différent des tableaux que l'on trouve dans les autres langages de programmation par
plusieurs aspects.
Tout d'abord. ils sont appelés listes dans la documentation de Python, ce qui est assez malheureux car une liste désigne en général une structure de donnés différente du tableau.
Ensuite, les tableaux de Python peuvent être agrandis où rétrécis du côté droit. c'est-à-dire du côté des indices les plus grands (avec des opérations append et pop que nous n'avons pas présentées), Cela les distingue des tableaux usuel où la taille est fixée une fois pour tontes à la création.
Enfin, accéder à nn tableau Python avec un indice négatif ne provoque pas nécessairement une erreur, contrairement à ce que l'on pourrait imaginer.
Python permet en effet d'accéder au dernier élément du tableau t avec t[-1], à son avant-dernier élément avec t[-2]. etc.
De manière générale, les indices -1 à —n peuvent être utilisés pour accéder à partir de la droite à un tableau de taille n. C'est parfois utile mais aussi dangereux : il suffit d'une petite erreur de calcul dans un programme pour ce retrouver avec un indice -1 plutôt que 0.
Par exemple, l'exécution du programme va alors se poursuivre sans signaler d'erreur.
Pour un tableau de taille n. seul un indice en dehors de l'intervalle [-n , n - 1] provoquera une erreur.
Nous avons délibérément choisi de nous en tenir uniquement au vocabulaire et notions usuels des tableaux tels qu'on les trouve dans dans les
langares usuelles.
Une meilleur solution consiste à utiliser une boucle pour parcourir les les cases du tableau concerné tout en accumulant le nombre total dans variable. On commence par introduire cette variable, en l'initialisant à zéro.
>>> total = 0
Puis on effectue une boucle for donnant successivement à la variable total toutes les valeurs entre 9 et 19 exclus, et on ajoute à chaque fois la valeur pda[i] à la variable total.
>>> for i in range(9, 19):
total += pda[i]
Le nom i donné à la variable importe peu. On aurait pu l'appeler age par exemple. Mais il est courant d'utiliser les noms i, j où k pour des variables qui vont être utilisées comme indices dans des tableaux.
total = 0
for i in range(9, 19):
total += pda[i]
print(total)
Tester le code ci-dessus et conclure.
On peut vérifier qu'après la boucle on a bien calculé dans la variable total la même valeur que celle obtenue précédemment avec notre addition de dix valeurs du tableau.
Erreurs. La possibilité d'accéder aux cases d’un tableau par des indices négatifs rend certaines erreurs difliciles à analyser. imaginons une fonction renvoyant le nombre d'occurrences de la valeur v entre les indices a
(inclus) et b (exclu) du tableau t.
def nb_occurrences(v, a, b, t):
nb = 0
for i in range(a, b):
if t[i] == v:
nb += 1
return nb
Supposons qu'un programmeur écrive
alors la séquence suivante.
t = [2, 1, 3, 1]
debut = -1
fin = len(t)
n = nb_occurrences(1, debut, fin, t)
print(n)
Tester le programme.
Que se passe-t-11?
Ici, le dernier élément sera compté deux fois, une pour
l'indice -1 et une pour l'indice 3, et on obtiendra le résultat de trois occurrences de la valeur 1 dans le tableau [2, 1, 3, 1].
Cette valeur erronée pour n est susceplible de provoquer une erreur plus tard dont il peut être compliqué de retrouver l'origine, à savoir ici une mauvaise définition de la variable début.
Mettre le résultat ici (code et figure).
Le programme 8 contient une version plus générale de ce calcul, où la plage d'âge est donnée par l'utilisateur.
Programme 8 — pyramide des âges
pda=[706382,716159,729139,749142,770897,795049,801336,818973,824266,844412,836610,841774,833484,
847250,828874,828224,825535,824243,830859,832135,778595,767419,738255,741493,731720,709814,
710229,747365,762740,783278,793756,805709,809462,824388,823154,817616,809113,860183,868514,
876362,830619,812560,815529,795012,818506,859407,905508,925828,921091,900389,888940,878137,
872944,891913,893796,901416,889289,857860,858184,852627,845836,827046,818270,809103,799407,
795066,776073,784280,760998,783527,766434,759622,739203,692884,518955,502516,483835,443448,
389310,397453,408011,390052,372609,362050,336284,325338,293641,280250,250255,226053,186015,160562,
132403,110466,89330,69801,53201,39728,29030,20035,21860]
age_min = int(input(\"âge minimum (inclus) : \"))
age_max = int(input(\"âge maximum (exclu) : \"))
total = 0
for age in range(age_min, age_max):
total += pda[age]
print (\"il y a\",total, \"personnes qui ont entre\", \\
age_min, \"et\", age_max, \"ans\")
Un cas particulier consiste à parcourir toutes les cases d’un tableau t avec une boucle for allant de 0 inclus à len(t) exclu.
On peut calculer ainsi la population française en janvier 2020 en parcourant tout notre tableau pda.
popu = 0
for i in range(0, len(pda)):
popu += pda[i]
print(popu)
Tester les programmes.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
Si on doit construire un tableau vraiment grand, par exemple de plusieurs centaines d'éléments, il devient difficile de le faire en énumérant tous ses éléments.
Fort heureusement, il existe uue opération dans le langage Python pour construire un tableau d’une taille arbitraire.
Elle s'utilise ainsi :
t = [0] * 1000
print(t)
On donne la taille du tableau après le symbole *, ici 1000, et entre crochets une valeur qui sera donnée à toutes les cases du tableau, ici 0.
On obtient donc ici un tableau t de taille 1000 dont toutes les cases contiennent pour l'instant la valeur 0. On peut notamment vérifier que le tableau ainsi construit à bien la taille 1000.
print(len(t))
Il faut bien comprendre que l'opération [0]*1000 n'implique pas une multiplication, même si elle utilise le même symbole *.
Il s'agit là d’une opération spécifique aux tableaux. Sa syntaxe n’est cependant pas liée au hasard;
elle est là pour suggérer l’idée que l'on «multiplie par 1000» un tableau d'une case contenant 0.
Une fois le tableau ainsi construit, on peut maintenant le remplir avec des valeurs de son choix, en utilisant des affectations.
Si par exemple on veut y stocker les carrés des 1000 premiers entiers, on peut le faire avec une boucle,
for i in range(0, 1000):
t[i] = i*i
print(t)
Nous verrons, plus tard dans l'année, une autre façon de construire un tel tableau.
Mettre le résultat ici (code et figure).
Il est possible de concaténer deux tableaux, c'est-à-dire de construire un nouveau tableau contenant, bout à bout, les éléments
des deux tableaux. On le fait avec l'opération +.
t1 = [8, 13, 21]
t2 = [34, 55]
print(t1)
print(t2)
t3 = t1 + t2 #concaténation de 2 tableau
print(t3)
Conune pour l'opération * utilisée pour la construction de tableaux, on réutilise ici un symbole arithmétique, mais il n'y a pas d’addition à proprement parler.
","title":"Concaténation de 2 tableaux"},{"edit":"Mettre le résultat ici (code et figure).
Les chaines de caractères offrent une certaine ressemblance avec les tableaux. En particulier. où peut obtenir la taille d'une chaine de caractères. c'est-à-dire son nombre de caractéres. avec l'opération len et accéder au n-ième caractère d'une chaine avec les crochets.
ch = \"bonjour\"
print(\"longueur de ch \",len(ch))
print(\"caractère en position 3 \",ch[2])
Comme ou le voit sur cet exemple, les caractères sont numérotés à partir de Zéro. comme dans un tableur. Qui peut également concaténer deux chaines avec + où encore construire une chaine contenant 10 répétitions
de ab avec “ab\" * 10.
En revanche, contrairement aus tableaux. les caractères d'une chaine ne peuvent pas être modifiés,
ch[2] = \"a\"
Tester les codes et conclure.
Mettre le résultat ici (code et figure).
Comme on l’a expliqué, une variable déclarée avec
x = 1
peut être représentée par une boîte appelée x contenant la valeur 1. x
x
1 |
Lorsque l'on modifie la valeur de la variable x. par exemple avec l'affectation x = x + 1, la valeur 1 a été remplacée par la valeur 2.
x
2 |
Lorsque l'on affecte à une nouvelle variable y la valeur de x, avec l'instruction y = x. une seconde boîte est créée, qui reçoit la valeur de x, c'est-à-dire 2.
x
y
2 |
2 |
On à maintenant deux variables indépendantes. En particulier, modifier la variable y. par exemple avec y=3, n'a pas d'effet sur la variable x.
y
3 |
Tester le code :
x = 1
print(\"x=\",x)
x = x + 1
print(\"x=\",x)
y = x
print(\"x=\",x,\"y=\",y)
y = 3
print(\"x=\",x,\"y=\",y)
Si nous en reparlons ici, c'est parce la situation devient un peu plus subtile lorsque les variable contiennent des tableaux.
On pourrait penser qu'une variable t contenant un tableau, par exemple initialisée avec t = [1, 2, 3], désigne trois boîtes plutôt qu'une seule, ce que l'on pourrait se représenter ainsi.
t
1 | 2 | 3 |
Malheureusement, cette vision des choses est incorrecte. En réalité, la valeur affectée à la variable t est l’adresse mémoire de l'espace alloué au tableau.
La valeur précise de cette adresse importe peu et on peut donc se le représenter ainsi :
t
→
a.m ; adresse mémoire
a.m. |
1 | 2 | 3 |
La flèche symbolise ici le fait que la variable t contient une valeur qui désigne l'emplacement mémoire où se trouve le tableau. En première approximation, cette distinction que nous venons de faire paraît inutile. Après tout, on visualise aussi bien l'accès à t[1} ou encore l'affectation t[2] = 7 avec notre premier schéma.
Mais considérons maintenant la création d’un nouveau tableau avec u = t. Comme avec les variables entières x et y plus haut, la variable u reçoit la valeur de la variable t. Maïs comme la valeur est ici une adresse mémoire, on se retrouve avec la situation suivante :
Autrement dit, les deux variables t et u désignent le même tableau. En particulier, toute modification du contenu du tableau t sera visible dans le tableau u.
Ainsi, si on exécute l'instruction t[2] = 7, on se retrouve avec la situation suivante.
On à donc également modifié la valeur de u[2]. On peut le vérifier dans Python.
t = [1, 2, 3]
u = t
print(\"t=\",t)
print(\"u=\",u)
t[2] = 7
print(\"t=\",t)
print(\"u=\",u)
Les tableaux t et u ne sont pas pour autant destinés à rester identiques éternellement. Rien ne nous empêche par exemple d'affecter un nouveau tableau à u, par exemple avec l'instruction u = [4, 5, 6], pour se retrouver alors dans la situation suivante.
t
→
a.m ; adresse
a.m. |
1 | 2 | 7 |
u
→
a.m ; adresse
a.m. |
4 | 5 | 6 |
Même s’il est peu fréquent, voire peu recommandé, de se retrouver ainsi avec deux variables qui désignent le même tableau, il est fondamental d’avoir compris cet aspect-là du fonctionnement de Python. C'est en particulier nécessaire lorsque l’on utilise des fonctions qui reçoivent des tableaux en argument, ce que nous allons expliquer maintenant.
","title":"Tableaux et variables"},{"edit":"Mettre le résultat ici (code et figure).
Illustrons le passage d'un tableau en argument d’une fonction à l'aide d'un petit exemple. Considérons une variable entière x et un tableau t, initialisées ainsi :
x = 1
t = [1, 2, 3]
Conformément à ce que nous avons expliqué précédemment, nous pouvons l'illustrer de la manière suivante :
x
t
→
1 |
a.m. |
1 | 2 | 3 |
Définissons maintenant une fonction f prenant deux arguments, appelés a et b. La variable a est supposée recevoir un entier et la variable b un tableau.
def f(a, b):
a=a+1
b[a] = 7
Comme on le voit. la fonction f commence par incrémenter la variable x puis modifie le tableau b à l'indice 2. Considérons maintenant l'appel de fonction
f(x, t). Juste après l’appel, on à la situation suivante où deux nouvelles variables a et b :
Ceci est conforme à ce qui à été expliqué dans la séquence sur les fonctions, ainsi qu'à ce qui à été expliqué plus haut sur la valeur d'une variable désignant un tableau. Une fois que les deux instructions qui constitue le fonction f ont été exécutées, on se retrouve donc dans la situation suivante :
La variable a a été incrémentée. et contient maintenant la valeur 2, et la valeur 7 à été affectée à la case d'indice 2 du tableau b. Une fois l'appel de fonction terminé, les variables a et b disparaissent et on se retrouve avec les valours suivantes pour les variables x et t.
x
t
→
1 |
a.m. |
1 | 2 | 7 |
def f(a, b):
print(\"2.\",\"a=\",a,\" b=\",b)
a=a+i
b[a] = 7
print(\"3.\",\"a=\",a,\" b=\",b)
x = 1
t = [1,2,3]
print(\"1.\",\"x=\",x,\" t=\",t)
f(x,t)
print(\"4.\",\"x=\",x,\" t=\",t)
Tester le programme ci-dessus et conclure.
Comme on le constate, le contenu du tableau t à été modifié par la fonction f, mais pas le contenu de la variable x.
Nous venons d'illustrer ici quelque chose d'important :
Une fonction peut modifier le contenu d'un tableau qui lui est passé en argument.
Mettre le résultat ici (code et figure).
De même qu'une fonction peut recevoir un tableau en argument, une fonction peut renvoyer un tableau comme résultat. Là encore, il convient de bien comprendre ce qui se passe, en l'illustrant de façon précise. Considérons la fonction suivante qui reçoit un entier n en argument et renvoie un tableau de taille n contenant les carrés des n premiers entiers.
def carres(n):
t= [0] * n
for i in range(n):
t[i] = i * i
return t
print(carres(5))
Supposons que l’on appelle cette fonction avec 4 comme argument et que l'on stocke le résultat dans une variable c, c'est-à-dire que l'on exécute l'instruction c = carres(4). Au moment où l'exécution de la fonction atteint l'instruction return. on est dans la situation suivante :
n
t
→
4 |
a.m. |
0 | 1 | 4 | 9 |
La valeur renvoyée par la fonction est la valeur de la variable t, c'est-à-dire l'adresse du tableau contenant les quatre carrés. C'est cette valeur qui est stockée dans la variable c juste après l'appel.
c
→
a.m. |
0 | 1 | 4 | 9 |
Autrement dit, la variable locale t qui à servi à la construction du tableau n'a pas survécu à l'appel mais en revanche la zone mémoire contenant ici quatre entiers à survécu à l'appel.
Mettre le résultat ici (code et figure).
Programme 8 — pyramide des âges
pda=[706382,716159,729139,749142,770897,795049,801336,818973,824266,844412,836610,841774,833484,
847250,828874,828224,825535,824243,830859,832135,778595,767419,738255,741493,731720,709814,
710229,747365,762740,783278,793756,805709,809462,824388,823154,817616,809113,860183,868514,
876362,830619,812560,815529,795012,818506,859407,905508,925828,921091,900389,888940,878137,
872944,891913,893796,901416,889289,857860,858184,852627,845836,827046,818270,809103,799407,
795066,776073,784280,760998,783527,766434,759622,739203,692884,518955,502516,483835,443448,
389310,397453,408011,390052,372609,362050,336284,325338,293641,280250,250255,226053,186015,160562,
132403,110466,89330,69801,53201,39728,29030,20035,21860]
age_min = int(input(\"âge minimum (inclus) : \"))
age_max = int(input(\"âge maximum (exclu) : \"))
total = 0
for age in range(age_min, age_max):
total += pda[age]
print (\"il y a\",total, \"personnes qui ont entre\", \\
age_min, \"et\", age_max, \"ans\")
Le prograunme 8 n’est pas très robuste. En effet, si
l'utilisateur donne pour age_max une valeur plus grande que 106 (la taille du tableau pda), alors le programme va échouer suite à un accès en dehors
des limites du tableau.
Améliorer ce programme pour que l'utilisateur puisse
spécifier une valcur de age_max arbitrairement grande, sans pour autant que le programme n’échoue.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
Il suffit de stopper le parcours au minimum de age_max et len(pda).
age_max = min(age_max, len(pda))
ou
if age_max > len(pda) :
age_max = len(pda)
Écrire une fonction occurrences(v, t) qui renvoie le nombre d'occurrences de la valeur v dans le tableau t.
Tester avec le code suivant :
t1 = [1,5,3,6,3,7,3,2,1,3]
x = 3
print(occurrences(x,t1))
Résultat : 4
Mettre le résultat ici (code et figure).
On utilise une variable occ comme accumulateur qu’on
incrémente à chaque occurrence de v trouvée.
def occurrences(v, t):
occ = 0
for i in range(len(t)):
if t[i] == v:
occ += 1
return occ
t1 = [1,5,3,6,3,7,3,2,1,3]
x = 3
print(occurrences(x,t1))
Écrire un programme qui construit un tableau de 100 entiers tirés au hasard entre 1 et 1000, puis l'affiche.
Mettre le résultat ici (code et figure).
Le tableau est créé avec une valeur quelconque dans
ses cases, ici 0.
from random import randint
tab = [0] * 100 # un tableau de taille 100
for i in range(0, 100):
tab[i] = randint(1, 1000)
print (tab)
Compléter le programme précédent pour déterminer l'élément maximum de ce tablean et l'afficher.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
from random import randint
tab = [0] * 100 # un tableau de taille 100
for i in range(0, 100):
tab[i] = randint(1, 1000)
print (tab)
#determination de l'élément max
maximum = 0
for i in range(0, 100):
if tab[i] > maximum:
maximum = tab[i]
print(maximum)
Ecrire un programme qui tire au hasard mille entiers entre 1 et 10 et affiche ensuite le nombre de fois que chaque nombre a été tiré. Relancer.
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
from random import randint
#On utilise un tableau de taille 11 dans lequel on stocke
#le nombre de fois que chaque nombre à été tiré. La case 0 de ce tableau n’est
#pas utilisée. Initialement, tous les compteurs sont à zéro.
hist = [0] * 11
#Puis on répète mille fois le tirage d’un entier n entre 1 et 10 (inclus). À
#chaque fois, on augmente son compteur d’une unité.
for k in range(0, 1000):
n = randint(1, 10) # entre 1 et 10 inclus
hist[n] = hist[n] + 1
#Enfin, on affiche les résultats.
for v in range(1, 11):
print(v, \"apparaît\", hist[v], \"fois\")
En mathématiques, la très célèbre suite de Fibonacci est une séquence infinie d'entiers définie de la façon suivante :
on part des deux entiers 0 et 1
puis on construit à chaque fois l'entier suivant comme la somme des deux entier précédents.
0 , 1 , 1 (0+1) , 2 (1+1) , 3 (1+2) , 5 (2+3) , ...
Écrire un programme qui construit puis affiche un tableau contenant les 30 premiers termes de la suite. Le dernier élément de ce tableau doit être 514229.
Mettre le résultat ici (code et figure).
n = 30
fib = [0] * n
fib[0] = 0
fib[1] = 1
for i in range(2, n):
fib[i] = fib[i-2] + fib[i-1]
print(fib)
Écrire une fonction copie(t) qui prend en paramètre un tableau t et renvoie une copie de ce tableau.
Quelle expérience peut-on faire pour s'assurer qu'on ne s'est pas trompé?
Exemple :
t1 = [0, 1, 2, 3]
t2 = copie(t1)
print(t1)
print(t2)
Mettre le résultat ici (code et figure).
On crée un nouveau tableau de même longueur dans
lequel on place un à un les éléments de t.
def copie(t):
r = [0] * len(t)
for i in range(len(t)):
r[i] = t[i]
return r
t1 = [0, 1, 2, 3]
t2 = copie(t1)
print(t1)
print(t2)
On peut vérifier ensuite qu'un tableau et sa copie son bien indépendants :
aucune modification de l’un n’est répercuté sur l’autre.
t1[0] = 4
t2[1] = 7
print(t1)
print(t2)
Cette expérience n’aurait pas fonctionné avec la définition t2 = t1.
Ecrire une fonetion ajout(v, €) qui crée un nouveau tableau contenant d'ahord tous les éléments de t puis v.
Exemple :
x = 4
t1 = [0, 1, 2, 3]
t2 = ajout(x,t1)
print(t2)
Mettre le résultat ici (code et figure).
def ajoute(v, t):
r = [v] * (len(t) + 1)
for i in range(len(t)):
r[i] = t[i]
return r
ou
def ajoute(v, t):
r = [0] * (len(t) + 1)
for i in range(len(t)):
r[i] = t[i]
r[len(t)]=v
return r
Ecrire une fonction concatenation(t1, t2) qui crée un nouveau tableau contenant, dans l’ordre, tous les éléments de t1 puis tous les éléments de t2.
Exemple :
t1 = [0, 1, 2, 3]
t2 = [4, 5, 6, 7]
t3 = concatenation(t1,t2)
print(t3)
Résultat :
[0, 1, 2, 3, 4, 5, 6, 7]
Mettre le résultat ici (code et figure).
def concatenation(ti, t2):
r = [0] * (len(t1) + len(t2))
for i in range(len(t1)):
r[i] = t1[i]
for i in range(len(t2)):
r[len(t1) + i] = t2[i]
return r
Ecrire une fonction tableau_aleatoire(n, a, b) qui renvoie
un tableau de taille n contenant des entiers tirés au hasard entre a et b.
Exemple ;
t1 = tableau_aleatoire(20,1,6)
print(t1)
Résultat :
[1, 2, 1, 4, 2, 3, 5, 1, 4, 2, 2, 3, 1, 3, 2, 4, 3, 3, 6, 2]
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
from random import randint
def tableau_aleatoire(n, a, b):
tab = [0] * n
for i in range(0, n):
tab[i] = randint(a, b)
return tab
Ecrire une fonction tableau_croissant(n) qui renvoie un tableau de taille n contenant des entiers tirés au hasard et ayant la propriété d'être trié par ordre croissant.
Pour faire cela, on pourrait utiliser l'exercice précédent pour construire un tableau aléatoire, puis le trier avec un algorithmce de tri. Il y à néanmoins une façon plus simple de procéder, consistant à remplir le tableau de gauche à droite en ajoutant à chaque fois un entier positif on nul à l'élément précédent.
Ainsi, le tableau est trié par construction.
Exemple :
t1 = tableau_croissant(20)
print(t1)
Résultat :
[0, 4, 5, 9, 11, 18, 23, 31, 33, 34, 38, 40, 46, 51, 53, 61, 69, 75, 76, 82]
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
On suit l’indication, en tirant à chaque fois un nombre
aléatoire (entre 0 et 10) pour l’ajouter à l'élément précédent.
from random import randint
def tableau_croissant(n):
tab = [0] * n
for i in range(1, n):
tab[i] = tab[i-1] + randint(0, 10)
return tab
Écrire ne fonction echange(tab, i, j) qui échange dans le
tableau tab les éléments aux indices i et j.
Exemple :
t1 = [1,2,3,4,5]
print(t1)
echange(t1,1,3)
print(t1)
Résultat :
[1, 2, 3, 4, 5]
[1, 4, 3, 2, 5]
Mettre le résultat ici (code et figure).
On utilise pour cela une variable temporaire, appelée
ici tmp.
def echange(tab, i, j):
tmp = tab[i]
tab[i] = tab[j]
tab[j] = tmp
Écrire une fonction somme(tab) qui calcule et renvoie la somme des éléments d'un tableau d'entiers. En déduire unc fonction moyenne(tab) qui calcule et renvoie la moyenne des éléments du tableau tab, supposé non
vide.
Exemple :
t1 = [1,2,3,4,5]
print(\"somme de t1 :\",somme(t1))
print(\"moyenne de t1 :\",moyenne(t1))
Résultat :
somme de t1 : 15
moyenne de t1 : 3.0
Mettre le résultat ici (code et figure).
def somme(tab) :
s = 0
for i in range(len(tab)):
s += tab[i]
return s
def moyenne(tab) :
s = 0
for i in range(len(tab)):
s += tab[i]
moy = s / len(tab)
return moy
Ecrire une fonction produit(tab) qui calcule et renvoie le
produit des éléments d'un tableau d'entiers. Si le tableau contient 0, la fonction devra renvoyer 0 sans terminer le calcul.
Exemple :
t1 = [1,2,3,4,5]
print(\"produit t1 :\",produit(t1))
t1 = [1,2,0,4,5]
print(\"produit t1 :\",produit(t1))
Résultat :
120
0
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
def produit(tab):
p=1
for i in range(len(tab)):
p *= tab[i]
if p == 0: return 0
return p
Écrire une fonction miroir(tab) qui reçoit un tableau en argument et le modifie pour échanger le premier élément avec le dernier, le second avec l'avant-dernier, etc.
Dit autrement, on remplace le tableau par son image miroir. On pourra se servir de la fonction echange de l'exercice précédent.
Exemple :
t1 = [1,2,3,4,5]
print(t1)
miroir(t1)
print(\"miroir:\",t1)
Résultat :
[1, 2, 3, 4, 5]
miroir: [5, 4, 3, 2, 1]
Mettre le résultat ici (code et figure).
En se servant de la fonction echange de l’exercice
précédent, comme suggéré, le code est assez court, mais pas complètement évident pour autant.
def echange(tab, i, j):
tmp = tab[i]
tab[i] = tab[j]
tab[j] = tmp
def miroir(tab):
n = len(tab)
for i in range(0, n // 2):
echange(tab, i, n - 1 - i)
D'une part, il faut bien identifier n-1-i comme étant l’élément miroir de l'élément i. D'autre part, il faut se persuader que la boucle for parcourt le bon intervalle. Si le tableau à une taille paire, c’est-à-dire n = 2^k, alors la boucle for va de 0 inclus à k exclu. Elle parcourt donc bien la premièreDUIULIUIS UES EXEICILES ri
n = 2k +1, la boucle for va toujours de 0 inclus à k exclu. Elle parcourt
donc la première moitié du tableau, à l'exclusion de l'élément central. Mais
comme il n’y a pas lieu de modifier l’élément central, c’est correct.
Pour mélanger les éléments d'un tableau aléatoirement, il existe un algorithme très simple qui procède ainsi :
on parcourt le tableau de la gauche vers la droite et, pour chaque élément à l'indice i, on l'échange avec un élément situé à un indice tiré aléatoirement entre 0 et i (inclus). Écrire une fonetion melange(tab) qui réalise cet algorithme. On pourra sc resservir de la fonction echange de l'exercice précédent. (Cet algorithme s'appelle le mélange de Kruth.)
Exemple :
t1 = [1,2,3,4,5]
print(t1)
melange(t1)
print(\"t1 mélangé :\",t1)
Résultat :
[1, 2, 3, 4, 5]
t1 mélangé : [2, 5, 3, 1, 4]
Mettre le résultat ici (code et figure).
On suit l'algorithme donné :
def echange(tab, i, j):
tmp = tab[i]
tab[i] = tab[j]
tab[j] = tmp
def melange(tab) :
for i in range(1, len(tab)):
echange(tab, i, randint(0, i))
On note qu’on démarre à l'indice 1, car il est inutile d'échanger le premier élément avec lui-même.
Écrire une fonction prefixe(tab1, tab2) qui renvoie True si le tableau tab1 est un préfixe du tableau tab2. c'est-à-dire si le tablean tab2 commence par les éléments du tableau tab1 dans le même ordre.
Exemple :
t1 = [1,2,3]
t2 = [1,2,3,4,5]
print(prefixe(t1, t2))
t3 = [1,2,8,4,5]
print(prefixe(t1, t3))
t4 = [1,2]
print(prefixe(t1, t4))
Résultat :
True
False
False
Mettre le résultat ici (code et figure).
def prefixe(tab1, tab2):
for i in range(len(tab1)):
if i >= len(tab2) or tab1[i] != tab2[i]:
return False
return True
Écrire une fonction suffixe(tab1, tab2) qui renvoie True si le tableau tab1 est en suffixe du tableau tab2, c'est-à-dire si le tableau tab2 termine par les éléments du tableau tab1 dans le même ordre.
Exemple :
t1 = [3,4,5]
t2 = [1,2,3,4,5]
print(suffixe(t1, t2))
t3 = [1,2,3,4,6]
print(suffixe(t1, t3))
t4 = [3,4]
print(suffixe(t1, t4))
Résultat :
True
False
False
Mettre le résultat ici (code et figure).
Même stratégie qu’à l'exercice précédent, en partant
de la fin de chacun des tableaux.
def suffixe(tab1, tab2):
n1 = len(tab1) - 1
n2 = len(tab2) - 1
for i in range(len(tab1)):
if i > n2 or tab1[n1 - i] != tab2[n2 - i]:
return False
return True
Écrire une fonction hamming(tab1, tab2) qui prend en paramètres deux tableaux, que l'on supposera de la même taille, el qui renvoie le nombre d'indices auxquels les deux tableaux différent.
Exemple :
t1 = [1,2,3,4,5]
t2 = [1,4,3,5,5]
print(hamming(t1, t2))
Résultat :
2
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
def hamming(tab1, tab2):
d=0
for i in range(len(tab1)):
if tab1[i] != tab2[i]:
d += 1
return d
Ecrire une fonction qui donne le nombre de jour de chaque mois. On utilisera un tableau pour stocké le nombre de jours de chaque mois.
Exemple :
print(nbjoursmois(2020, 2))
print(nbjoursmois(2020, 4))
Résultat :
29
30
","title":"Exercice"},{"edit":"Mettre le résultat ici (code et figure).
On fait un cas particulier pour le mois de février, puis
on utilise un tableau comme suggéré.
def nbjoursmois(a, m):
if m == 2 and a%4==0 :
return 29
t = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
return t[m - 1]
- Détails
- Écrit par : Richard GAUTHIER
- Clics : 1874
[[{"title":"Python - Programmation fonctionnelle","posi":0},{"text":"
"},{"text":""}],[{"text":"
","title":" "},{"edit":"
"}],[{"text":"
","title":" "},{"edit":"
"}],[{"text":"
","title":"Fonctions passées en arguments"},{"edit":""}],[{"text":"
","title":" "},{"edit":""}],[{"text":"
","title":"Fonction anonyme"},{"edit":""}],[{"text":"
"}],[{"title":"Fonctions renvoyées comme résultats"},{"text":"
"},{"edit":"
"}],[{"title":" Structures de données immuables"},{"text":"
"},{"edit":"
"}],[{"text":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"},{"solution":"
"}],[{"text":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"}],[{"text":"
","title":"Exercice"},{"edit":"
"},{"solution":"
"},{"solution":"
"},{"solution":"
"},{"solution":"
"},{"solution":""},{"solution":"
"},{"solution":"
"},{"solution":"
"},{"solution":"
"}]]
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
B 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.
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.
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ératif | Orienté objets | Fonctionnel |
Concurrent | Événementiel | Orienté requêtes |
Orienté contraintes | Synchrone | Logique |
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.
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.
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.
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.
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":"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).
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.
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 a∩b 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":"É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))
Mettre le résultat ici (code et figure).
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
É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))
Mettre le résultat ici (code et figure).
#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]
É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.
Mettre le résultat ici (code et figure).
def calcul2(f, op, t):
v = f(t[0])
for i in range(1, len(t)):
v = op(v, f(t[i]))
return v
Pour construire la chaîne demandée, il suffit de faire
print(calcu12((lambda x: str(x)), (lambda x, y: x+\", \"+y), t))
É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).
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.
É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.
Mettre le résultat ici (code et figure).
Le test s'écrit aussi simplement que ceci :
assert à | b == (a - b) | (b - a) | (a & b)
É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
Mettre le résultat ici (code et figure).
from time import sleep
def repeter_delai(f, n, t):
for i in range(n):
print(f(i))
sleep(t)
É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
Mettre le résultat ici (code et figure).
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))
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.
Mettre le résultat ici (code et figure).
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é.
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()
Mettre le résultat ici (code et figure).
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)
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()
Mettre le résultat ici (code et figure).
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).
#Partie 1.
# Question 1.1
a = point(0, 0)
b = point(1, 2)
c = point(-5, 18)
d = point(42, 37)
# 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)
# Question 1.3
t1 = triangle(a,b,c)
t2 = triangle(b,c,d)
# Question 1.4
t3 = deplacer_triangle(t1, -1, -1)
t4 = deplacer_triangle(t2, 2, 3)
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.2a = point(0, 0)
b = point(1, 2)
c = point(-5, 18)
d = point(42, 37)
# Question 2.3
t1 = triangle(a, b, c)
t2 = triangle(b, c, d)
# Question 2.4
ti.deplacer(-1, -1)
t2.deplacer(2, 3)
#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.
- Détails
- Écrit par : Richard GAUTHIER
- Clics : 1429