from typing import List, Tuple, Dict, Set

#Exercice 1 : Itineraires

#Exo 1, question 1 :

def resume(itineraire:List[Tuple[str,float]])->Tuple[float, float]:
     """Pre toutes les valeurs d'itinéaires sont >= 0
     Étant donné un itinéraire, renvoie un couple dont le premier élément donne le déplacement total qui est à effectuer vers le nord et le second élément donne le déplacement total qui
     est à effectuer vers l’est"""
     nord:float = 0
     est:float = 0                                    
     e : Tuple[str,float]

     for e in itineraire:
         s, n = e
         if s == 'nord':
             nord = nord + n
         elif s == 'sud':
             nord = nord - n
         elif s == 'est':
             est = est + n
         elif s == 'ouest':
             est = est - n
     return (nord, est)

assert resume([('nord', 3.2),('est', 2.5),('nord', 4)]) == (7.2, 2.5)
assert resume([('sud', 2)]) == (-2, 0)
assert resume([('sud', 3.2),('ouest', 2.5),('nord', 4.2)]) == (1, -2.5)
assert resume([('est', 2.5),('ouest', 2.5)]) == (0, 0)
assert resume([]) == (0, 0)

#Exo 1 : question 2 :

def retour(itineraire:List[Tuple[str,float]])->List[Tuple[str,float]]:
     """Pre toutes les valeurs d'itinéaires sont >= 0
     étant donné un itinéraire, renvoie l’itinéraire correspondant au retour"""
     e : Tuple[str,float]
     
     lp : List[Tuple[str,float]] = []
          
     for e in itineraire:
         s, n = e
         lr : List[Tuple[str,float]] = []
         
         if s == 'nord':
             lr.append(('sud', n))
             
         elif s == 'sud':
             lr.append(('nord', n))
             
         elif s == 'ouest':
             lr.append(('est', n))
             
         elif s == 'est':
             lr.append(('ouest', n))

         lp = lr + lp
                     
     return lp

assert retour([]) == []
assert retour([('sud', 2.0)]) == [('nord', 2.0)]
assert retour([('nord', 2.0)]) == [('sud', 2.0)]
assert retour([('est', 5.0)]) == [('ouest', 5.0)]
assert retour([('ouest', 6.2)]) == [('est', 6.2)]
assert retour([('est', 3.2),('est', 2.5)]) == [('ouest', 2.5),('ouest', 3.2)]
assert retour([('sud', 1.0),('ouest', 5.4),('nord', 6.0)]) == [('sud', 6.0),('est', 5.4),('nord', 1.0)]

#Exercice 2 : Typologie d'animaux

Dclassif:Dict[str, Set[str]]= {    #initialisation du dictionnaire Dclassif
    'animal':{'mammifere', 'oiseau'},
    'mammifere':{'chat', 'chien','lapin'},
    'oiseau':{'pigeon', 'poule'},
    'chat':{'chartreux', 'bengale', 'persan'},
    'chien':{'chihuahua','bouledogue','labrador','lévrier'}}

#Exo 2 : question 1 :

def nb_instances(Dcla:Dict[str, Set[str]]) -> int:
    """Pre :
    Étant donné un dictionnaire de classification, renvoie le nombre d’instances qu’il contient"""
    nb : int = 0
    p : Set[str] = set()
    res : str

    for (cat,_) in Dcla.items():
        p.add(cat)

    for (_,sub) in Dcla.items():
        for res in sub:
            if res not in p:
                nb = nb + 1
    return nb
         

assert nb_instances(Dclassif) == 10  

#Exo 2 : question 2 :

def cat_max(Dcla:Dict[str, Set[str]]) -> Set[str]:
    """Pre :
    Étant donné un dictionnaire de classification, renvoie l’ensemble des catégories comptant le nombre maximal de sous-catégories"""
    res : str = ''
    nb : int = 0
    p : Set[str] = set()
   
    for (cat,sub) in Dcla.items():
        if len(sub) > nb:
            p = {cat}
            nb = len(sub)

        elif len(sub) == nb and nb != 0:
            p.add(cat)

        elif len(sub) > nb and len(p) == 2:
            p = {cat}
            nb = len(sub)                  
    
    return p

assert cat_max(Dclassif) == {'chien'}

Dclassif_bis:Dict[str, Set[str]]= {  #initialisation d'un dictionnaire Dclassif sans les sous-catégories de chiens
    'animal':{'mammifere', 'oiseau'},
    'mammifere':{'chat', 'chien','lapin'},
    'oiseau':{'pigeon', 'poule'},
    'chat':{'chartreux', 'bengale', 'persan'}}

assert cat_max(Dclassif_bis) == {'chat','mammifere'}

#Exo 2 : question 3 :

def cate_sup(cat: str, Dcla: Dict[str, Set[str]], cat_sup: Set[str]) -> None:
    ''''''
    for (sup,sub) in Dcla.items():
        if cat in sub:
            cat_sup.add(sup)
            cate_sup(sup, Dcla, cat_sup)

def typologie(Dcla: Dict[str, Set[str]]) -> Dict[str, Set[str]]:
    ''''''    
    s : str
    typo : Dict[str, Set[str]] = dict()

    for (cat,sub) in Dcla.items():
        for s in sub:
            if s not in typo:
                typo[s] = set()
                cate_sup(s, Dcla, typo[s])

    return typo

