TD : Classes et Interfaces


Exercice 1 : Satellites


Q1. Définir une classe Satellite qui permette d'instancier des objets simulant des satellites artificiels lancés dans l'espace, autour de la terre. Le constructeur de cette classe initialisera les attributs d'instance ci-dessous :

  • L'attribut nom de type str, représentant le nom du satellite
  • L'attribut masse de type int ou float, représentant la masse du satellite en $kg$, ayant pour valeur par défaut 250
  • L'attribut vitesse de type int ou float, représentant la vitesse du satellite en $m.s^{-1}$, ayant pour valeur par défaut 0

Q2. Instancier les trois satellites suivants puis vérifier en console les valeurs de leurs attributs :

  • Satellite s1 portant le nom de "Sirius"
  • Satellite s2 ayant pour nom "Arcturus" et pour masse 100 $kg$
  • Satellite s3 ayant pour nom "Procyon", pour masse 150 $kg$ et pour vitesse 20 $m.s^{-1}$

Q3. Créer les trois méthodes infos, __repr__ et __str__ pour qu'elles produisent les affichages donnés en exemple.
Exemple :

>>> s3.infos()
'Le satellite Procyon (masse : 150 kg) a une vitesse de 20 m/s'
>>> s3
Nom : Procyon
Masse : 150 kg
Vitesse : 20 m/s
>>> print(s3)
Satellite Procyon : | Masse -> 150 kg | Vitesse -> 20 m/s

Q4. Tester les deux dernières instructions de la question 3 :

  • En commentant la méthode spéciale __repr__
  • En commentant la méthode spéciale __str__

On expliquera dans chaque cas les affichages obtenus.

Q5. On rappelle que l'énergie cinétique se calcule à l'aide de la formule : $E_{c} = \frac{1}{2} m v^{2}$
Définir une méthode energie qui renvoie la valeur de l'énergie cinétique de l'instance.

Q6. On rappelle que la variation $\Delta v$ subie par un objet de masse $m$ soumis à l'action d'une force $F$ pendant un temps $t$ vaut : $\Delta v = \frac{F \times t}{m}$. Par exemple, un satellite de 300 $kg$ qui subit une force de 600 $N$ pendant 10 secondes voit sa vitesse augmenter (ou diminuer) de 20 $m.s^{-1}$.
Définir une méthode impulsion qui :

  • Prend en paramètre la valeur de la force, force, en $N$ et la durée d'application de cette force, duree, en $s$
  • Modifie la vitesse du satellite en conséquence
In [ ]:
class Satellite:
    def __init__(self, nom, masse: int=250, vitesse: int=0):
        self.nom = nom
        self.masse = masse
        self.vitesse = vitesse
    
    def infos(self) -> str:
        return f'Le satellite {self.nom} (masse : {self.masse} kg) a une vitesse de {self.vitesse} m/s'
    
    def __repr__(self) -> str:
        return f'Nom : {self.nom}\nMasse : {self.masse} kg\nVitesse : {self.vitesse} m/s'
    
    def __str__(self) -> str:
        return f'Satellite {self.nom} : | Masse -> {self.masse} kg | Vitesse -> {self.vitesse} m/s'
    
    def energie(self) -> int:
        return int((self.masse * self.vitesse**2)/2)
    
    def impulsion(self, force: int, duree: int):
        self.vitesse += int((force * duree) / self.masse)
In [ ]:
s3 = Satellite('Procyon', masse=150, vitesse=20)
#s3.infos()
#s3
print(s3)
In [ ]:
s = Satellite('Zoé', masse=250, vitesse=10)
print(s.infos())
s.impulsion(500, 15)
print(s.infos())
print(f'Energie cinétique : {s.energie()} J')
s.impulsion(500, 15)
print(s.infos())
print(f'Energie cinétique : {s.energie()} J')

Exercice 2 : Comptes bancaires


Q1. Définir une classe CompteBancaire permettant d'instancier des objets représentant des comptes.
Le constructeur de cette classe initialisera deux attributs :

  • L'attribut numero, de type str, représentant le numéro de compte
  • L'attribut solde, de type float

Q2. Créer les méthodes credit et debit permettant respectivement d'ajouter et de retirer une certaine somme. Ces méthodes possèderont deux paramètres :

  • Le paramètre montant, de type float, correspondant à la somme à ajouter ou à débiter
  • Le paramètre description, de type str, qui précise le type d'opération.
    Ce paramètre sera initialisé par défaut à "Type de crédit/débit non précisé"

Q3. Créer la méthode info_solde qui renvoie la chaîne de caractères suivante :

'Le solde du compte n°{numéro du compte} est de {valeur du solde} €.'

Q4. Dans cette question, on souhaite générer un fichier au format csv représentant un relevé du compte.
Ce fichier devra posséder les quatre champs suivants : "Description", "Crédit", "Débit" et "Solde".

  • Créer un nouvel attribut d'instance histo et l'initialiser à la valeur 'Solde initial,,,{valeur du solde}\n'.
  • Compléter les méthodes credit et debit pour ajouter l'enregistrement correspondant à chaque opération.
  • Créer la méthode d'instance genere_historique qui crée le fichier releve_{noméro du compte}.csv comme indiqué dans l'exemple ci-dessous.

Exemple d'utilisation :

>>> compte = CompteBancaire('15667', 800)
>>> compte.credit(350, 'Virement CAF')
>>> compte.debit(200, 'Paiement CB')
>>> compte.info_solde()
Le solde du compte n°15667 est de 950.00 .
>>> compte.credit(500)
>>> compte.debit(15.75)
>>> compte.info_solde()
Le solde du compte n°15667 est de 1434.25 .
>>> compte.credit(1786.84, 'Versement salaire')
>>> compte.debit(60, 'Retrait DAB')
>>> compte.info_solde()
Le solde du compte n°15667 est de 3161.09 .
>>> compte.genere_historique()
td_classes_exo2.JPG
In [ ]:
class CompteBancaire:
    def __init__(self, numero: int, solde: float):
        self.numero = numero
        self.solde = solde
        self.histo = f'Solde initial,,,{self.solde:.2f}\n'
        
    def credit(self, montant: float, description: str = 'Type de crédit non précisé'):
        self.solde += montant
        self.histo += f'{description},{montant:.2f},,\n'
        
    def debit(self, montant: float, description: str = 'Type de débit non précisé'):
        self.solde -= montant
        self.histo += f'{description},,{montant:.2f},\n'
        
    def info_solde(self) -> str:
        return f'Le solde du compte n°{self.numero} est de {self.solde:.2f} €.'
    
    def genere_historique(self):
        with open('releve_'+ self.numero +'.csv', 'w', encoding='utf-8') as f:
            contenu = 'Description,Crédit,Débit,Solde\n'
            contenu += self.histo
            contenu += f'Nouveau solde,,,{self.solde:.2f}\n'
            f.write(contenu)
