"""
POO = Programmation orienté objet
"""

"""
Créer deux classes, Arbre et Giraffe, dont les constructeur prennent en parametre une taille en mêtre.
Dans la classe Giraffe, surcharger l'opérateur < ( __lt__(self, a) ) de façon à ce qui si la giraffe et l'arbre n'ont que 50cm d'écart, alors la giraffe peut manger et affiche "niam niam".
Pour s'assurer que l'objet passé en parametre est une instance de la classe arbre, on pourra utiliser la fonction isinstance(a, Arbre) qui retourne vrai si a est un Arbre.
"""

class Arbre():
    ...

class Giraffe():
    ...
       
    def __lt__(self, a):
        ...
            
            
g1 = Giraffe(4)
g2 = Giraffe(4.4)
a1 = Arbre(3.9)
a2 = Arbre(4.5)

g1 < a1 # niam niam
g1 < a2 # je ne peux pas manger ça !
g1 < g2 # Ca va pas la tête !
g1 < 2 # je ne peux pas manger ça !


"""
Exercice 1 : Compléter la méthode calculer (...) et corriger la méthode afficher (2 erreurs)
Astuce, si on souhaite corriger la méthode afficher en premier, on peut remplacer le corp de la méthode calculer par l'instruction "pass"
"""

class division_euclidienne():
	def __init__(self, a, b):
		
		assert isinstance(a, int), "le dividende doit être un entier"
		assert isinstance(b, int), "le diviseur doit être un entier"
		assert a >= 0, "le dividende doit être positif"
		assert b > 0, "le diviseur doit être positif"
		
		self.dividende = a
		self.diviseur = b
		self.quotient = 0
		self.reste = 0
		self.calcul_fait = False
		
	def calculer(self):
		self.reste = ... 
		while ...
			self.reste = ...
			self.quotient = ...
		self.calcul_fait = True
		
	def __str__():
		if calcul_fait:
			return f"{self.dividende} = {self.quotient} * {self.diviseur} + {self.reste}"
		else:
			return f"{self.dividende} = ??? * {self.diviseur} + ???"

			
a = division_euclidienne(11, 3)
a.afficher() # 11 = ??? * 3 + ???
a.calculer() 
a.afficher() # 11 = 3 * 3 + 2

"""
Exercice 22

Définir une classe Intervalle représentant des intervalles de nombres.
Cette classe possède deux attributs a et b représentant respective ment l'extrémité inférieure et l'extrémité supérieure de l'intervalle.
Les deux extrémités sont considérées comme incluses dans l'intervalle. Tout intervalle avec b < a représente l'intervalle vide.
• Écrire le constructeur de la classe Intervalle et une méthode est_vide renvoyant True si l'objet représente l'intervalle vide et False sinon.
• Ajouter des méthodes __len__ renvoyant la longueur de l'intervalle (l'intervalle vide a une longueur 0) et __contains__ testant l'appartenance d'un élément x à l'intervalle.
• Ajouter une méthode __eq__ permettant de tester l'égalité de deux intervalles avec == et une méthode __le__ permettant de tester l'inclusion d'un intervalle dans un autre avec <=. Attention: toutes les représentations de l'intervalle vide doivent être considérées égales, et incluses dans tout intervalle.
• Ajouter des méthodes intersection et union calculant respectivement l'intersection de deux intervalles et le plus petit intervalle contenant l'union de deux intervalles (l'intersection est bien toujours un intervalle, alors que l'union ne l'est pas forcément). Ces deux fonctions doivent renvoyer un nouvel intervalle sans modifier leurs paramètres.
• Tester ces méthodes.
"""

class Intervalle():
    def __init__(self, a, b):
        self.a = a
        self.b = b
        
    def __str__(self):
        return f"[{self.a};{self.b}]"
        
    def est_vide(self):
        pass
    
    def __len__(self):
        return False
    
    def __contains__(self, x):
        return False
    
