Python L1, le corrigé de vos devoirs

Vous trouverez ici des commentaires sur les variantes possibles, dont le but est de vous convaincre que Python est un langage souple, adaptable à votre raisonnement préféré. Par contre, si au lieu de raisonner, vous avez essayé de "deviner" la solution sans même lire les notes de cours, vous n'aviez presqu'aucune chance. Il m'est difficile de tolérer les étudiants qui ne savent pas lire... Quand je demande une fonction, et le programme ne définit aucune fonction, la note sera mauvaise. Quand je demande des boucles et il n'y a pas de boucles, idem.

Exercices en TP du 21.01.2015

Le calcul du logarithme de 2 par la sommation d'une série infinie : \(s = \displaystyle{-\sum_{n=1}^\infty {(-1)^n \cdot \frac{1}{n}}}\).

La solution est :

s=0
sgn=1
for n in range(1,300):
    s+=sgn/n
    print(s)
    sgn=-sgn

On a discuté cela en TD : pas la peine d'évaluer explicitement (-1)n, si la seule chose dont on a besoin c'est la séquence régulière : 1, -1, 1, -1, etc. On peut la construire également comme 2*(n%2)-1.

La seconde partie c'est l'optimisation d'Aitken. Si la séquence engendrée par le programme précédent : \(s_1, s_2, s_3,\ldots , s_n, \ldots\) converge mal – ici autour de n=300 on a :

...
0.6948392230929691
0.6914608447145907
0.694827848081594
0.6914721433836074
0.6948166249889586
...
tandis que log(2) vaut : 0.69314718056, on peut construire une autre série, disons rn donnée par la formule :

$$\displaystyle{r_n = \frac{(s_{n+2}\cdot s_n - s_{n+1}^2)}{(s_{n+2}-2 s_{n+1} + s_n)}}$$