In [ ]:
compte = CompteBancaire('15667', 800)
compte.credit(350, 'Virement CAF')
compte.debit(200, 'Paiement CB')
print(compte.info_solde())
compte.credit(500)
compte.debit(15.75)
print(compte.info_solde())
compte.credit(1786.84, 'Versement salaire')
compte.debit(60, 'Retrait DAB')
print(compte.info_solde())
compte.genere_historique()

Exercice 3 : Tableau périodique des éléments - Dictionnaires et classes


Dans cet exercice, nous allons utiliser les classes pour représenter les éléments du tableau de Mendeleiev et le tableau périodique lui-même.
On placera le fichier tableau_mendeleiev.csv dans le répertoire courant.

tableau_mendeleiev.JPG

Partie A : La classe Element


Q1. Créer la fonction csv_to_table qui :

  • Prend en argument le nom d'un fichier csv nom_fichier
  • Renvoie la liste des enregistrements sous forme de liste de dictionnaires

Utiliser cette fonction pour créer la liste de dictionnaires table, représentant le tableau périodique, à partir du fichier tableau_mendeleiev.csv.

In [ ]:
import csv

def csv_to_table(nom_fichier):
    with open(nom_fichier, 'r', encoding='utf-8') as f:
        reader = csv.DictReader(f, delimiter=',')
        res = [dict(ligne) for ligne in reader]
    return res

table = csv_to_table('tableau_mendeleiev.csv')
table

Q2. Créer la classe Element permettant d'instancier un élément du tableau périodique.
Le constructeur de cette classe initialisera les attributs d'instances suivants à partir d'un enregistrement de la liste table :

  • L'attribut nom, de type str, qui prendra la valeur du champ Nom
  • L'attribut symbole, de type str, qui prendra la valeur du champ Symbole
  • L'attribut numero_atomique, de type int, qui prendra la valeur du champ Numéro atomique
  • L'attribut t_fusion, de type float, qui prendra la valeur du champ Température de fusion (en K)
    Si la température de fusion n'est pas renseignée, t_fusion prendra la valeur None
  • L'attribut t_ebullition, de type float, qui prendra la valeur du champ Température d'ébullition (en K)
    Si la température d'ébullition n'est pas renseignée, t_ebullition prendra la valeur None

Q3. Définir la méthode spéciale __repr__ pour qu'elle renvoie la chaîne de caractères ci-dessous :

Symbole : {symbole}
Nom : {nom}
Numéro atomique : {numéro atomique}
Masse atomique : {masse atomique} u
Température de fusion : {température fusion} K
Température d'ébullition : {température ébullition} K

Q4 Définir la méthode d'instance nb_neutrons pour qu'elle renvoie le nombre de neutrons de l'élément.
Le nombre de neutrons se calcule en soustrayant le numéro atomique à la masse atomique arrondie à l'unité.

Q5. Définir les méthodes d'instances t_fusion_degre et t_ebullition_degre qui renvoient respectivement les températures de fusion et d'ébullition d'un élément en degré Celsius, arrondies à deux décimales. On rappelle que $T_{°C} = T_{K} - 273.15$.
Si la température en $K$ n'est pas renseignée, ces méthodes devront renvoyer None.

Exemple d'utilisation :

>>> e1 = Element(table[0])
>>> e2 = Element(table[16])
>>> print(e1)
Symbole : H
Nom : Hydrogène
Numéro atomique : 1
Masse atomique : 1.00794 u
Température de fusion : None K
Température d'ébullition : None K
>>> print(e2)
Symbole : Cl
Nom : Chlore
Numéro atomique : 17
Masse atomique : 35.453 u
Température de fusion : 171.65 K
Température d'ébullition : 239.11 K
>>> print(f'Nombre de neutrons de {e1.symbole} : {e1.nb_neutrons()}')
Nombre de neutrons de H : 0
>>> print(f'Nombre de neutrons de {e2.symbole} : {e2.nb_neutrons()}')
Nombre de neutrons de Cl : 18
>>> print(f'Température de fusion de {e1.symbole} : {e1.t_fusion_degre()}')
Température de fusion de H : None
>>> print(f'Température de ébullition de {e2.symbole} : {e2.t_ebullition_degre()}')
Température de ébullition de Cl : -34.04
In [ ]:
class Element:
    
    def __init__(self, e: dict):
        self.nom = e['Nom']
        self.symbole = e['Symbole']
        self.numero_atomique = int(e['Numéro atomique'])
        self.masse_atomique = float(e['Masse atomique (en u)'])
        if e['Température de fusion (en K)'] != '':
            self.t_fusion = float(e['Température de fusion (en K)'])
        else:
            self.t_fusion = None
        if e['Température d\'ébullition (en K)'] != '':
            self.t_ebullition = float(e['Température d\'ébullition (en K)'])
        else:
            self.t_ebullition = None
        
    def __repr__(self):
        return f'Symbole : {self.symbole}\n\
Nom : {self.nom}\n\
Numéro atomique : {self.numero_atomique}\n\
Masse atomique : {self.masse_atomique} u\n\
Température de fusion : {self.t_fusion} K\n\
Température d\'ébullition : {self.t_ebullition} K\n'
    
    def nb_neutrons(self):
        return round(float(self.masse_atomique)) - int(self.numero_atomique)
    
    def t_fusion_degre(self) -> float:
        if self.t_fusion is None:
            return None
        return round(self.t_fusion - 273.15, 2)
    
    def t_ebullition_degre(self) -> float:
        if self.t_ebullition is None:
            return None
        return round(self.t_ebullition - 273.15, 2)
