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 :
- Une utilisation fort maline du defaultdict par bob.
- Le signalement d’unidecode par zanguu qui aurait géré
'œ'
sans problème. Mais ça rajoute une dépendance.
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)))) |
Sans vouloir faire mon chieur (mais un peu quand même), pourquoi ces deux lignes :
texte = re.sub('[^%s]' % string.ascii_lowercase, ' ', texte.lower())
for i, e in enumerate(texte.split()):
alors que ceci me semble plus “opti” :
for i, e in enumerate(re.findall('[\w]+', texte.lower())):
vu qu’on ne passe qu’on parse qu’une fois le texte.
Surtout que
.encode('ascii', 'ignore')
à viré tout ce qui pouvais interférer avec la regex.Et j’ajouterai que tu m’as menti!
mais quand je fais tourner ton code j’ai :
ce que j’ai personnellement géré grace à :
u''.join(c for c in unicodedata.normalize('NFKD', texte) if not unicodedata.combining(c )
et qui m’autorise à oublier le.encode('ascii', 'ignore')
PS: wouhou je suis dans l’article… et je casse les couilles après !
Arf, j’ai merdé. Amen. J’aurais du les remplacer par un espace.
Findall est une meilleure solution en effet, par contre pas avec \w qui garde les underscores. Il faut une regex plus perfectionnée.
Ah pas faux, j’oublie toujours ce détail.
Étant donné qu’on a fait un
.lower()
on peut utiliser ‘[a-z\d]+’.Mais vu que ton texte ne comportait pas de ‘_’ j’y ai pas pensé.
@zanguu: oh le vilain, j’ai refait tourné le code, et j’obtiens bien “c” et “est” séparément.
Ce qui est logique puisque
exte = re.sub('[^%s]' % string.ascii_lowercase, ' ', texte.lower())
remplace l’apostrophe par un espace. Vil ! Perfide ! Enculé !Mais, monsieur le juge, je jure c’est la véritée.
Chez moi ton normalize me donne ça :
(c’est con le ‘i’ de ‘égoïstes’ avec trois points ne passe pas au c/c)
et le encode ascii le transforme en :
du coup ton sub ne remplace que la ponctuation et le ‘-‘ de ‘Tout-Puissant’ vu que le reste existe plus.
PS: mon python est le suivant (si ça joue): Python 2.7.3 (default, Sep 26 2013, 20:08:41) [GCC 4.6.3] on linux2
Et je voudrais finir ma défense avec la citation d’un homme célèbre:
J’ai pas de tampon LOL. Dommage.
Trouvé…
J’ai copié le texte depuis la news et collé dans sublime text.
Sauf que dans le texte les apostrophes sont des
>>> unicodedata.name('’'.decode('utf8'))
'RIGHT SINGLE QUOTATION MARK'
et quand je tape une apostrophe dans ST j’ai
>>> unicodedata.name('\''.decode('utf8'))
'APOSTROPHE'
Comme quoi y’a pas que l’encoding qui fait chier dans les fichiers de texte…
of course.
Sayg bien ces petits exos il y en aura d’autres?
Oui.