% Programmation avec Python (chapitre 5)
% Dimitri Merejkowsky


#

\center \huge Suite de l'atelier précédent

# Rappel

Travail de la dernière fois.

Les problèmes que j'ai vus:

* noms
* méthodologie

#

\center \huge naming

# Quelques principes

L'un des problèmes les plus compliqué de l'informatique

* Tout en anglais (sorry)
* Pas d'abréviation
* Pas de redondance

# Exemples

* `get_freq` -> `get_frequencies`
* `nom_fich` -> `filename`
* `list_frag` -> `fragments`  # le pluriel nous dit que c'est une liste
* `liste_mot` -> `words`

Essayez de rester cohérents!

# Exemples (2)

```python
# Avant
for fragment in list_frag:
   fragment_min = fragment.lower()
   frag_clean = clean(fragment_min)
   liste_mot.append(frag_clean)
```

```python
# Après
for fragment in fragments:
   fragment = fragment.lower()
   fragment = clean(fragment)
   words.append(fragment)
```

On peut ré-utiliser le même nom plusieurs fois!

#

\center \huge Style

# Espaces - exemple

\vfill


```python
def foo(a, b):
    a = 42
    bar(a, spam=False)


def bar(a, spam=True):
    ...

```

# Espaces - règles


* Deux lignes entre chaque fonction
* Des espaces autour des `=` pour les affectations
* Pas d'espace quand c'est un argument nommé
* Exactement un espace après chaque virgule

Question de convention. Voir PEP8.

#

\center \huge Repartons du début

\normalsize (un genre de corrigé)

# Revoir les changements chez vous

Vous pourrez voir la suite des changements sur github:

https://github.com/E2L/cours-python/tree/master/sources/topwords/


# Note importante

En programmation, il y a très rarement une seule
bonne réponse !

Ceci n'est qu'une solution possible, et une *technique possible*
pour arriver à cette solution ...


# Découper les mots

Dans la liste se trouvent des mots mal découpés:

* `peutétre`
* `lhumanité`