In [ ]:
e1 = Element(table[0])
e2 = Element(table[16])
print(e1)
print(e2)
print(f'Nombre de neutrons de {e1.symbole} : {e1.nb_neutrons()}')
print(f'Nombre de neutrons de {e2.symbole} : {e2.nb_neutrons()}')
print(f'Température de fusion de {e1.symbole} : {e1.t_fusion_degre()}')
print(f'Température de ébullition de {e2.symbole} : {e2.t_ebullition_degre()}')

Partie B : La classe TableauPeriodique


Q1. Créer la classe TableauPeriodique et définir les méthodes spéciales __init__ et __repr__ de la manière suivante :

  • Le constructeur initialise une variable d'instance elements, de type list d'Element, à partir de la liste d'enregistrements table passée en paramètre.
    On utilisera la méthode par compréhension pour créer cet attribut d'instance.
  • La méthode spéciale __repr__ doit renvoyer la chaîne de caractères ci-dessous :

    >>> t_mendeleiev = Tableau(table)
    >>> t_mendeleiev
    Symbole : H
    Nom : Hydrogène
    Numéro atomique : 1
    Masse atomique : 1.00794 u
    Température de fusion : None K
    Température d'ébullition : None K
    
    Symbole : He
    Nom : Hélium
    Numéro atomique : 2
    Masse atomique : 4.002602 u
    Température de fusion : None K
    Température d'ébullition : 4.22 K
    
    ...
    
    Symbole : Uuo
    Nom : Ununoctium
    Numéro atomique : 118
    Masse atomique : 294.0 u
    Température de fusion : None K
    Température d'ébullition : None K
    

Q2. Définir la méthode d'instance affiche_element qui :

  • Prend en paramètre le symbole d'un élément
  • Renvoie la chaîne de caractères correspondante si le symbole existe et None sinon

Exemple :

>>> t_mendeleiev = Tableau(table)
>>> print(t_mendeleiev.affiche_element('Ag'))
Symbole : Ag
Nom : Argent
Numéro atomique : 47
Masse atomique : 107.8682 u
Température de fusion : 1234.93 K
Température d'ébullition : 2473.15 K
>>> print(t_mendeleiev.affiche_element('Lg'))
None

Q3. On souhaite effectuer des opérations de sélection à l'aide d'une méthode select.
Cette méthode prend en argument :

  • Une liste de conditions conditions initialisée par défaut à la liste vide, de type list de str
  • Un dictionnaire attributs où :
    • La clé est le nom du champ à utiliser dans l'écriture de la condition
    • La valeur est l'attribut d'instance correspondant de la classe Element

Elle renvoie la liste des éléments répondant aux critères si la liste conditions est non vide et la liste de tous les éléments sinon.

On considère le code incomplet ci-dessous :

def select(self, 
               conditions: list=[], 
               attributs = {'nom':'e.nom', 
                            'sym':'e.symbole', 
                            'Z': 'e.numero_atomique', 
                            'A': 'e.masse_atomique', 
                            'Tf': 'e.t_fusion', 
                            'Te': 'e.t_ebullition'}) -> list:
        # Création par compréhension la liste des conditions en utilisant les attributs d'instance de la classe Element
        # On pourra utiliser la méthode replace des chaînes de caractères
        conditions_modifiees = ...
        # Création de la chaîne de caractères correspondant à la liste de ces conditions
        # On pourra utiliser la méthode join des chaînes de caractères
        chaine_conditions = ...
        # print(chaine_conditions)
        if chaine_conditions == '':
            return self.elements
        else:
            # Création de la liste des éléments qui répondent aux critères spécifiés
            # On pourra utiliser la fonction eval
            res = ...
            return res


Compléter les instructions d'affectation des variables conditions_modifiees, chaine_conditions et res en respectant les consignes données en commentaires. Si conditions = ['1 < Z < 5', '"H" in nom'], alors :

  • conditions_modifiees = ['1 < e.numero_atomique < 5', '"H" in e.nom']
  • chaine_conditions = '1 < e.numero_atomique < 5' and '"H" in e.nom'
  • res contient uniquement l'élément "Hélium"

Exemple :

>>> t_mendeleiev = TableauPeriodique(table)
>>> print(t_mendeleiev.select(['Z <= 5', 'Tf is not None', 'Tf > 1000']))
[Symbole : Be
Nom : Béryllium
Numéro atomique : 4
Masse atomique : 9.012182 u
Température de fusion : 1560.15 K
Température d'ébullition : 2744.15 K
, Symbole : B
Nom : Bore
Numéro atomique : 5
Masse atomique : 10.811 u
Température de fusion : 2348.15 K
Température d'ébullition : 4273.15 K
]
>>> print(t_mendeleiev.select(['1 <= Z <= 10', '"H" in nom']))
[Symbole : H
Nom : Hydrogène
Numéro atomique : 1
Masse atomique : 1.00794 u
Température de fusion : None K
Température d'ébullition : None K
, Symbole : He
Nom : Hélium
Numéro atomique : 2
Masse atomique : 4.002602 u
Température de fusion : None K
Température d'ébullition : 4.22 K
]
In [ ]:
class TableauPeriodique:
    
    def __init__(self, table: list):
        self.elements = [Element(e) for e in table]
    
    def __repr__(self):
        res = ''
        for e in self.elements:
            res += f'{e.__repr__()}\n'
        return res
    
    def affiche_element(self, symbole: str) -> str:
        for e in self.elements:
            if e.symbole == symbole:
                return f'{e.__repr__()}'
        return None
    
    def select(self, 
               conditions: list=[], 
               attributs = {'nom':'e.nom', 
                            'sym':'e.symbole', 
                            'Z': 'e.numero_atomique', 
                            'A': 'e.masse_atomique', 
                            'Tf': 'e.t_fusion', 
                            'Te': 'e.t_ebullition'}) -> list:
        conditions_modifiees = [c.replace(cle, attributs[cle]) for c in conditions for cle in attributs if cle in c]
        chaine_conditions = ' and '.join(conditions_modifiees)
        #print(chaine_conditions)
        if chaine_conditions == '':
            return self.elements
        else:
            res = [e for e in self.elements if eval(chaine_conditions)]
            return res