il faut disposer de trois dernières valeurs de \(s\) afin de construire une nouvelle \(r\). Voici la solution combinée, qui calcule et imprime les deux séries, et le résultat autour de n=50, où on aura presque 6 chiffres significatifs (la série d'origine : une).

s0,s1,s2=0,1,0.5
sgn=1
for n in range(3,50):
    s0,s1 = s1,s2
    s2+=sgn/n
    r=(s0*s2-s1*s1)/(s2-2*s1+s0)
    print(s2,r)
    sgn=-sgn
#
...
0.7041348653340472 0.6931485981680049
0.6823957348992646 0.6931458543450361
0.7036723306439455 0.6931484230713082
0.6828389973106122 0.6931460148544717
0.7032471605759183 0.6931482756611276
Une boucle de sommation est un exercice "type". En général, les exercices faits en TD/TP, et surtout les sujets proposés pour les devoirs sont des "motifs standard" de la programmation. On peut se tromper, égarer, etc. Mais si après un échec quelqu'un ne revise pas la solution, et répète les mêmes fautes pour la deuxième et la troisième fois, il gaspille son temps chez nous.


Interrogation sur papier du 28.01.2015

  1. Une boucle qui calcule et affiche la somme de nombres impairs.

    s=0
    for i in range(50):
        s+=(2*i+1)
        print(s)
    
    On peut coder ceci de plusieurs façons, par ex. :
    for i in range(1,100,2): s+=i
    

  2. Lire un nombre flottant comme une chaîne avec le point décimal, trouver le point, séparer les parties avant et après.

    s=input("Nombre : ")
    for i in range(len(s)):
        if s[i]==".": break
    print((s[:i],s[i+1:]))
    
    La séparation peut être réalisée de manière encore plus facile : s.split(".") retourne la liste de tous les fragments séparés par le caractère séparateur, ici le point. Par ex. si s="650.2312", on doit afficher ("650","2312").

    J'ai écrit qu'il fallait lire le nombre. Visiblement plusieurs n'ont pas compris ce que cela signifiait. Tant pis, il fallait poser des questions.

  3. Étant donné une séquence de tuples (paires) : \(c = ((a_0,b_0), (a_1,b_1), (a_2,b_2), \ldots (a_n,b_n))\), séparer les premiers et les seconds éléments, obtenir : \((a_0, a_1, a_2,\ldots , a_n)\) et \((b_0, b_1, b_2,\ldots , b_n)\). La solution (possible)

    c= ...
    aa=(); bb=()
    for x in c:
        aa+=(x[0],); bb+=(x[1],)
    
    En TD je vous ai montré l'usage de la fonction zip qui peut effectuer cette opération, ainsi que son inverse.


Exercices en TP du 04.02.2015

(L'énoncé se trouve ici ; le dossier utilisé en TP n'existe plus).

Affichage et lecture des nombres.

  1. Étant donnée la représentation visuelle d'un nombre sous forme de liste ou tuple de chiffres, par ex. [1,6,0,8,3] ou (1,6,0,8,3), construire la valeur numérique, ici 16083.
    Je vous ai donné l'algorithme – voir l'énoncé. Il peut être implémenté de plusieurs manières, par ex.

    def seqtonum(s):
        n=0
        for x in s:
            n=10*n+x
        return n
    
    ou
    def seqtonum1(s):
        pw=1; nl=len(s)
        n=0
        for i in range(nl):
            n+=s[i]*10**(nl-i-1)
        return n
    
    Cependant, certains n'ont même pas écrit une boucle, et ont construit "à la main" une forme adaptée aux séquences de, disons, longueur 5 et aucune autre. Or, il doit être évident, car je l'ai écrit noir sur blanc ! qu'il ne fallait pas confondre un exemple avec le problème. Votre programme aurait dû fonctionner avec une séquence quelconque. Si après un mois, on n'est toujours pas capable d'écrire une boucle, comment veut-on progresser?

  2. Voici la construction inverse. Un nombre entier est scindé algébriquement en chiffres. Le dernier est le reste de la division par 10. Le quotient subit la même manipulation, pour obtenir l'avant-dernier, etc.

    def numtoseq(n):
        l=[]
        while n>0:
            r=n%10
            l=[r]+l
            n=n//10
        return l
    
    et c'est tout. N'oubliez pas que l'opération // est la division entière, Euclidéenne, non pas flottante.


On peut aussi proposer une solution récursive, dans les deux sens.

def recston(s):
    if s==[]: return 0
    return 10*recston(s[:-1])+s[-1]

def recntos(n):
    if n==0: return []
    return recntos(n//10)+[n%10]
Observez l'élégante symétrie des deux fonctions.


Interrogation sur papier du 11.02.2015

  1. Lecture des nombres aléatoires, addition, statistique. Voici la solution :
    N=input("N=");  N=int(N)
    ll=[]
    for i in range(N):
        k=0
        somme=0
        while somme < 6:
            k+=1
            somme+=random()
        ll.append(k)  # ou ll+=[k]
    print(ll)
    
    dic={}
    for x in ll:
        if x in dic:
            dic[x]+=1
        else: dic[x]=1
    
    print(dic)
    

    Test :

    N=200
    [11, 16, 11, 10, 12, 16, 12, 9, 9, 10, 12, 8, 13, 16, 12, 13, 17, 13, 13, 11, 11, 13, 
    13, 13, 11, 13, 17, 14, 9, 14, 12, 10, 12, 10, 12, 11, 12, 14, 13, 16, 12, 13, 13, 9, 
    11, 16, 14, 14, 9, 16, 14, 11, 14, 14, 13, 11, 9, 13, 12, 15, 15, 18, 15, 10, 11, 12, 
    15, 14, 10, 10, 13, 11, 10, 14, 11, 12, 12, 17, 8, 11, 14, 14, 13, 10, 13, 9, 15, 15, 
    14, 12, 14, 16, 11, 14, 12, 14, 15, 13, 13, 14, 15, 10, 11, 12, 13, 14, 13, 10, 10, 
    11, 14, 13, 13, 14, 14, 12, 13, 14, 11, 12, 11, 20, 18, 8, 14, 12, 14, 11, 12, 12, 11, 
    16, 12, 10, 15, 9, 10, 15, 11, 12, 12, 10, 14, 11, 13, 11, 13, 12, 9, 15, 11, 12, 13, 
    12, 10, 14, 10, 11, 12, 11, 15, 16, 15, 11, 11, 11, 10, 10, 13, 10, 11, 11, 19, 10, 14, 
    12, 11, 12, 10, 13, 13, 13, 17, 12, 13, 10, 15, 10, 15, 13, 11, 14, 11, 16, 13, 14, 11, 
    12, 15, 12]
    

    >>> 
    >>> print(dic)
    {8: 3, 9: 9, 10: 24, 11: 35, 12: 33, 13: 33, 14: 29, 15: 16, 
    16: 10, 17: 4, 18: 2, 19: 1, 20: 1}
    >>> 
    

    Le nombre moyen des tirages oscille autour de 12 (ici : 12.52).
    C'est aussi le nombre le plus probable, mais les fluctuations existent.

    Voici comment se distribuent les occurrences, si on fait 1 000 000 tirages.

  2. On cherche les adresses mail en format xxxx@yyyy.fr avec les fragments avant et après l'arobase non-vides.

    Cet exercice teste surtout votre capacité de coder un peu de logique de manière rationnelle, sans écrire des phrases erronées syntaxiquement, ou sans aucun sens. La correction est très tolérante vis-à-vis des petites erreurs, mais une pseudo-solution chaotique, sans aucun sens, est rejetée tout de suite. La solution ci-dessous n'est pas la seule possible.

    nlim=279; k=0
    ans=False
    while not ans and k<nlim:
        k+=1
        s=input("Votre mail, SVP : ")
        if len(s)<6: continue
        if s[-3:]!=".fr": continue
        for i in range(len(s)-3):
            if s[i]=="@":
                a=s[:i];  b=s[i+1:-3]
                # print("**  ",a," | ",b)
                if (a!="") and (b!=""):
                    ans=True
                    break
    if ans:
        print("Merci : ",a,b)
    else:
        print("Vous n'êtes pas humain")
    

    Voici le test

    Votre mail, SVP : helloworld.fr
    Encore, Votre mail, SVP : c'est moi@.fr
    Encore, Votre mail, SVP : @allez...fr
    Encore, Votre mail, SVP : ok@jeme rends.fr
    Merci :  ok jeme rends
    >>> 
    


Exercices en TP du 25.02.2015

Exercice A.

Écrire une fonction qui parcourt une liste numérique, par ex. [2,3,7,10,-5,4,5,12,6,6,3,1,8,9], et qui construit une autre liste, composée de 1, 0 et -1. Quand la tendance dans la liste source est croissante, par ex.: ... 3, 7, ..., l'algorithme place 1 dans la liste-résultat. Si deux voisins sont identiques, par ex. ... 6, 6, l'élément qui sortira vers le résultat, sera 0. finalement, quant on se trouve dans un segment décroissant : ... 6, 3, ..., on stocke le nombre   -1 dans la liste résultante. Pour cet exemple on devra obtenir [1, 1, 1, -1, 1, 1, 1, -1, 0, -1, -1, 1, 1].

l=[2,3,7,10,-5,4,5,12,6,6,3,1,8,9] # ou autre...
def reg(l):
    x=l[0]
    buf=[]
    for y in l[1:]:
        if y>x: buf.append(1)
        elif y<x: buf.append(-1)
        else: buf.append(0)
        x=y
    return buf

On peut organiser les conditionnelles autrement, ceci ne changera pas le résultat.
Comme je vous ai dit, ceci est un modèle de contrôleur "thermostat" : quand la température monte, on refroidit, et quand elle baisse, on allume le chauffage.


Exercice B.

Ici il faut faire un peu de calcul mental, et je serai très, très tolérant. L'essentiel est de savoir ce que l'on veut faire. J'accepterai une "étincelle de pensée" quelconque, mais une pagaille sans aucun sens, non. Voici la solution.

def losange(n):
    m=[]
    mti=(n-1)//2  # la moitié de la taille (impaire)
    for i in range(n):
        if i<=mti: j=i  # d'abord croissant, ensuite décroissant
        else: j=n-i-1
        z=(mti-j)*[0]
        c=(2*j+1)*[1]
        m.append(z+c+z)
    return m

def losprint(m):
    n=len(m)
    for l in m:
        s=""
        for j in l:
            s+=str(j)  # Ramasser les chiffres ensemble
        print(s)


Exercice C.

Construire une "matrice 3D" (liste de listes de listes) contenant des nombres aléatoires réels entre 0 et 1 dans les listes les plus intérieures. La taille de l'objet : 3 × 2 × 4, c'est-à-dire : une liste de longueur 3 contenant 3 listes de longueur 2, dont chacune contient 2 listes de longueur 4, avec les nombres.

On peut, naturellement, construire cette matrice par des boucles standard Python, mais aussi avec les compréhensions. Il fallait éviter des incongruités, par exemple construire une liste simple, et l'utiliser comme 3-dimensionnelle. Je ne peux rien faire contre une manque flagrante de discipline logique...

Aussi, si en fin février, quelques uns ne sont toujours pas capables de distinguer entre random() et randint(...), c'est leur problème. Je vous ai dit une cinquantaine de fois de reviser.

Solution la plus compacte.

r=[[[random() for i in range(4)] 
    for j in range(2)] 
   for k in range(3)]

Je vous ai dit que c'est un exercice de 40 secondes...

Et voici la solution plus traditionnelle, avec des boucles for standard Python.
r=[]
for k in range(3):
    r1=[]
    for j in range(2):
        r2=[]
        for i in range(4):
            r2.append(random())
        r1.append(r2)
    r.append(r1)

Au moins une personne a essayé (sans trop de réussite) de former d'abord une matrice de zéros, et de la remplir avec le contenu final. Ceci est parfaitement OK comme idée, mais il faut avoir la discipline de programmation suffisante. Voici la solution.

r=[[4*[0] for j in range(2)] for i in range(3)]
for i in range(3):
    for j in range(2):
        for k in range(4):
            r[i][j][k]=random()

C'est tout.


Interrogation sur papier du 04.03.2015

  1. Étant donnée une liste numérique, par exemple [6, 0, 2, 9, 5, 4, 10, 7, 3], séparer-la en deux parties, les "éléments grands", et les "petits", par rapport au premier élément de la liste. Écrire une fonction qui implémente la solution de ce problème, et qui retourne le tuple contenant les deux listes-réponses. Technique : on prévoit deux tampons, et en parcourant la liste-source, on jette l'élément vu, comparé avec la tête de la liste, soit dans l'un soit dans l'autre tampon.

    Grâce aux compréhensions et le filtrage (la compréhension for avec if), le code peut se réduire à une seule ligne (un peu longue...). Construisez aussi ce code. Le résultat pour la source ci-dessus sera ([0, 2, 5, 4, 3],[9, 10, 7S]), mais l'ordre d'éléments dans les deux listes peut être différent.

    Voici la solution itérative classique avec deux tampons :

    def partition(l):
        a,b = [],[]
        p=l[0]
        for x in l[1:]:
            if x<=p: a.append(x)
            else: b.append(x)
        return (a,b)
    

    La solution avec les compréhensions, qui parcourt la liste-source deux fois, est :

    def partit(l):
        return ([p for p in l[1:] if p<=l[0]],[p for p in l[1:] if p>l[0]])
    


    Finalement, une troisième solution, assez différente des autres, elle était plus populaire quand les langages de programmation étaient plus "statiques" au sens de création des listes et autres structures de données de taille dynamique dans la mémoire. On préférait au nom de l'efficacité, le travail dans des tableaux de taille fixe. On modifiait ces tableaux, sans en créer d'autres.

    La solution ci-dessous parcourt le tableau (liste), et si l'élément courant est plus petit que le pivot (le premier élément), on l'échange avec un autre élément "marqueur", qui détermine la frontière entre les éléments petits et grands, et ensuite on déplace la frontière.

    def partit(l):
        j=1
        for i in range(1,len(l)):
            if l[i]<l[0]:
                l[i],l[j] = l[j],l[i]
                j+=1
        return (l[1:j],l[j:])
    

    Attention. Ceci n'est pas un exercice parmi milliers, mijoté uniquement pour fatiguer les étudiants. Cet algorithme est la partie essentielle d'un des meilleurs algorithmes connus pour trier les listes/tableaux (surtout si on sait que la source est vraiment désordonnée) : le TRI RAPIDE, ou Quicksort. Il suffit d'y ajouter un peu de récursivité. Ceci sera discuté en détails, car connaître quicksort est tout simplement obligatoire à tous ceux qui sont proches de l'informatique.

    Quelques références :

    Tri rapide

    Voici l'usage de notre fonction "partition" pour trier les listes. Le programme vous a été présenté, et dans votre intérêt est de l'apprendre, car c'est un des algorithmes fondamentaux de tri.
    Voici la procédure principale, récursive.
    def qsort(l):
        if len(l)<=1: return l
        p,a,b = partition(l)
        return qsort(a)+[p]+qsort(b)
    

    On peut, bien sûr, optimiser cet algorithme. D'abord, on peut incorporer partition dans qsort ("inlining"), en éliminant les appels fonctionnels inutiles. Mais un gain plus conséquent serait possible, si on éliminait la concaténation récurrente des sous-listes. La concaténation : l1 + l2 en Python n'est pas destructrice, il faut donc recopier intégralement les deux. Si ceci se déroule plusieurs fois, lors de chaque partition récursive, la gestion de mémoire devient assez onéreuse. Si on transformait les listes sur place, on pourrait accélérer la procédure. Ceci n'est pas envisageable en Python, car la structure du code se complique, et la vitesse d'exécution du code interprété pénalise l'algorithme. Ainsi, malgré le fait que la solution avec les compréhensions parcourt la liste deux fois, ce parcours se fait par une boucle interne, plus rapide, et l'algorithme devient aussi efficace que l'original, qui parcourt la liste une fois, mais dont le code est plus lent...


  2. Étant donnée une liste numérique, par ex. [2, 3, 7, 10, -5, 4, 5, 12, 6, 6, 3, 1,8, 9] écrire une fonction qui trouve les deux valeurs plus grandes dans la liste. La procédure retourne le tuple qui contient la valeur maximale, et la seconde valeur la plus grande, ici : (12,10). La liste est parcourue une seule fois, et ne subit aucune modification. Par contre, il faut opérer avec deux variables de travail, locales, qui stockent les valeurs mis à jour lors du parcours de la liste.

    Voici une possible solution :

    def maxx(l):
        m1=m2=l[0]
        for x in l[1:]:
            if x>m1: 
                m2=m1
                m1=x
            else:
                if x>m2: m2=x
        return (m1,m2)
    
    Si on abandonne les contraintes que la liste ne soit pas modifiable, et qu'un seul parcours effectue le travail, la solution plus courte serait
    def maxx(l):
        p=max(l); l.remove(p)
        return (p,max(l))
    


Exercices en TP du 18.03.2015

Voici les données.

comp = [{'a_mort': 1791, 'a_naiss': 1756, 'comp': 'Mozart'}, 
 {'a_mort': 1827, 'a_naiss': 1770, 'comp': 'Beethoven'}, 
 {'a_mort': 1759, 'a_naiss': 1685, 'comp': 'Haendel'}, 
 {'a_mort': 1828, 'a_naiss': 1797, 'comp': 'Schubert'}, 
 {'a_mort': 1741, 'a_naiss': 1678, 'comp': 'Vivaldi'}, 
 {'a_mort': 1643, 'a_naiss': 1567, 'comp': 'Monteverdi'}, 
 {'a_mort': 1849, 'a_naiss': 1810, 'comp': 'Chopin'}, 
 {'a_mort': 1750, 'a_naiss': 1685, 'comp': 'Bach'}]

oeuvres = [{'interpr': 'T. Pinnock', 'titre': 'Les quatre saisons', 'duree': 20, 'comp': 'Vivaldi'}, 
 {'interpr': 'M. Perahia', 'titre': 'Concerto piano N°12', 'duree': 25, 'comp': 'Mozart'}, 
 {'interpr': 'A. Grumiaux', 'titre': 'Concerto violon N°2', 'duree': 40, 'comp': 'Brahms'}, 
 {'interpr': 'W. Kempf', 'titre': 'Sonate "au clair de lune"', 'duree': 14, 'comp': 'Beethoven'}, 
 {'interpr': 'W. Kempf', 'titre': 'Sonate "pathétique"', 'duree': 17, 'comp': 'Beethoven'}, 
 {'interpr': 'SE of London', 'titre': 'Quintette "la truite"', 'duree': 39, 'comp': 'Schubert'}, 
 {'interpr': 'H. Von Karajan', 'titre': 'La création', 'duree': 109, 'comp': 'Haydn'}, 
 {'interpr': 'M.J. Pires', 'titre': 'Concerto piano N°1', 'duree': 42, 'comp': 'Chopin'}, 
 {'interpr': 'P. Burmester', 'titre': 'Toccata & fugue', 'duree': 9, 'comp': 'Bach'}, 
 {'interpr': 'M. Pollini', 'titre': 'Concerto piano N°4', 'duree': 33, 'comp': 'Beethoven'}, 
 {'interpr': 'F. Bruggen', 'titre': 'Symphonie N°40', 'duree': 29, 'comp': 'Mozart'}, 
 {'interpr': 'S. Richter', 'titre': 'Concerto piano N°22', 'duree': 35, 'comp': 'Mozart'}, 
 {'interpr': 'S. Richter', 'titre': 'Concerto piano N°3', 'duree': 37, 'comp': 'Beethoven'}]

Exercice A.

Calculer la durée moyenne des morceaux composés par les compositeurs vivants au 17-ème siècle (c'est-à-dire, vivant au moins une année entre 1601 et 1700). Comment cette condition se traduit par les contraintes concernant l'année de naissance et l'année de la mort?

Pour modulariser un peu la solution, voici, séparément, le critère de sélection du compositeur :

def crit(na,mo):
    return not (mo<1600 or na>1700)
Et voici le reste, et la solution :
def exA():
    co=[i['comp'] for i in comp if crit(i['a_naiss'],i['a_mort'])]
    dur=[]
    for x in oeuvres:
        if x['comp'] in co:
            dur.append(x['duree'])
    return sum(dur)/len(dur)

print(exA())

14.5


Exercice B.

Trouvez, et afficher les titres des morceaux qui ont été interprétés par un artiste qui en a joué au moins deux. [Dans notre exemple la réponse doit être : les deux Sonates et le concert no. 3 de Beethoven, et le concert no. 22 de Mozart]. Trouvez d'abord les interprètes qui ont participé à plus d'une représentation [Wilhelm Kempf et Sviatoslav Richter]. Un autre exercice d'aujourd'hui (lequel?) peut vous être utile.

Pour trouver l'interprète qui a exécuté au moins deux morceaux, l'analyse fréquentielle s'avère utile.

def exB():
    d={}
    for x in oeuvres:  # Analyse fréquentielle
        art=x['interpr']
        d[art]=1+d.get(art,0)
    l=[a for a in d if d[a]>1]
    print("***>> ",l)   # pour tester !!
    for x in oeuvres:
        if x['interpr'] in l:
            print(x['titre'])

exb()


***>>  ['W. Kempf', 'S. Richter']

Sonate "au clair de lune"
Sonate "pathétique"
Concerto piano N°22
Concerto piano N°3
Si vous n'aimez pas dic.get(...), utilisez une conditionnelle classique.


Exercices en TP du 25.03.2015

Exercice A.

Vous disposez d'un fichier textuel Pays.txt, contenant les noms des pays (européens, aussi partiellement), et leurs capitales avec la population. La largeur des colonnes est fixe (si vous voyez des caractères mal codés, vérifiez si le navigateur utilise le codage UTF-8 ! en cas de problème, appelez-moi), et c'est la première ligne qui contient les mots : "PAYS", "CAPITAL", et "POPULATION", qui détermine la colonne où commence un champ. Donc, commencez par la lecture de la première ligne, et utilisez la méthode .find(...) afin de trouver l'indice où commence le nom de la capitale, ou la population. Ensuite :

Conseil. Afin d'éliminer les espaces et les fins de ligne qui traînent à la fin (et/ou en début) d'une chaîne, vous pouvez utiliser la méthode .strip() . La documentation des fonctions prédéfinies est ici, les méthodes des chaînes : ici.

Solution

Voici la lecture du fichier, et l'affichage du nombre de lignes.

ff=open('../Pays.txt','r',encoding='utf-8')
ls=ff.readlines()
print(len(ls))
ff.close()
L'identification des champs avec le nom de la capitale, et la population :
pa=ls[0]
icap=pa.find('CAPITAL')
ipop=pa.find('POPU')
Le scanning et l'affichage des lignes partielles :
l=[]
for li in ls[1:]:
    cap=li[icap:ipop].strip()
    pop=int(li[ipop:])
    l.append([cap,pop])
... Et le bonus :
def secondel(x): return x[1]

aa=max(l,key=secondel)
print(aa)


Exercice B.

Vous avez à votre disposition un autre fichier textuel : convert.txt, qui contient le descriptif d'une monnaie suivi par le code international de cette devise, par ex. (EUR), (CZK), (UAH), etc. : trois lettres entre parenthèses. Le reste de la ligne contient le taux d'échange /euro, mais en format français, avec la virgule, par ex. : 7,4544 . Ceci (dans la configuration par défaut) n'est pas accepté par Python comme la représentation d'un nombre flottant. Vous allez écrire un programme qui fait le suivant :

Solution

Voici la solution complète.

gg=open('../convert.txt','r',encoding='utf-8')
cv=gg.readlines()
gg.close()

dct={}
for li in cv:
    id=li.find('(')
    cod=li[id:id+5]
    num=li[id+6:].strip()
    num=num.replace(',','.')
    dct[cod]=float(num)

print(dct)
ce, qui affiche :
{'(ETB)': 22.238, '(KYD)': 0.8935, '(ANG)': 1.9505, '(AMD)': 521.2545, '(MUR)': 39.929, 
 '(JOD)': 0.7721, '(XPF)': 119.6744, '(PLN)': 4.119, '(NPR)': 108.5573, '(XOF)': 657.8406, 
 '(LYD)': 1.51, '(BMD)': 1.0897, '(MMK)': 1125.52, '(LKR)': 144.017, '(RSD)': 120.1666, 
 '(INR)': 67.8324, '(QAR)': 3.9672, '(UYU)': 27.4407, '(AUD)': 1.3944, '(COP)': 2805.584, 
 '(MYR)': 3.9894, '(BWP)': 10.8005, '(SGD)': 1.4957, '(KHR)': 4332.967, '(EUR)': 1.0, 
 '(JMD)': 125.2576, '(PYG)': 5265.46, '(CUP)': 1.0897, '(ISK)': 149.3556, '(VND)': 23455.15, 
 '(AED)': 4.0017, '(LSL)': 13.1187, '(TTD)': 6.918, '(GMD)': 46.7469, '(NZD)': 1.4294, 
 '(BRL)': 3.4947, '(KES)': 100.2308, '(ARS)': 9.571, '(SEK)': 9.3165, '(NOK)': 8.664, 
 '(CAD)': 1.3662, '(THB)': 35.4359, '(DKK)': 7.4544, '(GTQ)': 8.3136, '(BIF)': 1710.782, 
 '(LBP)': 1624.392, '(HRK)': 7.6453, '(KWD)': 0.3263, '(CHF)': 1.059, '(IDR)': 14163.53, 
 '(SAR)': 4.0854, '(VEF)': 6.8639, '(UAH)': 23.7692, '(PHP)': 48.8037, '(CZK)': 27.3632, 
 '(JPY)': 130.4965, '(USD)': 1.0896, '(BND)': 1.4962, '(SCR)': 14.867, '(MWK)': 472.3448, 
 '(IQD)': 1257.97, '(HKD)': 8.4498, '(NIO)': 29.3029, '(BDT)': 84.1754, '(UGX)': 3230.872, 
 '(BGN)': 1.9558, '(NGN)': 217.6616, '(SZL)': 13.1187, '(DOP)': 48.6974, '(RUB)': 64.3033, 
 '(BBD)': 2.1793, '(PKR)': 111.2553, '(XCD)': 2.9421, '(TWD)': 34.1622, '(ILS)': 4.3696, 
 '(SOS)': 764.4591, '(LTL)': 3.1971, '(MXN)': 16.316, '(MOP)': 8.7042, '(KZT)': 202.695, 
 '(GHS)': 4.0972, '(RON)': 4.4223, '(CRC)': 580.1405, '(HNL)': 23.3407, '(LAK)': 8773.812, 
 '(CNY)': 6.771, '(HUF)': 304.155, '(EGP)': 8.3028, '(ZMW)': 8.3578, '(IRR)': 30504.22, 
 '(GBP)': 0.7317, '(CLP)': 685.0776, '(ZAR)': 13.0615, '(KRW)': 1210.887, '(PAB)': 1.0897, 
 '(OMR)': 0.4194, '(DZD)': 104.9374, '(MAD)': 10.7194, '(XAF)': 657.8406, '(BSD)': 1.0897, 
 '(DJF)': 193.7924, '(RWF)': 750.7828, '(PEN)': 3.3605, '(BOB)': 7.5296, '(BYR)': 16072.64, 
 '(CVE)': 111.2444, '(TRY)': 2.7889, '(FJD)': 2.2221, '(MDL)': 20.0409, '(TZS)': 2004.395, 
 '(BZD)': 2.1739, '(BHD)': 0.411, '(HTG)': 51.3359, '(TND)': 2.1126}


Interrogation sur papier du 01.04.2015

Vous avez déjà vu le graphe à droite, et son implémentation Python (sans marquage des arcs) sous forme de dictionnaire : {'A': ['H', 'C'], 'B': ['A'], 'C': ['G', 'E'], 'D': ['B'], 'E': [], 'F': ['B'], 'G': [], 'H': []}.

Le graphe augmenté par les étiquettes sur les arcs représente les dettes des individus dans une communauté. Ainsi A doit 2 grounx à C, C doit 4 grounx à E et 1 grounx à G, B doit 7 grounx à A, etc. Ces donnés sont représentées par une liste de tuples : [('A','C',2), ('A','H',3), ..., ('F', 'B',5), ('D', 'B', 2),...].

A. Écrire un programme qui prend le graphe original (non marqué) et la liste de dettes, et qui construit le marquage, une autre rerésentation du graphe. Ce sera un dictionnaire de clés-noeuds, comme auparavant, mais les valeurs associées sont des dictionnaires représentant les dettes de chaque noeud débiteur : {'A': {'H':3, 'C':2}, 'B':{'A':7}, 'C':{'G':1, 'E':4} ...}. (Le débiteur connaît ses créanciers, l'inverse n'est pas vrai).

Solution.

gr={'A': ['H', 'C'], 'B': ['A'], 'C': ['G', 'E'], 
    'D': ['B'], 'E': [], 'F': ['B'], 'G': [], 
    'H': []}

detlist=[('A','C',2), ('A','H',3), ('B','A',7), ('C','G',1),
         ('C','E',4), ('D','B',2), ('F','B',5)]

for db in gr:
    v={}
    for cr in gr[db]:
        for (d,c,n) in detlist:
            if d==db and c==cr:
                v[c]=n
    gr[db]=v

Attention. Certains ont essayé de reconstruire le graphe de dettes en utilisant seulement la liste de dettes, sans le graphe d'origine. Ceci est faisable, mais il faut tenir compte du fait que cette liste ne contient pas des noeuds qui ne sont pas des débiteurs (seulement créanciers, comme E, G, et H). Le programme doit les installer, avec les descendants vides :

tm={}
for (nd,ci,n) in detlist:
    if nd not in tm: tm[nd]={}
    di=tm[nd]
    di[ci]=n
    if ci not in tm: tm[ci]={}


B. Écrire une procédure (fonction Python) qui étant donné un noeud (son étiquette), trouve sa dette globale. Il faut accumuler ses dettes, mais également chercher tous ses débiteurs, et déduire la summe qui lui est due, de sa dette propre. Le graphe de dettes est global et possède la représentation construite par l'ex. A.

Solution.

def globdet(nd):
    cr=gr[nd]
    sd=sum([cr[c] for c in cr])
    for x in gr:
        ncr=gr[x]
        if nd in ncr:
            sd-=ncr[nd]
    return sd

Question-piège. Supposons que l'on parcourt le graphe, et accumule dans un tampon la dette globale de chacun. Le résultat, la somme, est une valeur négative, disons -10 grounx. Qu'est-ce que cela signifie?

La réponse à la "question piège" est : ceci n'est pas possible. L'explication est que le programme serait bogué... Si le bilan : bil=sum([globdet(nd) for nd in gr]) n'était pas zéro, cela signifierait que dans un Univers fermé, la matière n'est pas conservée.


Retour à l'index