PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

De l’intérêt des tuples comme clé de dictionnaire

samedi 21 décembre 2013 à 09:18

On peut utiliser n’importe quel objet hashable comme clé de dictionnaire en Python, pas uniquement des strings. Donc des entiers bien entendu, mais également, et c’est rarement utilisé, des tuples.

Imaginez que vous ayez une structures de données ainsi initialisée :

from random import choice, randint
 
tags = ('personne', 'animal', 'objet')
depart = {}
 
depart = {'%s_%s' % (choice(tags), randint(0, 10)): None for x in range(10)}

Cela donne quelque chose comme ça :

{u'personne_6': None,
 u'personne_5': None,
 u'objet_9': None,
 u'objet_6': None,
 u'objet_4': None,
 u'personne_8': None,
 u'objet_2': None,
 u'objet_0': None,
 u'animal_8': None}

On voit que les clés ont ici une valeur sémantique importante : elles sont porteuses de sens.

Si vous voulez la liste des nombres utilisés, il va vous falloir changer votre structure de données : en avoir plusieurs séparées, probablement. Ou alors faire de la manipulation de chaîne à base de split() et de casting.

Par contre, si vous utilisez un tuple comme clé, vous avez le même format pour votre dictionnaire depart, avec les mêmes possibilités, mais en plus un accès aux clés plus complet.

Déjà le code de génération est plus simple :

depart = {(choice(tags), randint(0, 10)): None for x in range(10)}

Ce qui donne :

{(u'animal', 2): None,
 (u'personne', 5): None,
 (u'personne', 4): None,
 (u'objet', 6): None,
 (u'objet', 10): None,
 (u'animal', 7): None,
 (u'animal', 1): None,
 (u'animal', 10): None,
 (u'personne', 8): None}

Mais en prime, on peut faire ça :

for (tag, number), value in depart.items():
    print tag, number, value
 
## animal 2 None
## personne 5 None
## personne 4 None
## objet 6 None
## objet 10 None
## animal 7 None
## animal 1 None
## animal 10 None
## personne 8 None

Bref, quand vos clés ont une valeur sémantique importante, pensez à utiliser des tuples, voir carrément, un namedtuple, qui est une structure de données trop souvent ignorée alors qu’elle est très puissante, et peut remplacer bien des classes conteneurs.

flattr this!

Changement dans l’unpacking des iterables en Python 3

vendredi 20 décembre 2013 à 08:49

Ahhh, l’unpacking… On croit qu’on a complètement fait le tour de cette fonctionalité merveilleuse, et PAF, on découvre encore autre chose.

Par exemple, la syntaxe a été améliorée avec Python 3, et accepte maintenant un unpacking partiel !

Ca se fait en l’utilisant l’opérateur splat, c’est à dire l’étoile :

>>> l = list(range(5))
>>> l
[0, 1, 2, 3, 4]
>>> a, *b = l
>>> a
0
>>> b
[1, 2, 3, 4]
>>> a, *b, c = l
>>> a
0
>>> b
[1, 2, 3]
>>> c
4

Ca marche bien entendu également dans les boucles for.

flattr this!

Article retiré pour cause de grosse merde

jeudi 19 décembre 2013 à 09:10

Désolé pour ceux qui ont reçu l’article via RSS ou email. Je le retire. C’était de la merde.

flattr this!

Solution de l’exercice d’hier

mardi 17 décembre 2013 à 10:17

Il faut bien noter que ce n’est qu’une solution parmi d’autres :

import re
import sys
import string
import unicodedata
 
mots = {}
texte = open(sys.argv[1]).read().decode('utf8').replace(u'œ', 'oe')
texte = unicodedata.normalize('NFKD', texte).encode('ascii', 'ignore')
texte = re.sub('[^%s]' % string.ascii_lowercase, ' ', texte.lower())
 
for i, e in enumerate(texte.split()):
    mots.setdefault(e, []).append(i)
 
mots = sorted(mots.items(), key=lambda x: (len(x[1]), sorted(x[1])))
 
for mot, positions in mots:
    print('- %s: %s' % (mot, ', '.join(map(str, positions))))

On ignore cordialement toute gestion d’erreur, donc le code peut se permettre d’être court. Et .replace(u'œ', 'oe') n’est pas très générique :-)

Dans les propositions de code des commentaires, il faut noter :

Décorticage :

import re
import sys
import string
import unicodedata
 
# On va tocker les mots dans ce dico
mots = {}
 
