
Eleves={
    'Matthieu' : { 'Math' : 12, 'Physique':11, 'Informatique':18, 'Anglais' : 14 } ,
    'Ambre':{ 'Math' : 14, 'Physique':16, 'Informatique':20, 'Anglais' : 10 },
    'Chloe':{ 'Math' : 9, 'Physique':12, 'Informatique':11, 'Anglais' : 18 }
}

Eleves['Ambre']['Anglais'] = 15

# ==========================================
# =  Exemples de cours mutable/immutable   =
# ==========================================
#
# Exemples de types immutables
# avec des nombres
n1 = 5
n2 = n1
n1 = 6
print ( n1, " ",  n2 )          # affiche :      6   5

# avec des tuples
tuple1 = (1,2,3,4)
tuple2 = tuple1
tuple1 = tuple1 + (5,6)
print (tuple1, " ", tuple2)     # affiche :      (1, 2, 3, 4, 5, 6)   (1, 2, 3, 4)

# Exemples de types mutables
l1 = [ 1,2,3,4 ]
l2 = l1
l1[0] = 5
print ( l1, l2 )                # affiche :      [5, 2, 3, 4] [5, 2, 3, 4]

l2 =l1.copy()
l1[0] = -1
l1.append(7)
print ( l1, l2 )                # affiche :     [-1, 2, 3, 4, 7] [5, 2, 3, 4]


Eleve = {'nom': 'Matthieu', 'classe': 'MPSI', 'age': 18}
Eleve1 = Eleve
Eleve['age'] = 20
print (Eleve, Eleve1)
# affiche   {'nom': 'Matthieu', 'classe': 'MPSI', 'age': 20}
#           {'nom': 'Matthieu', 'classe': 'MPSI', 'age': 20}

Eleve = {'nom': 'Matthieu', 'classe': 'MPSI', 'age': 18}
Eleve2 = Eleve.copy()
Eleve['age'] = 19
print (Eleve, Eleve2)
# affiche   {'nom': 'Matthieu', 'classe': 'MPSI',  'age': 19}
#           {'nom': 'Matthieu', 'classe': 'MPSI',  'age': 18}
Eleve = {'nom': 'Matthieu', 'classe': 'MPSI', 'notes info': [17, 12, 10], 'age': 18}
Eleve2 = Eleve.copy()
Eleve['age'] = 19
Eleve['notes info'][2] = 12
print (Eleve, Eleve2)
# affiche   {'nom': 'Matthieu', 'classe': 'MPSI', 'notes info': [17, 12, 12], 'age': 19}
#           {'nom': 'Matthieu', 'classe': 'MPSI', 'notes info': [17, 12, 12], 'age': 18}
import copy
Eleve3 = copy.deepcopy(Eleve)
Eleve['age'] = 19
Eleve['notes info'][2] = 20
Eleve['notes info'].append(19)
print (Eleve, Eleve3)
# affiche   {'nom': 'Matthieu', 'classe': 'MPSI', 'notes info': [17, 12, 20, 19,20], 'age': 18}
#           {'nom': 'Matthieu', 'classe': 'MPSI', 'notes info': [17, 12, 12], 'age': 18}
#test fonction hash
unentier = 1
unechaine = 'dictionnaire'
unflottant = 4.55
# Afficher les valeurs de hachage
print("La valeur de hachage de l'objet integer est:"+ str(hash(unentier)))
print("La valeur de hachage de l'objet string est:"+ str(hash(unechaine)))
print("La valeur de hachage de l'objet float est:"+ str(hash(unflottant)))


"""
    Correction des exercices du cours
"""
# =====================================================
# = exercice 1 : dictionnaires des admis et non admis =
# =====================================================
etudiants = {"Arua" : 13 , "Baltazar" : 17 , "Briant" : 9 , "Clore" : 15 ,
             "Curare" : 8 , "Drouin" : 14 , "Flion" : 16 , "Garrouste" : 12 ,
			 "Larrere" : 13 , "Lion" : 15 , "Martin" : 14 , "Muller" : 9 ,
             "Niortin" : 10 , "Parra" : 12 , "Poulard" : 13 , "Rustin" : 7 ,
			 "Tavard" : 12 , "Trillet" : 15 , "Varret" : 9 , "Yimez" : 17 }

# on crée 2 dictionnaires vides : un pour les admis et l'autre pour les non-admis
etudiants_Admis = dict()
etudiants_NonAdmis = dict()

# on parcourt la liste des clés et des valeurs simultanément:
# si la clé est < 10 l'étudiant sera ajouté au dictionnaire etudiants_NonAdmis des étudiants non admis
# sinon l'étudiant sera ajouté au dictionnaire etudiants_Admis des étudiants admis
for cle , valeur in etudiants.items():
    if(valeur < 10):
        etudiants_NonAdmis[cle] = valeur
    else:
        etudiants_Admis[cle] = valeur

#affichage des dictionnaires => Clé et valeurs
print("Liste des etudiants admis et notes: " , etudiants_Admis)
print("Liste des etudiants non admis et notes: " , etudiants_NonAdmis)