In [ ]:
t_mendeleiev = TableauPeriodique(table)
#print(t_mendeleiev)
#print(t_mendeleiev.affiche_element('Ag'))
#print(t_mendeleiev.affiche_element('Lg'))
print(t_mendeleiev.select(['Z <= 5', 'Tf is not None', 'Tf > 1000']))
print(t_mendeleiev.select(['1 <= Z <= 10', '"H" in nom']))

Exercice 4 : Une classe Texte


Q1. Créer une classe Texte et son constructeur. Celui-ci devra initialiser un attribut d'instance chaine à la valeur d'une chaîne de caractères passée en argument.
Créer ensuite une instance mon_texte de cette classe avec la chaine suivante :
blablabla bla blabla bli blo blabla blabla blabla blo bli

Q2. Créer une méthode nb_caracteres renvoyant le nombre de caractères de l'attribut chaine.
Exemple :

>>> mon_texte.nb_caracteres()
57

Q3. Créer la méthode ajout_prefixe qui modifie la valeur de l'attribut chaine en lui ajoutant un texte passé en paramètre en début de chaîne.
Exemple :

>>> mon_texte.ajout_prefixe('blibli ')
>>> mon_texte.chaine
'blibli blablabla bla blabla bli blo blabla blabla blabla blo bli'

Q4. Créer la méthode trouve_lettre qui :

  • Prend en paramètre une lettre
  • Renvoie la position de la première occurrence de cette lettre si elle est présente et -1 sinon

Exemple :

>>> mon_texte.trouve_lettre('i')
2
>>> mon_texte.trouve_lettre('u')
-1

Q5. Créer la méthode occurrences_lettre qui prend en paramètre une lettre et renvoie le nombre d'occurrences de celle-ci dans la chaîne, puis la tester.

Q6. On considère ici qu'un mot est un groupe de lettres ne contenant pas d'espace.
Créer la méthode nombre_mots qui renvoie le nombre de mots contenus dans chaine.

Q7. Créer la méthode dico_mots qui renvoie le dictionnaire des mots présents dans chaine où :

  • Les clés sont les mots
  • Les valeurs sont les nombres d'occurrences de chaque mot

Exemple :

>>> mon_texte.dico_mots()
{'blibli': 1, 'blablabla': 1, 'bla': 1, 'blabla': 4, 'bli': 2, 'blo': 2}