assert typologie(Dclassif) == {
                               'mammifere':{'animal'},
                               'oiseau':{'animal'},
                               'chat': {'animal', 'mammifere'}, 
                               'chien': {'animal', 'mammifere'},
                               'lapin':{'mammifere','animal'},
                               'pigeon':{'oiseau','animal'},
                               'poule':{'oiseau','animal'},
                               'chartreux':{'chat','mammifere','animal'},
                               'bengale':{'chat','mammifere','animal'},
                               'persan':{'chat','mammifere','animal'},
                               'chihuahua':{'chien','mammifere','animal'},
                               'bouledogue':{'chien','mammifere','animal'},
                               'labrador':{'chien','mammifere','animal'},
                               'lévrier':{'chien','mammifere','animal'}}

#Exo 2 : question 4 :

Dcaract:Dict[str, Set[str]]= {      #Initialisation du dictionnaire de caractéristiques Dcaract
    'animal':{'respire'},
    'oiseau':{'vole','oeuf','plumes','2pattes'},
    'mammifere':{'poils','4pattes'}}

def caracteristiques(categorie: str, Dcla: Dict[str, Set[str]], dcar: Dict[str, Set[str]]) -> Set[str]:
    '''Pre : categorie est une chaîne de caractères donnant le nom d'une categorie, par forcement present dans Dcla
    Étant donné une chaîne de caractères donnant le nom d’une catégorie, un dictionnaire de classification et un dictionnaire de caractéristiques, renvoie
    l’ensemble des caractéristiques possédées par la catégorie'''
    
    typo_cat: Dict[str, Set[str]] = typologie(Dcla)
    res : str
    p : Set[str] = set()
    f : Set[str] = set()
    caract : Set[str] = set()

    for (cat,sub) in typo_cat.items():
        if cat == categorie:
            p = sub

    for res in p:
        for (cat,sub) in dcar.items():
            if res == cat:
                f = f | sub
    return f

assert caracteristiques('vache', Dclassif, Dcaract) == set()
assert caracteristiques('poule', Dclassif, Dcaract) == {'respire', 'vole','oeuf','plumes','2pattes'}                     
assert caracteristiques('lapin', Dclassif, Dcaract) == {'respire', 'poils','4pattes'}
assert caracteristiques('labrador', Dclassif, Dcaract) == {'respire', 'poils','4pattes'}
assert caracteristiques('chien', Dclassif, Dcaract) == {'respire', 'poils','4pattes'}

#Exo 2 : question 5 :

Dexc:Dict[str, str]= {'autruche':'non_vole', 'sphynx':'non_poils'} #Initialisation d'un dictionnaire d'exception Dexc

Dclassif2:Dict[str, Set[str]]= {        #Initialisation d'un deuxième dictionnaire de classification Dclassif2
                               'animal':{'mammifere', 'oiseau'},
                               'mammifere':{'chat', 'chien','lapin'},
                               'oiseau':{'pigeon', 'poule','autruche'},
                               'chat':{'chartreux', 'bengale', 'persan','sphynx'},
                               'chien':{'chihuahua','bouledogue','labrador','lévrier'}}

def oppose(s1: str, s2: str)->bool:
    """Pre : len(s1) and len(s2) > 0, s2 and s1 appartiennent a un dictionnaire de caractéristiques
    Étant donné deux chaînes de caractères s1 et s2, renvoie le booléen True si l’une est égale à l’autre préfixée par 'non_'"""

    return s1[:4] == 'non_' and s1[4:] == s2 or s2[:4] == 'non_' and s2[4:] == s1

assert oppose('non_vole','vole')  == True
assert oppose('poils','non_poils')  == True
assert oppose('non_vole','non_poils')  == False
assert oppose('vole','poils')  == False

#Exo 2 : question 6 :

Donner une définition de la fonction caracteristiques_exc qui, étant donné une chaîne
de caractères donnant le nom d’une catégorie, un dictionnaire de classification, un dictionnaire
de caractéristiques et un dictionnaire d’exceptions, renvoie l’ensemble des caractéristiques
possédées par la catégorie considérée, en tenant compte des exceptions.
Par exemple :
>>> caracteristiques_exceptions(’lapin’, Dclassif2, Dcaract, Dexc)
{’respire’, ’poils’,’4pattes’}
>>> caracteristiques_exceptions(’chien’,Dclassif2, Dcaract, Dexc)
{’respire’, ’poils’,’4pattes’}
>>> caracteristiques_exceptions(’labrador’, Dclassif2, Dcaract, Dexc)
{’respire’, ’poils’,’4pattes’}
>>> caracteristiques_exceptions(’poule’,Dclassif2, Dcaract, Dexc)
{’respire’, ’vole’,’oeuf’,’plumes’,’2pattes’}
>>> caracteristiques_exceptions(’vache’,Dclassif2, Dcaract, Dexc)
set()
>>> caracteristiques_exceptions(’autruche’,Dclassif2, Dcaract, Dexc)
{’respire’, ’oeuf’,’plumes’,’2pattes’}
>>> caracteristiques_exceptions(’sphynx’,Dclassif2, Dcaract, Dexc)
{’respire’,’4pattes’}