# affichage uniquement de clés
print("Liste des etudiants admis")
for cle in etudiants_Admis.keys():
    print(cle)


# ================================================
# = exercice 2 : inversion d'un dictionnaire     =
# ================================================
def inverseDictionnaire( dict_init ):
    """
        def inverseDictionnaire( dict_init : dict)-> dict
        entrées : dict_init dictionnaire dont on veut inverser clé et valeur
        sortie : dict_res dictionnaire inversé
    """
    dict_res={}
    for cle, valeur in dict_init.items() :
        dict_res[valeur] = cle
    return dict_res
d1={ 'MPSI' : 43, 'PCSI': 42, 'MP':41, 'PC':32 }
print ( inverseDictionnaire( d1 ) ) # affiche {43: 'MPSI', 42: 'PCSI', 41: 'MP', 32: 'PC'}

#exemple avec des valeurs égales
d1={ 'MPSI' : 43, 'PCSI': 43, 'MP':41, 'PC':32 }
print( d1 )
print ( inverseDictionnaire( d1 ) ) # affiche {43: 'PCSI', 41: 'MP', 32: 'PC'}

"""
#exemple de plantage
d1={ 'MPSI' : 43, 'PCSI': 43, 'MP':41, 'PC':32,'MP2I' : [10,15] }
print( d1 )
print ( inverseDictionnaire( d1 ) ) # génère une erreur
"""

# ================================================
# = exercice 3 : Nombre d'occurences dans un mot =
# ================================================
def determine_nb_occurences(mot):
    '''
        determine_nb_occurences(mot : str) -> dict
        entree : mot, chaine de caracteres dont on veut connaitre le nb d occurences
        sortie : dict_occurences le dictionnaire dict_occurences comportant les couples caractere, nb d'occurrences
    '''
    dict_occurences = {}
    for car in mot :
        if( car in dict_occurences ):
            dict_occurences[car] = dict_occurences[car]  + 1
        else :
            dict_occurences[car] = 1
    return dict_occurences

print ( determine_nb_occurences("bonjour") ) # {'b': 1, 'o': 2, 'n': 1, 'j': 1, 'u': 1, 'r': 1}


# ================================================
# = exercice 4 : commerciaux                     =
# ================================================
ventes={"Jeff Bezos":10, "Bill Gates Jobs":13, "Mark Zuckerberg":8, "Elon Musk":15}
def chiffre_Affaires( dict ):
    ca = 0
    for val in dict.values() :
            ca+= val
    return ca
print (chiffre_Affaires( ventes ))
from numpy import inf
def max_Ventes(dict) :
    maxi = -inf
    for val in dict.values() :
        if val > maxi  :
            maxi= val
    return maxi
def meilleur_Commercial( dict ):
    dict_meilleurs ={}
    maxi = max_Ventes(dict)
    for cle, valeur in dict.items() :
        if valeur == maxi :
            dict_meilleurs[cle] = valeur
    return dict_meilleurs
print ("Meilleur commercial", meilleur_Commercial( ventes ))

import sys
from numpy import inf
def min_Ventes(dict) :
    mini = inf
    for val in dict.values() :
        if val < mini  :
            mini= val
    return mini

def supprime_PireCommercial( dictC ): # Approche sans effet de bord
    """ supprime_PireCommercial( dictC : dict )-> dic
        entrees : dictC  dictionnaire comportant les couples nom/Montant des commerciaux
        sorties : dict_res dictionnaire comportant les couples nom/Montant des commerciaux
               de dictC. dict_res ne contient pas le ou les pires commerciaux
    """
    dict_res = dictC.copy()
    mini = min_Ventes(dictC)
    for cle, valeur in dictC.items() :
        if valeur == mini :
            del dict_res[cle]
    return dict_res # retour du dictionnaire ne contenant pas le(s) pire(s) commercial(aux)
ventes = supprime_PireCommercial(ventes)
print ( "sans effet de bord" , ventes )

def supprime_PireCommercial( dict ): #tentative d'effet de bord
    mini = min_Ventes(dict)
    for cle, valeur in dict.items() :
        if valeur == mini :
            del dict[cle]   #RuntimeError: dictionary changed size during iteration
# supprime_PireCommercial(ventes)

def supprime_PireCommercial( dict ):    # avec effet de bord :
                                        #modification du CONTENU du dictionnaire dict
    dict_res = dict.copy()  # dans ce cas dict_res sert à copier les clés/valeurs
                            #pour les reaffecter à dict
    mini = min_Ventes(dict)
    dict.clear() # vidage d'une dictionnaire initial
    for cle, valeur in dict_res.items() : # puis remplissage avec les couples non minimaux
        if valeur != mini :
            dict[cle] = valeur # ajout au dictionnaire initial quand ce n'est pas le mini
supprime_PireCommercial(ventes)
print ( "avec effet de bord" , ventes )