i = Intervalle(2,8)
print(Intervalle(2,8).est_vide()) #False
print(Intervalle(8,8).est_vide()) #True
print(Intervalle(8,6).est_vide()) #True
print(len(Intervalle(2,8))) #6
print(len(Intervalle(8,8))) #0
print(len(Intervalle(8,6))) #0
print(6 in Intervalle(2,8)) #True
print(2 in Intervalle(2,8)) #True
print(8 in Intervalle(2,8)) #True
print(9 in Intervalle(2,8)) #False
print(-1 in Intervalle(2,8)) #False

"""
Exercice 21 : compléter la classe fraction et tester les opérations
"""
class Fraction():
	def __init__(self, a, b):
		self.a = a
		self.b = b 
		if b == 0 : 
		    raise ValueError("Le dénominateur ne peut pas être nul")
	
	# a / b
	def __str__(self):
	    pass
	
	#opérateur == : retoure True si les deux fraction sont égales. Conseil : utiliser l'égalité des produits en croix
	def __eq__(self, f):
	    pass
	
	#opérateur < : retoure True si la fraction est plus petite que f
	def __lt__(self, f):
	    pass
	
	#opérateur + : retoure une nouvelle fraction qui est la somme des deux fractions
	def __add__(self, f):
	    pass
	
	#opérateur * : retoure une nouvelle fraction qui est le produit des deux fractions
	def __mul__(self, f):
	    pass
		
	def irreductible(self):
		# BONUS simplifier a et b par le PGCD(a,b), utiliser la fonction math.gcd de python
		pgcd = math.gcd(self.a,self.b)
		a = self.a // pgcd
		b = self.b // pgcd
		return Fraction(a,b)
	    

a = Fraction(1,2)
b = Fraction(1,4)
c = Fraction(1,4)
print(a) # 1 / 2
print(b) # 1 / 4
print(a == b) # false
print(c == b) # True
print(a < b) # False
print(b < a) # True
print(b + a) # 3 / 4
print(b * a) # 1 / 8
print((b + a) * (b + a)) # 9 / 16
print(a+a) # 1 / 1
print(b+b) # 1 / 2

"""
Exercice 24

Définir une classe Date pour représenter une date, avec trois attributs jour, mois, année.
• Ecrire son constructeur
• Ajouter une méthode __str__ qui renvoie une chaîne de caractères de la forme "8 Mai 1945". On pourra se servir d'un attribut de classe qui est un tableau donnant les douze mois de l'année.
  tester en construisant des objets de la classe Date puis en les affichant avec print.
• Ajouter une méthode __lt__ qui permet de déterminer si une date d1 est antérieur à une date d2 en écrivant d1 < d2; La tester.

"""

"""
Exercice 25
Dans certains langages de programmation, comme Pascal ou Ada, les tableaux ne sont pas nécessairement indexés à partir de 0. C'est le programmeur qui choisit sa plage d'indices.
Par exemple, on peut déclarer un tableau dont les indices vont de -10 à 9 si on le souhaite.
Dans cet exercice, on se propose de construire une classe Tableau pour réaliser de tels tableaux.
Un objet de cette classe aura deux attributs, un attribut premier qui est la valeur du premier indice et un attribut contenu qui est un tableau Python contenant les éléments.
Ce dernier est un vrai tableau Python, indexé à partir de 0.
• Écrire un constructeur __init__(self, imin, imax, v) où imin est le premier indice, imax le dernier indice et v la valeur utilisée pour initialiser toutes les cases du tableau.
  Ainsi, on peut écrire t = Tableau (-10, 9, 42) pour construire un tableau de vingt cases, indexées de -10 à 9 et toutes initialisées avec la valeur 42.
• Écrire une méthode __len__(self) qui renvoie la taille du tableau.
• Écrire une méthode __getitem__(self, i) qui renvoie l'élément du tableau self d'indice i.
  De même, écrire une méthode __setitem__(self, i, v) qui modifie l'élément du tableau self d'indice i pour lui donner la valeur v.
  ( Ces deux méthodes doivent vérifier que l'indice i est bien valide et, dans le cas contraire, lever l'exception IndexError avec la valeur de i en argument (c'est-à-dire raise IndexError (i))).
• Enfin, écrire une méthode ___str__(self) qui renvoie une chaîne de caractères décrivant le contenu du tableau.
"""