# Je récupère en vrac le contenu du fichier. Comme on a pas de gestion des
# erreurs, je récupère cash pistache le chemin de la ligne de commande
# et je suppose un encoding en UTF8. Le résultat obtenu est un objet
# unicode de tout le texte du fichier, sans le caractère 'œ'.
texte = open(sys.argv[1]).read().decode('utf8').replace(u'œ', 'oe')
 
# Astuce pour normaliser les caractères spéciaux. Ne marche que pour 
# l'alphabet latin malheureusement. Donc le script est limité. Unidecode
# permettrait d'avoir un script plus générique.
texte = unicodedata.normalize('NFKD', texte).encode('ascii', 'ignore')
 
# string.ascii_lowercase contient toutes les lettres ASCII en minuscule,
# ce qui permet de faire un remplacement, via regex, de 
# [^abcdefghijklmnopqrstuvwxyz]', c'est à dire tout ce qui n'est pas
# une lettre ASCII minuscule.
texte = re.sub('[^%s]' % string.ascii_lowercase, ' ', texte.lower())
 
# Je récupère tous les "mots", split() sans paramètre coupe en effet toute 
# combinaison de caractères non imprimables. enumerate() me permet d'avoir
# la position de chaque mot. setdefault() me permet d'ignorer les clés qui
# n'existent pas encore dans le dico. J'aurais pu utiliser un defaultdict, mais
# comme on a qu'une seule ligne ici, c'est plus court.
# J'obtiens donc un dico {mot1: [positon1, position2, ...], mot2: ...}
for i, e in enumerate(texte.split()):
    mots.setdefault(e, []).append(i)
 
# On récupère le contenu du dico sous forme de liste de tuples 
# [(mot, positions)...], et on l'ordonne selon le nombre d'apparitions
# (len(x[1])), ou a défaut par ordre naturel des apparitions sorted(x[1]).
# Pour rappel, key attend une fonction qui prend chaque élement, et retourne
# une clé. La clé est utilisée pour ordonner les éléments : chaque élément
# voit sa clé comparée à celle des autres, et ordonnée par ordre naturel.
# Y a un article sur ça : http://sametmax.com/ordonner-en-python/
# En gros, une entrée ('salut', 4, 18) aura pour clé (2, (4, 18)),
# ce que Python peut comparer facilement.
# Je réalise en rédigeant ces lignes que mon sorted est inutile, puisque 
# le processus est incrémental et déjà ordonné. Je le laisse comme référence.
mots = sorted(mots.items(), key=lambda x: (len(x[1]), sorted(x[1])))
 
# Et on affiche tout ça, non sans caster les positions du type int vers str
# pour éviter un crash
for mot, positions in mots:
    print('- %s: %s' % (mot, ', '.join(map(str, positions))))

Enoncé de l’exercice.

Télécharger le code de l’article.

flattr this!

Petit exercice en Python

lundi 16 décembre 2013 à 08:27

Nouveau concept, de temps en temps, je vais proposer un exercice à faire en Python, vous postez vos solutions, et j’en posterai une le lendemain.

Pas de pression, c’est pour le fun.

Les solutions dans un autre langage sont bienvenues, mais privilégiez Python si vous avez le choix, c’est quand même le but.

Postez votre code sur un pastebin ou ailleurs avec un lien en commentaire, pas le code directement dans les commentaires.

Exercice du jour :

Un script qui attend un fichier en paramètre, l’ouvre, et trouve toutes les positions de chaque mot.

Le script doit prendre en compte les apostrophes, supprimer la ponctuation, et normaliser la casse et les caractères spéciaux des mots.

Le résultat doit afficher une liste à de mots avec leurs positions ordonnée par le nombre d’apparition, ou en cas d’égalité, par ordre naturel des positions.

Aucune gestion d’erreur n’est demandée.

Par exemple si j’ai un fichier contenant :

« La marche des vertueux est semée d’obstacles qui sont les entreprises égoïstes que fait sans fin surgir l’œuvre du Malin. Béni soit-il l’homme de bonne volonté qui, au nom de la charité, se fait le berger des faibles qu’il guide dans la vallée d’ombre, de la mort et des larmes, car il est le gardien de son frère et la providence des enfants égarés. J’abattrai alors le bras d’une terrible colère, d’une vengeance furieuse et effrayante sur les hordes impies qui pourchassent et réduisent à néant les brebis de Dieu. Et tu connaîtras pourquoi mon nom est l’éternel quand sur toi s’abattra la vengeance du Tout-Puissant ! »