def supprime_PireCommercial( dict ):    # tentative d'effet de bord en reaffectant
                                        # l'adresse du dictionnaire
    dict_res = dict.copy()  # dans ce cas dict_res est une variable locale qui doit servir
                            # a reaffecter dict
    mini = min_Ventes(dict)
    for cle, valeur in dict.items() :
        if valeur == mini :
            del dict_res[cle]
    print (dict_res)
    dict = dict_res
    print (dict) # affiche pas le bon résultat : dictionnaire non modifié
supprime_PireCommercial(ventes) # n'affiche pas le bon résultat : dictionnaire non modifié
                                # => echec de la tentative

# =====================================================
# = exercice 5 : nombre de points au Scrabble         =
# =====================================================
def  score_scrabble(mot, dico_scrabble) :
    '''
        score_scrabble(mot : str, dico_scrabble : dict) ->int
        entrées : mot, chaine de caracteres sur laquelle on veut obtnir le nb de points
                : dico_scrabble, dictionnaire qui a pour clé la lettre et pour valeur le nb de points associés
        sortie : score, entier, qui corespont au score du mot au scrabble
    '''
    score = 0
    for  car in mot :
        score += dico_scrabble[car.upper()]
    return score

dico_scrabble={"A":1,"B":3,"C":3,"D":2,"E":1,"F":4,"G":2,"H":4,"I":1,"J":8,"K":10,"L":1,"M":2,"N":1,"O":1,"P":3,\
          "Q":8,"R":1,"S":1,"T":1,"U":1,"V":4,"W":10,"X":10,"Y":10,"Z":10}
print ( score_scrabble("bonjour", dico_scrabble) ) # affiche 16


# =====================================================
# = exercice 6 : Fonctions de hachage                 =
# =====================================================
import math
# pre traitement
def transforme_chaine_entier( chaine ) :
    '''
        transforme_chaine_entier( chaine :str ) -> int
        retourne un entier resultat correspondant à la chaine de caractères chaine
    '''
    lgchaine = len(chaine)
    resultat = 0
    for i in range(0,lgchaine):
        nb = ord( chaine[i] ) # nb prend la valeur ascii du ieme caractere de la chaine
        resultat = nb*256**i + resultat
    return resultat

# fonction hachage division
def hachage( valeur_a_hacher , m ):
    '''
        hachage( valeur_a_hacher : int, m:int  ) -> int
        retourne un entier correspondant à la valeur de hachage  permettant d'obtenir
    '''
    return valeur_a_hacher%m

# fonction hachage multiplication
def hachage_mult( valeur_a_hacher , m, c ):
    '''
        hachage_mult(nb : int, m:int , c :float ) -> int
        retourne un entier correspondant à la valeur de hachage
    '''
    res = math.floor(  m* ( (valeur_a_hacher * c)%1) )
    return res
longueur_cle = 256
nbtest1 = transforme_chaine_entier( "test" )
print ( "test devient ", nbtest1 )
print(  "test devient après hachage"  ,hachage(nbtest1,longueur_cle ) )
c = (math.sqrt(5)-1)/2
print ( "test devient après hachage multiplication", hachage_mult( nbtest1, longueur_cle , c) ) #test devient après hachage multiplication 176
nbtest2 = transforme_chaine_entier( "dico" )
print ( "==dictionnaire devient ", nbtest2 )
print(  "==dictionnaire devient après hachage"  ,hachage(nbtest2,longueur_cle ) )
print(  "dictionnaire devient après hachage multiplication"  , hachage_mult(nbtest2,longueur_cle , c) )


# =====================================================
# = exercice 7: compression  decompression LZ78       =
# =====================================================
def compressionLZ78( texte ):
    n=len(texte)
    indice = 0
    code =''
    dico ={'':0}

    while indice < n :
        ch=''
        while indice<n and ch + texte[indice] in dico: # recherche d'une chaine non presente dans dico
            ch += texte[indice]
            indice +=1
        if indice < n  :
            lg_dico =len(dico)  # determination de la valeur index
            car = texte[indice]
            chaine_a_ajouter = ch + car         # determination de la clé : chaine_a_ajouter pas encore presente dans le dictionnaire
            dico[chaine_a_ajouter] = lg_dico    # ajout dans dico du couple (chaine_a_ajouter,lg_dico)
            code += (str(dico[ch]) +',' + car + '|')
        elif ( indice ==n and ch in dico) : #gestion du dernier motif de la chaine s'il dejà présent dans le dictionnaire
            code += (str(dico[ch[:-1]]) +',' + ch[-1] + '|')

        indice = indice +1
    return code, dico

def decompressionLZ78( code, dico ):
    # inversion du dictionnaire les valeurs deviennent cle => decompression
    dico_inverse = {v: k for k, v in dico.items()}
    texte =''
    if( code[-1] == '|' ):
        code = code[0:-1] #retrait du dernier |
    les_codes = code.split('|') #decoupage des différents codes
    for elt in les_codes :
        E = elt.split(',')
        debut= dico_inverse[ int(E[0].strip()) ]
        texte += debut + E[1]
    return texte

res,dico = compressionLZ78( "abracadabrarabarabaran" )
print (res)
res = decompressionLZ78( res,dico )
print (res)
print ('test hash',res.__hash__)