Q8. Créer la méthode dico_lettres qui renvoie le dictionnaire des lettres contenues dans chaine où :

  • Les clés sont les lettres
  • Les valeurs sont les couples (nombre d'occurrences de la lettre, liste des positions de la lettre dans chaine)

Exemple :

>>> mon_texte.dico_lettres()
{'b': (18, [0, 3, 7, 10, 13, 17, 21, 24, 28, 32, 36, 39, 43, 46, 50, 53, 57, 61]), 'l': (18, [1, 4, 8, 11, 14, 18, 22, 25, 29, 33, 37, 40, 44, 47, 51, 54, 58, 62]), 'i': (4, [2, 5, 30, 63]), ' ': (10, [6, 16, 20, 27, 31, 35, 42, 49, 56, 60]), 'a': (12, [9, 12, 15, 19, 23, 26, 38, 41, 45, 48, 52, 55]), 'o': (2, [34, 59])}

Q9. Créer un attribut de classe nb_instances qui dénombre le nombre d'instances de la classe Texte créées.
Compléter le programme en ajoutant une méthode de classe nombre_textes_crees renvoyant le nombre d'instances créées, puis la tester.

Q10. Définir la méthode spéciale repr pour qu'elle produise l'affichage donné en exemple.

>>> mon_texte = Texte('blibli blablabla bla blabla bli blo blabla blabla blabla blo bli')
>>> mon_texte
Nombre total de caractères : 64
Nombre total de mots : 11

Mots utilisés :
Le mot "blibli" apparaît 1 fois
Le mot "blablabla" apparaît 1 fois
Le mot "bla" apparaît 1 fois
Le mot "blabla" apparaît 4 fois
Le mot "bli" apparaît 2 fois
Le mot "blo" apparaît 2 fois

Lettres utilisées :
La lettre "b" apparaît 18 fois aux positions [0, 3, 7, 10, 13, 17, 21, 24, 28, 32, 36, 39, 43, 46, 50, 53, 57, 61]
La lettre "l" apparaît 18 fois aux positions [1, 4, 8, 11, 14, 18, 22, 25, 29, 33, 37, 40, 44, 47, 51, 54, 58, 62]
La lettre "i" apparaît 4 fois aux positions [2, 5, 30, 63]
La lettre " " apparaît 10 fois aux positions [6, 16, 20, 27, 31, 35, 42, 49, 56, 60]
La lettre "a" apparaît 12 fois aux positions [9, 12, 15, 19, 23, 26, 38, 41, 45, 48, 52, 55]
La lettre "o" apparaît 2 fois aux positions [34, 59]
In [ ]:
class Texte:
    nb_instances = 0
    
    def __init__(self, une_chaine: str):
        self.chaine = une_chaine
        Texte.nb_instances += 1
        
    def nb_caracteres(self) -> int:
        return len(self.chaine)
    
    def ajout_prefixe(self, une_chaine: str):
        self.chaine = une_chaine + self.chaine
    
    def trouve_lettre(self, lettre: str) -> int:
        for i in range(len(self.chaine)):
            if self.chaine[i] == lettre:
                return i
        return -1
    
    def occurrences_lettre(self, lettre: str) -> int:
        res = 0
        for car in self.chaine:
            if car == lettre:
                res += 1
        return res
        
    def dico_mots(self) -> dict:
        res = {}
        for mot in self.chaine.split():
            if mot in res:
                res[mot] += 1
            else:
                res[mot] = 1
        return res

    def nombre_mots(self) -> int:
        return len(self.chaine.split())
    
    def dico_lettres(self) -> dict:
        res = {}
        for i in range(len(self.chaine)):
            lettre = self.chaine[i]
            if lettre in res:
                nb, pos = res[lettre]
                pos.append(i)
                res[lettre] = (nb + 1, pos)
            else:
                res[lettre] = (1, [i])
        return res
    
    def __repr__(self) -> str:
        res = f'Nombre total de caractères : {self.nb_caracteres()}\n' +\
              f'Nombre total de mots : {self.nombre_mots()}\n\n' +\
              'Mots utilisés :\n'
        for mot, nb in self.dico_mots().items():
            res += f'Le mot "{mot}" apparaît {nb} fois\n'
        res += '\nLettres utilisées :\n'
        for lettre, (nb, pos) in self.dico_lettres().items():
            res += f'La lettre "{lettre}" apparaît {nb} fois aux positions {pos}\n' 
        return res
               
    @classmethod
    def nombre_textes_crees(cls) -> int:
        return cls.nb_instances
    
mon_texte = Texte('blablabla bla blabla bli blo blabla blabla blabla blo bli')
print(mon_texte.nb_caracteres())
mon_texte.ajout_prefixe('blibli ')
print(mon_texte.chaine)
print(mon_texte.trouve_lettre('a'))
print(mon_texte.trouve_lettre('u'))
print(mon_texte.occurrences_lettre('a'))
print(mon_texte.occurrences_lettre('u'))
print(mon_texte.occurrences_lettre('i'))
print(mon_texte.dico_mots())
print(mon_texte.nombre_mots())
print(mon_texte.dico_lettres())
print(mon_texte)

print(Texte.nombre_textes_crees())
autre_texte = Texte('blu')
print(Texte.nombre_textes_crees())

Exercice 5 : Jeu de cartes "La Bataille" - Utilisation de classes par une autre classe


Dans cet exercice, on considère un jeu de 52 cartes.

jeu_52_cartes.jpg

Partie A : La classe JeuDeCartes


Q1. Définir une classe JeuDeCartes permettant d'instancier des objets dont le comportement soit similaire à celui d'un vrai jeu de 52 cartes en respectant les contraintes ci-dessous :
Cette classe doit contenir les deux attributs de classe suivants :

  • L'attribut de classe figures contenant le dictionnaire {11: 'Valet', 12: 'Dame', 13: 'Roi', 14: 'As'}
  • L'attribut de classe couleurs contenant la liste des couleurs "Coeur", "Carreau", "Pique" et "Trèfle"

Le constructeur se chargera de créer l'attribut d'instance cartes qui contiendra une liste de 52 éléments, qui sont eux-mêmes des tuples de la forme (int, str). Cette liste de tuples contiendra les caractéristiques de chacune des 52 cartes. En effet, une carte peut être représentée par sa valeur et sa couleur. Par exemple la carte "As de Pique" sera représentée par le tuple (14, 'Pique'), la carte "Sept de Coeur" par le tuple (7, 'Coeur'), ...

Q2. Définir une méthode est_vide qui renvoie True si le jeu de cartes est vide et False sinon.
Exemple :

>>> j = JeuDeCartes()
>>> j.est_vide()
False
>>> j.cartes = []
>>> j.est_vide()
True

Q3. Définir une méthode battre qui permet de mélanger le jeu de cartes, uniquement si la liste cartes est non-vide.
On pourra utiliser la fonction shuffle du module random, en l'important au préalable, sur l'attribut cartes.

Q4. Définir une méthode tirer : Lorsque cette méthode est appelée, une carte est retirée du jeu et le tuple contenant sa valeur et sa couleur est renvoyé. On retire toujours la dernière carte de la liste.
Si cette méthode est invoquée alors qu'il ne reste plus aucune carte dans la liste, la méthode renverra None.
Exemple :

>>> j = JeuDeCartes()
>>> j.tirer()
(14, 'Trèfle')
>>> j.cartes = []
>>> j.tirer()
None

Q5. Définir une méthode nom_carte qui prend en paramètre un tuple représentant une carte et renvoyant la chaîne de caractères désignant la carte comme indiqué dans l'exemple. La méthode renverra None si la carte
Exemple :

>>> j = JeuDeCartes()
>>> j.nom_carte((14, 'Pique'))
'As de Pique'
>>> j.nom_carte((7, 'Coeur'))
'7 de Coeur'
>>> j.nom_carte(None)
None

Q6. Implémenter la suite d'instructions suivantes en Python puis tester pour n = 3, n = 5 et n = 60:

Affecter à n l'entier 3 (n représente le nombre de cartes à tirer)
Créer une instance j de JeuDeCartes
Battre le jeu de cartes
Afficher le jeu de cartes mélangés
Tirer et afficher le nom des n dernières cartes
In [ ]:
from random import shuffle

class JeuDeCartes():
    figures = {11: 'Valet', 12: 'Dame', 13: 'Roi', 14: 'As'}
    couleurs = ['Coeur', 'Carreau', 'Pique', 'Trèfle']
    
    def __init__(self):
        self.cartes = []
        for couleur in JeuDeCartes.couleurs:
            for i in range(2, 15):
                self.cartes.append((i, couleur))

    def est_vide(self) -> bool:
        return len(self.cartes) == 0
    
    def battre(self):
        if not(self.est_vide()):
            shuffle(self.cartes)
        
    def tirer(self) -> tuple:
        if self.est_vide():
            return None
        return self.cartes.pop()
    
    def nom_carte(self, carte: tuple) -> str:
        if carte is not None:
            valeur, couleur = carte
            if valeur>10:
                valeur = JeuDeCartes.figures[valeur]
            return f'{valeur} de {couleur}'
        return None
In [ ]:
j = JeuDeCartes()
print(j.est_vide())
j.cartes = []
print(j.est_vide())

j = JeuDeCartes()
print(j.tirer())
j.cartes = []
print(j.tirer())

j = JeuDeCartes()
print(j.nom_carte((14, 'Pique')))
print(j.nom_carte((7, 'Coeur')))
print(j.nom_carte(None))
In [ ]:
n = 3
j = JeuDeCartes()
j.battre()
print(j.cartes)
i = 0
while not(j.est_vide()) and i<n:
    print(j.nom_carte(j.tirer()))
    i += 1

Partie B : La bataille


Principe de la Bataille :

Ce jeu se joue à deux joueurs. Chacun possède un jeu de 52 cartes mélangés. Chaque joueur tire la carte située en haut du tas (retourné). Le joueur qui a la carte avec la plus grande valeur remporte le point. Si les deux valeurs sont égales, on passe au tirage suivant. On procède ainsi avec les 52 cartes des tas. Le gagnant est celui qui totalise le plus de points.

Q1. Définir une classe Joueur contenant les méthodes suivantes :

  • Un constructeur qui :
    • Initialise une variable d'instance nom, de type str, avec le nom du joueur passé en paramètre.
    • Initialise une variable d'instance score, de type int, avec la valeur passée en paramètre. Ce paramètre prendra pour valeur par défaut 0.
    • Initialise une variable d'instance tas de type JeuDeCartes.
  • Une méthode gagne qui permet d'augementer le score du joueur de 1 point
  • Une méthode info_score qui renvoie la chaîne de caractères suivante : Score du joueur {nom} : {score} point(s).
In [ ]:
class Joueur:
    def __init__(self, nom: str, score: int=0):
        self.nom = nom
        self.tas = JeuDeCartes()
        self.score = score
    
    def gagne(self):
        self.score += 1
        
    def info_score(self) -> str:
        return f'Score du joueur {self.nom} : {self.score} point(s)'

Q2. Définir une classe Match contenant les méthodes suivantes :

  • Un constructeur qui :
    • Initialise une variable d'instance joueur_A de type Joueur
    • Initialise une variable d'instance joueur_B de type Joueur
    • Initialise une variable d'instance vainqueur à None
  • Une méthode play qui simule une partie entre les deux joueurs.
    Le jeu commence par le mélange des tas de cartes de chacun des joueurs.

Exemple de partie au cours de laquelle le joueur B gagne :

A : 7 de Coeur | B : 10 de Coeur -> B gagne
A : 6 de Coeur | B : 2 de Carreau -> A gagne
A : 5 de Coeur | B : 2 de Pique -> A gagne
...
A : 3 de Coeur | B : 8 de Carreau -> B gagne
A : 2 de Coeur | B : 3 de Carreau -> B gagne

score du joueur Alice : 23 point(s)
score du joueur Bob : 27 point(s)
Le vainqueur est Bob avec un score de 27 points.

Exemple de partie au cours de laquelle les joueurs obtiennent le même score :

A : 7 de Coeur | B : 10 de Coeur -> B gagne
A : 6 de Coeur | B : 2 de Carreau -> A gagne
A : 5 de Coeur | B : 2 de Pique -> A gagne
...
A : 3 de Coeur | B : 8 de Carreau -> B gagne
A : 2 de Coeur | B : 3 de Carreau -> B gagne

Match nul. Les deux joueurs ont un score égal à 24.
In [ ]:
class Match:
    
    class Joueur:
        def __init__(self, nom: str, score: int=0):
            self.nom = nom
            self.tas = JeuDeCartes()
            self.score = score

        def gagne(self):
            self.score += 1

        def info_score(self) -> str:
            return f'Score du joueur {self.nom} : {self.score} point(s)'
    
    def __init__(self, joueur_A: joueur, joueur_B: joueur):
        self.joueur_A = joueur_A
        self.joueur_B = joueur_B
        self.vainqueur = None
    
    def play(self):
        self.joueur_A.tas.battre()
        self.joueur_B.tas.battre()
        for i in range(52):
            ca = self.joueur_A.tas.tirer()
            cb = self.joueur_B.tas.tirer()
            if ca[0] > cb[0]:
                self.joueur_A.gagne()
                print(f'A : {self.joueur_A.tas.nom_carte(ca)} | B : {self.joueur_B.tas.nom_carte(cb)} -> A gagne')
            elif ca[0] < cb[0]:
                self.joueur_B.gagne()
                print(f'A : {self.joueur_A.tas.nom_carte(ca)} | B : {self.joueur_B.tas.nom_carte(cb)} -> B gagne')
            else:
                print(f'A : {self.joueur_A.tas.nom_carte(ca)} | B : {self.joueur_B.tas.nom_carte(cb)} -> ex aequo')
        print('\n' + self.joueur_A.info_score())
        print(self.joueur_B.info_score())
        score_a, score_b = self.joueur_A.score, self.joueur_B.score
        if score_a == score_b:
            print(f'Match nul. Les deux joueurs ont un score égal à {score_a}.')
        else:
            if score_a > score_b:
                self.vainqueur = self.joueur_A
            else:
                self.vainqueur = self.joueur_B
            print(f'Le vainqueur est {self.vainqueur.nom} avec un score de {self.vainqueur.score} points.')
In [ ]:
match = Match(Joueur('Alice'), Joueur('Bob'))
match.play()

Exercice 6 : Points du plan et polygones - Interfaces


Partie A : Classe Point


On considère l'interface suivante :

from interface import Interface, implements

class InterfacePoint(Interface):
    
    def __init__(self, nom: str, x: int, y: int):
        pass

    def __repr__(self):
        pass
    
    @classmethod
    def distance(cls, a, b) -> float:
        pass

Q1. Définir une classe Point implémentant l'interface InterfacePoint puis compléter le corps des méthodes spéciales __init__ et __repr__ de la manière suivante :

  • Le constructeur __init__ doit initialiser :
    • L'attribut d'instance nom de type str qui prendra pour valeur le nom du point
    • L'attribut d'instance x de type int représentant l'abscisse du point
    • L'attribut d'instance y de type int représentant l'ordonnée du point
  • La méthode spéciale __repr__ doit renvoyer la chaîne {nom du point}({valeur de x}, {valeur de y})

Q2. Compléter le corps de la méthode de classe distance qui prend en paramètres deux instances a et b de Point et renvoie la valeur de la distance entre ces deux points.

Exemple d'utilisation :

>>> a = Point('A', -50, -150)
>>> b = Point('B', 200, -100)
>>> a
A(-50, -150)
>>> b
B(200, -100)
>>> Point.distance(a, b)
254.95097567963924
In [ ]:
from interface import Interface, implements

class InterfacePoint(Interface):

    def __init__(self, nom: str, x: int, y: int):
        pass

    def __repr__(self):
        pass

    @classmethod
    def distance(cls, a, b) -> float:
        pass
In [ ]:
from math import sqrt

class Point(implements(InterfacePoint)):
    
    def __init__(self, nom: str, x: int, y: int):
        self.nom = nom
        self.x = x
        self.y = y

    def __repr__(self):
        return f'{self.nom}({self.x}, {self.y})'
    
    @classmethod
    def distance(cls, a, b) -> float:
        return sqrt((b.x - a.x)**2 + (b.y - a.y)**2)
In [ ]:
a = Point('A', -50, -150)
b = Point('B', 200, -100)
print(a)
print(b)
print(Point.distance(a, b))

Partie B : Classe Polygone


On considère à présent l'interface donnée ci-dessous :

class InterfacePolygone(Interface):
    
    def __init__(self, points: list):
        pass
    
    def __repr__(self):
        pass
    
    def perimetre(self) -> float:
        pass
    
    def dessine(self):
        pass

Q1. Définir une classe Polygone implémentant l'interface InterfacePolygone puis compléter le corps des méthodes spéciales __init__ et __repr__ de la manière suivante :

  • Le constructeur __init__ doit initialiser l'attribut d'instance points, de type list de Point, correspondant à la liste des sommets du polygone. Lors de l'initialisation de cet attribut, on veillera à bien créer une nouvelle liste d'instances de la classe Point à partir de la liste points passée en paramètre, en utilisant la méthode par compréhension.
  • La méthode spéciale __repr__ doit renvoyer la chaîne suivante :
      Polygone {P1}{P2} ... {Pn} : [({x1}, {y1}), ({x2}, {y2}), ..., ({xn}, {yn})]

Q2. Compléter le corps de la méthode de d'instance perimetre qui renvoie la valeur du périmètre du polygone.

Q3. Compléter le corps de la méthode d'instance dessine ci-dessous permettant de dessiner le polygone dans une nouvelle fenêtre à l'aide du module turtle.
On pensera à importer ce module au préalable à l'aide de l'instruction import turtle.

def dessine(self):
        turtle.reset()
        turtle.setup()
        turtle.title(f'Représentation du polygone {self.__repr__()}')
        # Partie à compléter
        ...
        turtle.exitonclick()

Exemple d'utilisation :

>>> a = Point('A', -50, -150)
>>> b = Point('B', 200, -100)
>>> c = Point('C', 150, 100)
>>> p = Polygone([a, b, c])
>>> p
Polygone ABC : [(-50, -150),(200, -100),(150, 100)]
>>> p.perimetre()
781.2624688321647
>>> p.dessine()
td_classes_exo5.JPG
In [ ]:
class InterfacePolygone(Interface):
    
    def __init__(self, points: list):
        pass
    
    def __repr__(self):
        pass
    
    def perimetre(self) -> float:
        pass
    
    def dessine(self):
        pass
In [ ]:
import turtle

class Polygone(implements(InterfacePolygone)):
    
    def __init__(self, points: list):
        assert(len(points) >= 3), 'Un polygone doit avoir au minimum 3 sommets.'
        self.points = [point for point in points]
    
    def __repr__(self):
        nom, coords = '', ''
        for p in self.points:
            nom += p.nom
            coords += f'({p.x}, {p.y}), '
        return f'Polygone {nom} : [{coords[:-2]}]'
    
    def perimetre(self) -> float:
        res = Point.distance(self.points[0], self.points[-1])
        for i in range(len(self.points) - 1):
            res += Point.distance(self.points[i], self.points[i + 1])
        return res
    
    def dessine(self):
        turtle.reset()
        turtle.setup()
        turtle.title(f'Représentation du polygone {self.__repr__()}')
        turtle.up()
        turtle.goto(self.points[0].x, self.points[0].y)
        turtle.write(self.points[0].nom)
        turtle.down()
        for i in range(1, len(self.points)):
            turtle.goto(self.points[i].x, self.points[i].y)
            turtle.write(self.points[i].nom, align = "left")
        turtle.goto(self.points[0].x, self.points[0].y)
        turtle.exitonclick()
In [ ]:
a = Point('A', -50, -150)
b = Point('B', 200, -100)
c = Point('C', 150, 100)
p = Polygone([a, b, c])
print(p)
print(p.perimetre())
p.dessine()

Exercice 7 : Vecteurs 2D à coordonnées entières - Interface


Dans cet exercice, nous allons implémenter une classe permettant de créer et de manipuler des vecteurs 2D, à coordonnées entières, à partir de l'interface donnée ci-dessous :

from interface import Interface, implements

class InterfaceVecteur2D(Interface):
    
    def __init__(self, x: int=None, y: int=None):
        pass
    
    def __repr__(self) -> str:
        pass
    
    def multiplication_ext(self, k: int):
        pass

    def representant(self, point: tuple) -> tuple:
        pass
    
    def norme(self) -> float:
        pass
    
    @classmethod
    def addition(cls, u, v):
        pass
    
    @classmethod
    def produit_scalaire(cls, u, v) -> float:
        pass

    @classmethod
    def orthogonaux(cls, u, v) -> bool:
        pass
    
    @classmethod
    def colineaires(cls, u, v) -> bool:
        pass

Q1. Définir une classe Vecteur2D implémentant l'interface InterfaceVecteur2D puis compléter le corps des méthodes spéciales __init__ et __repr__ de la manière suivante :

  • Le constructeur __init__ doit initialiser :
    • L'attribut d'instance x de type int représentant l'abscisse du vecteur
    • L'attribut d'instance y de type int représentant l'ordonnée du vecteur
  • La méthode spéciale __repr__ doit renvoyer la chaîne ({valeur de x}, {valeur de y})

Exemple :

>>> u = Vecteur2D(4, 3)
>>> u
(4, 3)
>>> v = Vecteur2D(2)
>>> v
(2, None)
>>> w = Vecteur2D(y=7)
>>> w
(None, 7)

Q2. Compléter le corps de la méthode multiplication_ext qui prend en argument un entier k et renvoie une instance de Vecteur2D représentant le vecteur $k \vec{u}$.
Si une des coordonnées du vecteur $\vec{u}$ n'est pas définie, la méthode renverra None.

Exemple :

>>> u = Vecteur2D(4, 3)
>>> u.multiplication_ext(-6)
(-24, -18)
>>> v = Vecteur2D()
>>> print(v.multiplication_ext(-6))
None

Q3. Compléter le corps de la méthode representant qui prend en argument un tuple point (initialisé par défaut au couple (0, 0)) représentant l'origine du vecteur et renvoie un tuple constitués des couples représentant respectivement l'origine du vecteur et son extrémité.
Si une des coordonnées du vecteur $\vec{u}$ n'est pas définie, la méthode renverra None.

Exemple :

>>> u = Vecteur2D(4, 3)
>>> u.representant()
((0, 0), (4, 3))
>>> u.representant((2, 9))
((2, 9), (6, 12))
>>> v = Vecteur2D()
>>> print(v.representant((2, 9)))
None

Q4. Compléter le corps de la méthode norme qui renvoie la norme du vecteur $\vec{u}$.
Si une des coordonnées du vecteur $\vec{u}$ n'est pas définie, la méthode renverra None.

Exemple :

>>> u = Vecteur2D(4, 3)
>>> u.norme()
5.0
>>> v = Vecteur2D()
>>> print(v.norme())
None

Q5. Compléter le corps des quatre méthodes de classe de la manière suivante puis les tester.

  • La méthode addition doit renvoyer une instance de Vecteur2D représentant la somme $\vec{u} + \vec{v}$
  • La méthode produit_scalaire doit renvoyer le produit scalaire $\vec{u} . \vec{v}$
  • La méthode orthogonaux doit renvoyer True si les vecteurs $\vec{u}$ et $\vec{v}$ sont orthogonaux et False sinon
  • La méthode colineaires doit renvoyer True si les vecteurs $\vec{u}$ et $\vec{v}$ sont colinéaires et False sinon

Si une des coordonnées du vecteur $\vec{u}$ ou du vecteur $\vec{v}$ n'est pas définie, ces méthodes renverront None.

In [ ]:
    from interface import Interface, implements

    class InterfaceVecteur2D(Interface):

        def __init__(self, x: int=None, y: int=None):
            pass

        def __repr__(self) -> str:
            pass

        def multiplication_ext(self, k: int):
            pass

        def representant(self, point: tuple=(0, 0)) -> tuple:
            pass

        def norme(self) -> float:
            pass

        @classmethod
        def addition(cls, u, v):
            pass

        @classmethod
        def produit_scalaire(cls, u, v) -> float:
            pass

        @classmethod
        def orthogonaux(cls, u, v) -> bool:
            pass

        @classmethod
        def colineaires(cls, u, v) -> bool:
            pass
In [ ]:
from math import sqrt

class Vecteur2D(implements(InterfaceVecteur2D)):
    def __init__(self, x: int=None, y: int=None):
        self.x = x
        self.y = y
    
    def __repr__(self) -> str:
        return f'({self.x}, {self.y})'
    
    def multiplication_ext(self, k: int):
        if self.x is None or self.y is None:
            return None
        return Vecteur2D(k * self.x, k * self.y)

    def representant(self, point: tuple=(0,0)) -> tuple:
        if self.x is None or self.y is None:
            return None
        xo, yo = point
        return (point, (xo + self.x, yo + self.y))
    
    def norme(self) -> float:
        if self.x is None or self.y is None:
            return None
        return sqrt(self.x**2 + self.y**2)
    
    @classmethod
    def addition(cls, u, v):
        if u.x is None or u.y is None or v.x is None or v.y is None:
            return None
        return Vecteur2D(u.x + v.x, u.y + v.y)
    
    @classmethod
    def produit_scalaire(cls, u, v) -> float:
        if u.x is None or u.y is None or v.x is None or v.y is None:
            return None
        return u.x * v.x + u.y * v.y

    @classmethod
    def orthogonaux(cls, u, v) -> bool:
        if u.x is None or u.y is None or v.x is None or v.y is None:
            return None
        return cls.produit_scalaire(u, v) == 0
    
    @classmethod
    def colineaires(cls, u, v) -> bool:
        if u.x is None or u.y is None or v.x is None or v.y is None:
            return None
        return u.x * v.y - u.y * v.x == 0
In [ ]:
u = Vecteur2D(4, 3)
print(u)
v = Vecteur2D(2)
print(v)
w = Vecteur2D(y=7)
print(w)

u = Vecteur2D(4, 3)
print(u.multiplication_ext(-6))
u = Vecteur2D()
u.multiplication_ext(-6)

u = Vecteur2D(4, 3)
print(u.representant())
print(u.representant((2, 9)))
v = Vecteur2D()
print(v.representant((2, 9)))

u = Vecteur2D(4, 3)
print(u.norme())
v = Vecteur2D()
print(v.norme())

u = Vecteur2D(4, 3)
v = Vecteur2D(-7, 5)
w = Vecteur2D()
print(Vecteur2D.addition(u, v))
print(Vecteur2D.addition(u, w))

u = Vecteur2D(4, 3)
v = Vecteur2D(-7, 5)
w = Vecteur2D()
print(Vecteur2D.produit_scalaire(u, v))
print(Vecteur2D.produit_scalaire(u, w))

u = Vecteur2D(4, 3)
v = Vecteur2D(3, -4)
t = Vecteur2D(3, 2)
w = Vecteur2D()
print(Vecteur2D.orthogonaux(u, v))
print(Vecteur2D.orthogonaux(u, t))
print(Vecteur2D.orthogonaux(u, w))

u = Vecteur2D(4, 3)
v = Vecteur2D(-7, 5)
t = Vecteur2D(-20, -15)
w = Vecteur2D()
print(Vecteur2D.colineaires(u, v))
print(Vecteur2D.colineaires(u, t))
print(Vecteur2D.colineaires(u, w))