Ça fait des années que je répète ça. L’enfoiré qui l’entend, il meurt aussitôt. J’avais jamais cherché à comprendre, je trouvais seulement que ça en jetait de dire ça avant de flinguer un mec. Et puis ce matin, j’ai vu quelque chose qui m’a fait réfléchir. D’un seul coup, je me dis, ça pourrait bien vouloir dire que tu es l’œuvre du malin, et que l’homme vertueux c’est moi, et que mon joli 9 mm ce serait mon protecteur, mon berger dans la vallée de l’angoisse et des larmes. Ou encore mieux, c’est moi le berger et toi l’homme vertueux, et c’est le monde qui est l’œuvre de Lucifer. Qu’est-ce que tu dis de ça ? Mais rien de tout ça n’est juste. Ce qui est vrai, c’est que tu es le faible et que je suis la tyrannie des méchants. Et moi j’essaie, Ringo, au prix d’un effort harassant, de protéger les faibles.

On appelle le script ainsi :

python ton_script.py ton_fichier.txt

Et il affiche ceci :

- marche: 1
- semee: 5
- obstacles: 7
- sont: 9
- entreprises: 11
- egoistes: 12
- sans: 15
- fin: 16
- surgir: 17
- beni: 22
- soit: 23
- bonne: 28
- volonte: 29
- charite: 35
- se: 36
- guide: 44
- ombre: 49
- mort: 52
- car: 56
- gardien: 60
- son: 62
- frere: 63
- providence: 66
- enfants: 68
- egares: 69
- abattrai: 71
- alors: 72
- bras: 74
- terrible: 77
- colere: 78
- furieuse: 82
- effrayante: 84
- hordes: 87
- impies: 88
- pourchassent: 90
- reduisent: 92
- neant: 94
- brebis: 96
- dieu: 98
- connaitras: 101
- pourquoi: 102
- eternel: 107
- quand: 108
- s: 111
- abattra: 112
- puissant: 117
- annees: 121
- repete: 124
- enfoire: 127
- entend: 130
- meurt: 132
- aussitot: 133
- avais: 135
- jamais: 136
- cherche: 137
- comprendre: 139
- trouvais: 141
- seulement: 142
- en: 145
- jetait: 146
- avant: 150
- flinguer: 152
- mec: 154
- puis: 156
- matin: 158
- ai: 160
- vu: 161
- quelque: 162
- chose: 163
- m: 165
- reflechir: 168
- seul: 171
- coup: 172
- me: 174
- pourrait: 177
- bien: 178
- vouloir: 179
- joli: 199
- mm: 200
- serait: 202
- protecteur: 204
- angoisse: 212
- ou: 216
- encore: 217
- mieux: 218
- monde: 233
- lucifer: 239
- mais: 248
- rien: 249
- n: 253
- juste: 255
- vrai: 259
- faible: 266
- suis: 270
- tyrannie: 272
- mechants: 274
- essaie: 278
- ringo: 279
- prix: 281
- effort: 284
- harassant: 285
- proteger: 287
- malin: 21, 187
- au: 31, 280
- nom: 32, 104
- faibles: 41, 289
- qu: 42, 240
- dans: 45, 207
- vallee: 47, 209
- larmes: 55, 215
- une: 76, 80
- vengeance: 81, 114
- sur: 85, 109
- toi: 110, 225
- tout: 116, 251
- dire: 148, 180
- dis: 175, 245
- es: 183, 264
- vertueux: 3, 192, 228
- oeuvre: 19, 185, 237
- du: 20, 115, 186
- homme: 26, 191, 227
- berger: 39, 206, 223
- a: 93, 138, 166
- un: 153, 170, 283
- moi: 195, 221, 276
- les: 10, 86, 95, 288
- fait: 14, 37, 119, 167
- il: 24, 43, 57, 131
- j: 70, 134, 159, 277
- tu: 100, 182, 244, 263
- mon: 103, 198, 203, 205
- je: 123, 140, 173, 269
- ce: 157, 201, 242, 256
- c: 193, 219, 230, 260
- d: 6, 48, 75, 79, 169, 282
- le: 38, 59, 73, 222, 232, 265
- des: 2, 40, 54, 67, 120, 214, 273
- qui: 8, 30, 89, 128, 164, 234, 257
- ca: 118, 125, 144, 149, 176, 247, 252
- la: 0, 34, 46, 51, 65, 113, 208, 271
- que: 13, 122, 143, 181, 189, 197, 243, 262, 268
- l: 18, 25, 106, 126, 129, 184, 190, 211, 226, 236
- est: 4, 58, 105, 194, 220, 231, 235, 241, 254, 258, 261
- de: 27, 33, 50, 61, 97, 147, 151, 210, 238, 246, 250, 286
- et: 53, 64, 83, 91, 99, 155, 188, 196, 213, 224, 229, 267, 275

Solution

flattr this!