Il faut revoir pas mal de code! (comme souvent après la découverte
d'un bug).

On va essayer d'être plus méthodique.

# Tester chaque fonction une par une

* Spécification
* Test
* Implémentation

# Découpage en fragments - spécification

On a:

> L'univers est, peut-être, « infini! »

On veut:

```python
["l", "univers", "est", "peut-être", '«',  "infini", '»']
```

On s'occupera d'enlever la ponctuation plus tard.

# Découpage en fragments - test

```python
contents = "L'unives est, peut-être, infini!"
fragments = split_fragments(contents)
print(fragments)
```

# Découpage en fragments - implémentation

```python
def split_fragments(contents):
    res = list()
    for fragment in contents.split():
        if "’" in fragment:
            (before, after) = fragment.split("’")
            res.append(before)
            res.append(after)
         else:
            res.append(fragment)
    return res
```

# Découpage en mots - spécification

On a:

```
L'unives est, peut-être, « infini! »
```

On veut:

```
['l', 'unives', 'est', 'peut-être', 'infini']
```

# Découpage en mots - test

```python
contents = "L'univers est, peut-être, « infini! »"
words = split_words(contents)
print(words)
```

# Découpage en mots - implémentation

On peut reprendre le code vu ensemble,
avec quelques modifications:

```python
def split_words(text):
   fragments = split_fragments(text)
   res = list()
   for fragment in fragments:
      fragment = fragment.lower()
      fragment = clean_fragment(fragment)
      if fragment:   # le fragment peut-être vide ici
         res.append(fragment)
   return res
```

# clean_fragment()

Juste une petite modification:

```python
def clean_fragment(fragment):
    result = ""
    for c in fragment:
        # autorise les tirets et apostrophes à l'intérieur
        # des mots
        if c.isalpha() or c in ["-", "'"]:
            result += c
    return result
```

# Calcul des fréquences - spécification

On a:

```python
["pomme", "poire", "banane", "poire", "banane", "banane"]`
```

On veut:

```python
{"pomme": 1, "poire": 2, "banane": 3}
```

\vfill

Note: par encore d'ordre ici!

# Calcul des fréquences - test


```python
words = [
   "pomme", "poire", "banane",
   "poire", "banane", "banane"
]
frequencies = get_frequencies(words)
print(frequencies)
```

# Calcul des fréquences - implémentation


```python
def get_frequencies(words):
   res = dict()
   for word in words:
      # Si le mot est déjà là, on incrémente
      # son compteur:
      if word in res:
         res[word] += 1
      else:
         # Sinon on crée une nouvelle clé
         # avec la valeur 1
         res[word] = 1
   return res
```


# Tri - spécification

On a:

```python
{'pomme': 1, 'poire': 2, 'banane': 3}
```

On veut:

```python
[
   (3, 'banane'),
   (2, 'poire'),
   (1 'pomme'),
]
```

# Tri - test

```python
frequencies = {'pomme': 1, 'poire': 2, 'banane': 3}
scores = get_scores(frequencies)
print(scores)
```


# Tri - implémentation

```python
def get_scores(frequencies):
   res = list()
   for word, count in frequencies.items():
      res.append((count, word))
   res.sort(reverse=True)
   return res
```

# Affichage des résultats

On a :

```python
scores = [
   (20, "banane"),
   (19, "poire"),
   ...
]
```

On veut:

```
20 banane
19 poire
```

# Affichage des résultats - test

```python
scores = [
   (20, "banane"),
   (19, "poire"),
   ...
]
print_scores(scores)
```

# Affichage des résultats - implémentation


```python
def print_scores(scores):
   for count, word in scores:
      print(count, word)
```

# Assemblons les morceaux

```python
def main():
   filename = "ruffin.txt"
   file = open(filename)
   contents = file.read()
   words = split_words(contents)
   frequencies = get_frequencies(words)
   scores = get_scores(frequencies)
   top_words = scores[:20]  # le top 20
   print_scores(top_words)

main()
```

# Touches finales

C'est bien si le nom du fichier est lu depuis la ligne de commande:

```python
import sys

def main():
   if len(sys.argv) < 2:
      sys.exit("not enough arguments")
   filename = sys.argv[1]
   ...
```

#

\center \huge Compléments - tris


# sort() - ordre naturel

```python
>>> nombres = [2, 3, 1, 5]
>>> nombres.sort()
>>> nombres
[1, 2, 3, 5]
```

Notez que la liste est modifiée *sur place*.

# sort() - ordre alphabétique

```python
>>> mots = ["abeille", "faucon", "chat"]
>>> mots.sort()
>>> mots
['abeille', 'chat', 'faucon']
```

# sort() - ordre lexicographique

Pour chaque "liste-élément" on compare le premier élément.
S'il y a égalité, on regarde le deuxième élément, etc:

```python
>>> composite = [["chat", 1], ["abeille", 2], ["chat", 3]]
>>> composite.sort()
>>> composite
[['abeille', 2], ['chat', 1], ['chat', 3]]
```

L'ordre alphabétique est l'ordre lexicographique pour les chaînes de caractères :)

# Attention!

Tous les éléments de la liste doivent être comparables deux à deux:

\vfill

```python
>>> mauvaise_liste = ["un", 2]
>>> mauvaise_liste.sort()
TypeError
```


# Comparer autrement

Exemple: trier les mots par leur taille avec l'argument `key`

\vfill

```python
def taille(mot):
    return len(mot)

mots = ["chat", "abeille", "faucon"]
mots.sort(key=taille)
>>> mots
["chat", "faucon", "abeille"]
```

# Lambda

Sert définir une fonction sans utiliser `def`

```python
>>> retourne_42 = lambda: 42  # pas d'argument
>>> retourne_42()
42
>>> ajoute_deux = lambda x: x + 2  # un seul argument
>>> ajoute_deux(3)
5
>>> multiplie = lambda x, y: x* y  # deux arguments
>>> multiplie(2, 3)
6
```
Note: le corps de la fonction doit tenir en une seule ligne

# Utilisation avec sort

```python
>>> mots = ["chat", "abeille", "faucon"]
>>> mots.sort(key=lambda x: len(x))
>>> mots
["chat", "faucon", "abeille"]
```


# sorted()

Si on a besoin de l'ordre initial après coup:

```python
b = a.copy()
b.sort()

# ou:
b = sorted(a)
```

#


\center \huge Compléments - fichiers


# Rappel: lire

```python
file = open("toto.txt", "r")   # 'r' comme 'read'
contenu = file.read()
file.close()
```

Note: le fichier `toto.txt` doit exister!

# Écrire

On peut écrire tout le contenu d'un coup:

```python
contenu = "du texte à sauvegarder"
file = open("article.txt", "w") # 'w' comme 'write'
file.write(contenu)
file.close()
```


* Le fichier `article.txt` sera écrasé s'il existe déjà.
* N'oubliez surtout pas d'appeler `close()`

# Que faire en cas d'erreur ?

```python
file = open("article.txt", "w") # 'w' comme 'write'
# ... beacoup de code ici
# ... < une erreur
file.close()
```

S'il y a une erreur entre `open()` et `close()`, le fichier ne sera pas fermé!


# Le mot-clé with

```python
with open("toto.txt", "w") as file:
    file.write("du texte")
```

Quand on sort du bloc `with` on a la garantie que `file.close()` sera appelé,
*même* si on sort du bloc à cause d'une erreur.

# Convention

Il n'y a maintenant plus aucune raison d'appeler `.close()` "à la main",
donc ne le faites pas et utilisez `with`.

# Lire et écrire des lignes

Très courant:

```python
with open("toto.txt", "r") as file:
    lignes = file.readlines()

# faire quelque chose avec la liste de lignes

with open("toto.txt", "w") as file:
    file.writelines(lignes)
```

Pensez à fermer le premier fichier avant d'ouvrir le second.
(ça marche même s'ils ont le même nom)

#

\center \huge Atelier 2

# Problème à résoudre

Garder une liste de scores persistent dans
le jeu du pendu

# Point de départ

https://github.com/E2L/cours-python/tree/master/sources/hangman

* Version modifiée du pendu (grâce aux suggestions de certains
  d'entre vous)
* Préparation d'un squelette isolé

# Pour la prochaine fois

* Essayez d'implémenter la gestion des scores multi joueurs
  dans `hangman.py` en partant de `scores.py`.

* Envoyez moi vos solutions par e-mail :)
