Mon but est d’analyser la fréquence de syllabes ou de mots depuis différents textes. Les fréquences d’apparition de chaque mot/syllabe étant cumulées dans une base de données.
À chaque fois que j’ai posté du code ici je n’ai reçu que des critiques constructives alors je vous soumets celui-là :)
Le code fait ce que je lui demande mais il est très lent sur de gros textes. Je pense que le problème se situe dans mes interactions avec la base de données. J’ai quelques une idée pour remédier à ce problème, vous verrez plus bas, vous me donnerez peut-être votre avis.
#!/usr/bin/env python3
"""Reader.py: Read text for strings frequency."""
__author__ = "M4rotte"
__copyright__ = "Copyright 2015, Institut Marotte pour un Mouling de Qualitäy"
__license__ = "GPL"
__version__ = "0.1"
import sys # SYS module (used for argument management)
import re # Regular expressions
import sqlite3 # SQLite
import time # Time is something you've never have enough
from collections import deque # Read buffer
MAXLEN = 24
KEEPMAXLEN = 3 # Over this length we only keep strings which don't match following regexp to avoid too many splitted words
reKEEP = re.compile(r'^( |[A-ZÉÀÔ])(.*)( |\n|\.|,|\;)$')
buf = deque(['' for s in range(MAXLEN)])
counter = 0
string = ''
strings = {}
strings_ok = {}
dbfile = './reader.sqlite'
### Read input and populate `strings{}`
try:
while True:
c = sys.stdin.read(MAXLEN-1)
if c:
for i in range (0,len(c)):
buf.popleft()
buf.append(c[i])
#print(buf)
for i in range (MAXLEN-len(c)-1, MAXLEN):
string = ''
for j in range (i, MAXLEN):
try:
#print("(i,j):"+str(i)+","+str(j))
if buf[i]:
string += buf[j]
s = string.replace ("\n"," ")
string = s.replace(" "," ")
if len(string) > 1:
strings[string] += 1
#print ("ANOTHER:"+string)
except KeyError:
strings[string] = 1
#print ("NEW:"+string)
#print(strings)
counter += 1
else:
break
except KeyboardInterrupt as e:
sys.stderr.write (repr(e))
sys.stderr.flush()
sys.stdout.flush()
exit(1)
sys.stderr.write (str(counter) + "\t×"+ str(MAXLEN) + " bytes read.\n")
sys.stderr.write (str(len(strings)) + "\traw strings.\n")
### Remove unwanted strings
for s in strings:
if len(s) < KEEPMAXLEN:
strings_ok[s] = strings[s]
elif reKEEP.match(s):
strings_ok[s] = strings[s]
#print(s)
### Free some memory
buf.clear()
strings.clear()
sys.stderr.write (str(len(strings_ok)) + "\tOK strings.\n")
# Open database
connection = sqlite3.connect(dbfile)
cursor = connection.cursor()
# Create SQLite table if not exists, to store shits…
cursor.execute('create table if not exists strings (string text primary key, freq int)')
# Store `strings_ok{}` into database
inserted = 0
updated = 0
for s in strings_ok:
try:
cursor.execute("insert into strings values (?, ?)",(s, strings_ok[s]))
inserted += 1
except sqlite3.IntegrityError:
updated += 1
cursor.execute("update strings set freq=freq+? where string like ?",(strings_ok[s],s))
if ((inserted+updated) % 100 == 0):
prog = (inserted+updated)/len(strings_ok)*100
sys.stderr.write ("Database update: "+str(round(prog,1))+"%\r")
sys.stderr.flush()
print("Database update:\tOK. ")
cursor.execute("select count(*) from strings")
count = cursor.fetchone()[0]
sys.stderr.write (str(inserted)+"\tstrings inserted in database.\n")
sys.stderr.write (str(updated)+"\tstrings updated in database.\n")
sys.stderr.write (str(count)+"\trecords in database.\n")
connection.commit()
connection.close()
Je pense que la grosse erreur se situe ici :
cursor.execute("update strings set freq=freq+? where string like ?",(strings_ok[s],s))
ce doit être une requête coûteuse à cause de la clause where…
Donc mon idée serait de reporter l’incrément des compteurs au niveau du code Python, ce qui ne nécessite qu’un select, puis de remplacer l’update par un insert or replace…
# Erratum
Posté par Marotte ⛧ . Évalué à 2.
Le premier commentaire est erroné :
s/~which don't match~/which match/
[^] # Re: Erratum
Posté par Benoît Sibaud (site web personnel) . Évalué à 5.
Corrigé, merci.
# En memoire
Posté par Duncan Idaho . Évalué à 3. Dernière modification le 15 décembre 2015 à 04:51.
Pourquoi ne pas stocker l'information en memoire (et ne la mettre dans la base de donnee qu'a la fin). J'imagine effectivement que le goulet doit se situer dans la mise a jour de la base de donnee.
A priori, ta structure de donnee comptant les syllabes/mots ne devraient pas augmenter lineairement avec la taille de tes textes (si ma memoire est bonne, la taille doit augmenter en logarithme, distribution de Zipf, toussa). Un bete
dict
devrait faire l'affaire, mais python a des structures plus subtiles pour compter je crois.Ca devrait etre assez simple a tester (et tu devrais pouvoir chronometrer le temps passer sur la mise a jour de ta table egalement—meme juste en utilisant
time.time()
).[edit: vas y efface, c'est ce que tu fais deja j'ai mal lu ton code]
[^] # Re: En memoire
Posté par Marotte ⛧ . Évalué à 2.
Euh… bah non :)
Utiliser time() je sais faire mais je ne l’ai pas fait ici, je lance simplement le script précédé de la commande time. Vu la sortie d’erreur je sais à peu près combien de temps prend chaque étape. Je remarque nettement que ça va à une vitesse correct quand la base est vide (donc que les INSERT aboutissent).
Oui. Plus les textes passeront plus il aura de mises à jour et moins d’insertions.
C’est ce que je vais faire.
[^] # Re: En memoire
Posté par Meewan . Évalué à 1.
Attention avec les dict ça bouffe très vite énormément de mémoire. Si tu n'as pas besoin des clefs et que tu peux te contenter des list fais le.
A titre d'exemple une liste de 16 000 000 dict de 7 éléments pèse plus de 32go en RAM, en remplaçant simplement les dict par des list on passe a 500Mo-800Mo de RAM.
De plus tu utilise sqlite, je ne connais pas les performance de ce moteur pour les update mais peut être que choisir mysql(ou mariadb) avec le bon moteur sur la table tu dois pouvoir réduire la charge.
[^] # Re: En memoire
Posté par Marotte ⛧ . Évalué à 2.
Oui… quand on parcourt toutes les combinaisons possibles rien qu’entre caractères lus ça va vite très vite.
Pour la mémoire j’ai abandonné le deque pour stocker mon tampon, le problème étant qu’un deque n’est pas adressable à l’aide de slices ([:4])… contrairement à un string. J’ai bien profité du conseil concernant collections.Counter(), c’est vraiment le type de variable dont j’avais besoin.
J’ai un peu changé d’approche pour conserver les « strings » (sentences), je me base sur des expressions régulières, c’est plus propre. Dès que j’ai un truc correct je le postici.
Alors là attention, que ça puisse fonctionner plus ou ou moins vite sous MariaDB c’est clairement pas un angle d’étude pour le moment… J’utilise sqlite parce que c’est parfaitement adapté à la modélisation (et c’est par ailleurs l’un des meilleurs moteur opensource de base relationnelle). SQLite ça marche partout. sqlite passe le test ACID : https://fr.wikipedia.org/wiki/Propri%C3%A9t%C3%A9s_ACID je n’en suis pas si sûr pour MarioleDB…
[^] # Re: En memoire
Posté par j_m . Évalué à 3.
Tu n'as pas besoin des propriété ACID. Il n'y a qu'un utilisateur à la fois sur ta db, non ?
Mais sinon, je te rejoins là-dessus pourquoi Maria DB serait mieux. Peut-être un redis qui travaille au maximum en mémoire avec un système clé / valeur.
[^] # Re: En memoire
Posté par Marotte ⛧ . Évalué à 2.
Pour l’instant.
Entendons nous bien, ce n’est pas pour ce caractère que j’ai pris SQLite. En plus des raisons déjà évoquées c’est que MariaDB je connais pas mal alors que SQLite je découvre un peu.
Ou encore BerkeleyDB. Mais je ne veux pas me limiter à un système clé-valeur pour les autres fonctionnalités que j’ai en tête. Je compte utiliser les tables virtuelles avec recherche de texte (FTS), par exemple.
[^] # Re: En memoire
Posté par j_m . Évalué à 2.
Ah, tiens une db clé/valeur comme librairies embarquée. C'est intéressant.
Si tu veux aller plus loin dans l'analyse de texte une db clé valeur n'est peut-être pas la meilleur piste.
Moi, pour faire une application un peu complexe, j'utiliserais Solr et surtout la bibliothèque Lucène, mais je ne sais pas si il y a un binding python. Je pense que ça fait tout :-)
Et c'est cette librairie qui est utilisée dans partie recherche textuelle pour des applications comme cassandra. Et je la soupçonne d'être partout où il y a de la recherche de texte qui marche bien. Github peut-être.
[^] # Re: En memoire
Posté par j_m . Évalué à 4.
Dict se base sur une table de hashage.
Il y a aussi le Trie (pas tree!) qui travaille avec la même interface mais utilise une autre structure de données qui est peut-être plus efficace en terme d'espace utilisé.
C'est un arbre basé sur les lettres du mot. Par exemple si j'insère valide, valeur et vanne ça donne ça
On comprends que si les mots de sont pas trop long c'est assez rapide de les retrouver. Et la structure semble assez économe en espace mémoire.
Voilà le lien vers une implémentation python: https://pypi.python.org/pypi/patricia-trie/8
# une seule exécution SQL
Posté par LeBouquetin (site web personnel, Mastodon) . Évalué à 10.
Il est toujours plus lent d'exécuter plusieurs requêtes SQL en plusieurs fois.
Trois pistes :
- utilise "insert or update" au lieu de "insert"
- bufferise tes requêtes SQL dans une chaîne de caractères et exécute les toutes en une seule fois avec la fonction executemany()
- utilise des noms de variables moins génériques que string ou buf qui degradent la lisibilité du code et compliquent donc la démarche de t'aider.
Enfin… C'est juste un avis de moule qui se dit que plus est de fous moins il y a de riz… Et accessoirement que même qqun qui ne connait pas SQLite pourra mieux t'aider si la lecture de ton code est immédiate.
#tracim pour la collaboration d'équipe __ #galae pour la messagerie email __ dirigeant @ algoo
# Commentaire supprimé
Posté par Anonyme . Évalué à 6.
Ce commentaire a été supprimé par l’équipe de modération.
[^] # Re: nltk
Posté par Arthur Accroc . Évalué à 2.
Gère-t-elle le français et le découpage en syllabes (si c’est vraiment ce dont Marotte a besoin) ?
« Le fascisme c’est la gangrène, à Santiago comme à Paris. » — Renaud, Hexagone
[^] # Re: nltk
Posté par lejocelyn (site web personnel) . Évalué à 3.
Pour le traitement d'un texte, si considérer le texte comme un sac de mots te suffit (et donc perdre les informations liées à la progression du texte), tu peux le découper (tokenize) en une liste de mots et ensuite compter les occurrences des mots. Pour les syllabes, c'est plus difficile en fait, ça dépend si tu ne souhaites que les syllabes à l'intérieur des mots ou si les liaisons t'intéressent. Si les liaisons t'intéressent, il te faut conserver la progression du texte et réfléchir aux liaisons possibles (mais bon, entre celles possibles et réalisées, il y a des différences importantes selon les régions de France où tu te trouves et les classes socio-culturelles… y'a pas mal d'articles sur le sujet aussi.) ; si c'est simplement de règles pseudo-grammaticales/normatives de découpage syllabique qui t'intéresse à l'intérieur d'un mot, ça doit se faire en une expression régulière à peine complexe :)
Sinon, NLTK gère le français et les fréquences distributionnelles, il peut même te sortir des graphiques assez rapidement. Voir ça par exemple : http://www.nltk.org/api/nltk.html#nltk.probability.FreqDist
[^] # Re: nltk
Posté par Marotte ⛧ . Évalué à 3.
Comme un sac de caractères même… :)
Je me pencherai sur NLTK un de ces jours ça a l’air bien mais là je veux une approche plus « bête », basée seulement sur les fréquences d’apparition.
[^] # Re: nltk
Posté par Marotte ⛧ . Évalué à 2.
Très intéressant, merci.
# Je ne ferais pas ça comme ça.
Posté par Arthur Accroc . Évalué à 4.
Les syllabes ? Les mots ? Les deux ? Dans la même base ???
As-tu au moins essayé de mettre une trace entre l’analyse du texte et la mise à jour de la base de données pour en être sûr ?
Pour l’analyse du texte, je ne dis pas que ça impacte les performances (normalement, ça les améliorerait, mais avec le coup du remplacement de chaîne relancé à chaque caractère, c’est moins sûr), mais tu fais un truc genre prise de tête en C avec des MAXLEN, des buf… ce n’est même pas évident de voir ce que ça fait exactement et si c’est ce qui est attendu, alors que tu utilises un langage de script avec un type chaîne dynamique, des expressions régulières…
Si tu veux la performance pure, ce n’est pas le meilleur langage. Si tu utilises un langage de haut niveau, autant en profiter pour faire quelque chose de plus compréhensible.
Personnellement, je lirais l’entrée par ligne, je splitterais sur les espaces et signes de ponctuation (ou tout ce qui n’est pas une lettre), comme ça, j’aurais directement les mots et ensuite seulement, pour chaque mot, j’itérerais sur la lettre de départ et la longueur pour avoir les groupes de lettres.
Là, il ne s’agirait que des groupes de lettres et pas vraiment des syllabes, mais je n’ai pas l’impression que ton code fasse mieux. Le découpage en véritables syllabes d’un mot (est-ce réellement ce dont tu as besoin ?) est un problème nettement moins trivial.
Si je devais faire ça en C, je ne ferais pas comme toi non plus. Je lirais aussi par ligne (même avec le C, on peut !), voire je chargerais tout le texte en mémoire (mon premier ordinateur avait 8 Ko de mémoire, mais on n’en est plus là), j’itérerais sur le caractère de la ligne (ou du texte complet dans le deuxième cas), si ce n’est pas une lettre, je viderais le mot courant, sinon, je lui ajouterai la lettre et pour les groupes de lettres, j’itérerais sur la longueur jusqu’au caractère courant du mot. Ainsi, pas de remplacements de chaînes ni de popleft…
« Le fascisme c’est la gangrène, à Santiago comme à Paris. » — Renaud, Hexagone
[^] # Re: Je ne ferais pas ça comme ça.
Posté par Marotte ⛧ . Évalué à 3.
Je le vois simplement grâce au
sys.stderr.write(…)
qui est fait entre chaque étape, la première étape (chargement) et la deuxième (suppression des chaînes qui coupent des mots en deux, sauf les petites…) s’exécutent en un temps acceptable.En faisant ça si j’ai une première ligne qui contient "voici une première ligne" et une seconde qui contient "et là une deuxième" je n’aurais pas d’entrée créée contenant le dernier mot de la première et le premier de la deuxième ("ligne et"). Je n’aurais pas non plus 'e e', 'e et', etc… (je remplace systématiquement "\n" par "n"…)
J’utilise donc un tampon de MAXLEN caractères qui se décale de MAXLEN-1 caractères à chaque fois afin que le dernier caractère du précédent tampon se retrouve premier du tampon suivant. Pour ça un deque me semblait plus adapté qu’un string
Comme tu peux le voir le programme lit l’entrée standard, pour l’instant il attend EOF pour traiter les données mais à terme je veux qu’il puisse tourner en continue, en faisant un màj de la base tous les N tampons/caractères.
[^] # Re: Je ne ferais pas ça comme ça.
Posté par Tit . Évalué à 3. Dernière modification le 15 décembre 2015 à 15:13.
je comprend pas bien ce que tu veux faire, au début parles de compter l’occurrence de chaque mot et syllabe mais en fait tu sembles vouloir compter n'importe quel groupe de lettres… C'est quoi le but?
Tu n'aurais pas un petit exemple de texte avec le résultat obtenu?
[^] # Re: Je ne ferais pas ça comme ça.
Posté par Marotte ⛧ . Évalué à 3.
Oui c’est ça. N’importe quel groupe de caractères en fait.
Y’en a pas vraiment… je fais des essais on va dire…
[^] # Re: Je ne ferais pas ça comme ça.
Posté par polux14 . Évalué à 2.
Je vais enfoncer une porte ouverte mais tu pourrai regarder du coté des algos de compression par dictionnaire. Il y a sûrement des idées à récupérer.
[^] # Re: Je ne ferais pas ça comme ça.
Posté par Tit . Évalué à 1.
Merci pour ta réponse.
a priori, ça m'aurait semblé plus intéressant de compter le nombres d'apparitions de mots dans divers textes (ne serait-ce que pour vérifier la loi de zipf ;-)), que de compter la fréquence dans les textes étudiés d'une suite de caractères telles que "a ma".
[^] # Re: Je ne ferais pas ça comme ça.
Posté par Marotte ⛧ . Évalué à 2.
En incluant les suites telles que "a ma" je peux toujours par la suite filtrer avec une expression régulière pour n’avoir que les mots.
[^] # Re: Je ne ferais pas ça comme ça.
Posté par Arthur Accroc . Évalué à 3.
Tu peux facilement exclure les suites de caractères qui contiennent autre chose que des lettres, mais je ne vois pas comment tu pourrais savoir qu’une suite de caractères qui n’en contient pas correspond à un mot complet.
Si tu as « car » dans ta base, comment pourrais-tu savoir facilement si ça correspond au mot « car », à un monceau de « caractère », de « carrément » ou aux trois, avec plus ou moins d’occurrence chacun ?
Il y a moyen de faire un traitement inversé du genre s’il y a deux fois la suite « caractère », il faut la soustraire deux fois du nombre d’occurrences de la suite « car » pour arriver finalement au nombre d’occurrences du mot « car », mais le mieux pour faire des traitements sur les mots entiers et de stocker à part les mots entiers…
« Le fascisme c’est la gangrène, à Santiago comme à Paris. » — Renaud, Hexagone
[^] # Re: Je ne ferais pas ça comme ça.
Posté par BAud (site web personnel) . Évalué à 2.
je pense que c'est un cas d'utilisation auquel il n'avait pas encore pensé :-)
sympa de lui suggérer :p (mais ça ne va pas améliorer les perfs /o)
# On dirait du C
Posté par vermillon . Évalué à 10.
ça s'écrit plutôt comme ça:
vu qu'on est en python, pas besoin de reprendre un idiome du C. De manière générale, dès que tu as "range(len(x))", tu sais que tu as quelque chose de moche en python (si tu as vraiment besoin de l'indice, tu peux toujours utiliser enumerate). Et par habitude, "c" c'est plutôt un caractère, du coup le mettre pour représenter une chaîne, ça choque l'œil.
ça s'écrit:
Pas besoin de tampon.
il y a directement une fonction pour ça:
Pour ce qui est de compter, tu as collections.Counter qui te permet d'éviter de vérifier si une clé est déjà dans ton dictionnaire (et d'incrémenter si c'est le cas où de mettre à 1 sinon): avec Counter, tu fais simplement compteur[clé] += 1 et ça marche toujours: si la clé n'est pas présente, elle est considérée valoir 0.
Et enfin, parce que vraiment, on dirait du C ce code, tout le foin avec le buffer etc, ça me parait entièrement inutile. Tu peux parfaitement lire ligne par ligne l'entrée standard:
De manière générale, si tu crois que tu as besoin de gérer la mémoire toi-même en Python pour une tâche aussi simple, c'est que tu n'utilises pas les bons outils.
[^] # Re: On dirait du C
Posté par Mimoza . Évalué à 3.
Et pour l'affichage d'un message/erreur aussi :
https://docs.python.org/3.1/whatsnew/3.0.html
Message
Erreur
[^] # Re: On dirait du C
Posté par Marotte ⛧ . Évalué à 2.
Merci pour tous ces conseils avisés ! Surtout pour le collections.Counter je pense que ça va me servir…
Ma ligne peut avoir une longueur variable. J’aurais toujours besoin de la traiter en morceau de N caractères car je ne vais pas itérer sur tous les caractères de la ligne si je n’ai pas besoin de conserver des chaînes de plus de N caractères. J’ai également besoin d’avoir la fréquence de chaînes telles que "titi\ntoto" je devrais donc conserver un certain nombre de caractères de la ligne précédente à chaque fois…
J’ai trouvé plus simple d’avoir une sorte de « fenêtre » (buf) qui va « glisser » sur mon entrée… Par exemple si je prends un MAXLEN de 2 et que mon entrée est "abcd\n" j’aurais tour à tour :
Avec MAXLEN à 3 j’aurais, en plus :
Non, j’ai bien conscience que ce n’est pas à moi de gérer la mémoire.
[^] # Re: On dirait du C
Posté par copapa . Évalué à 4. Dernière modification le 15 décembre 2015 à 21:05.
Piqué sur http://stackoverflow.com/questions/6822725/rolling-or-sliding-window-iterator-in-python
La sortie :
# I like =
Posté par Elfir3 . Évalué à 4.
Je remarque que tu utilises la clause
like
dans dans tes requêtes SQL.Je ne connais pas SQLite et son optimisation des requêtes, mais il me semble qu'il est préférable d'utiliser
=
comme comparateur si ton but est de vérifier l'égalité.Sauf erreur de ma part (j'ai lu entre les lignes), tu n'utilises pas de wildcard dans ton code.
Si ce n'est déjà fait, il serait peut être aussi intéressant d'utiliser des indexes pour améliorer les performances des requêtes.
[^] # Re: I like =
Posté par Elfir3 . Évalué à 2.
Et oublie le passage sur les indexes..
[^] # Re: I like =
Posté par TilK . Évalué à 3.
Je confirme pour le like. Ici, cela t'empêche d'utiliser l'index et donc tu te tapes un full scan de ta table à chaque update. Cela est très coûteux.
Après, je ne connais pas très bien sqlite, je viens d'apprendre qu'il n'y a pas de "INSERT … ON DUPLICATE KEY UPDATE …" ce qui t'aurais bien aidé ici.
Mais il y a une question que tu peux te poser. Aurais tu plus d'insert ou d'update ?
Ton code exécute 2 requêtes pour un update et une seule pour un insert. Si tu prévois que l'update sera plus récurent, mieux vaut faire le contraire : update d'abord, et si le nombre de rows modifiés est 0, tu fais l'insert.
Par exemple :
Sinon, il y aurait une solution a une requête :
Mais elle ne semble pas performante.
[^] # Re: I like =
Posté par Marotte ⛧ . Évalué à 2.
Je viens de tester avec
=
à la place delike
les performances sont bien meilleures effectivement :)Merci à vous deux. Le temps d’exécution est passé de 15 min à 40 s (environ)…
Pour le nombre de chaque requête je ne sais pas encore, je garde en tête ta remarque très judicieuse sur l’ordre d’exécution.
[^] # Re: I like =
Posté par j_m . Évalué à 3.
Oui, parce que un index a dû être créé automatiquement sur la colonne string car elle est définie comme primary key.
# occurrence des mots
Posté par zurvan . Évalué à 9.
moi j'utilise ça :
c'est pas très précis, mais c'est simple et ça suffit pour mes besoins (savoir si je répète trop certains mots)cat texte.txt | tr " " "\n" | sort | uniq -c | sort -n >> resultat.txt
« Le pouvoir des Tripodes dépendait de la résignation des hommes à l'esclavage. » -- John Christopher
[^] # Commentaire supprimé
Posté par Anonyme . Évalué à 3.
Ce commentaire a été supprimé par l’équipe de modération.
[^] # Re: occurrence des mots
Posté par Marotte ⛧ . Évalué à 3.
C’est volontaire de vouloir prendre les signes de ponctuation…
Pour moi 'mice' et 'mice.' ne sont pas deux chaînes identiques. De la deuxième on peut présager que le mot « mice » a des chances de terminer une phrase. À part le remplacement des retour à la ligne par des espaces puis la réduction des espaces consécutives à une seule espace je travaille en mode suite de caractères.
# un pipe de six commandes shell et s'est plié
Posté par binoyte . Évalué à 6. Dernière modification le 15 décembre 2015 à 13:44.
Dans le livre Classic Schell Scripting de chez O'reilly [»], il y a un exemple d'analyse fréquentielle des mots d'un texte pour montrer la puissance des pipes. En six commandes et c'est plié.
[^] # Commentaire supprimé
Posté par Anonyme . Évalué à 7. Dernière modification le 15 décembre 2015 à 21:00.
Ce commentaire a été supprimé par l’équipe de modération.
[^] # Re: un pipe de six commandes shell et s'est plié
Posté par binoyte . Évalué à 1. Dernière modification le 16 décembre 2015 à 09:13.
Quelques suggestions rapides à tes remarques :
1. en convertissant les encodages avec la commande
iconv
2. en accédant aux dictionnaires (comme
/usr/share/dict/words
ou un dictionnaire Hunspell) et en s'assurant que le tiret est bien absent de la variableIFS
3. en comptant le total des mots du fichier avec
wc -l
sur la liste des mots avant le dé-doublonnage paruniq -c
4. …
… Je ne vois rien de rédhibitoire à résoudre ce problème en Shell.
Le traitement de données textuelles est justement le domaine de prédilection du Shell.
# Compliqué
Posté par barmic . Évalué à 5.
Je ne suis pas expert en python, mais tu semble faire des choses assez compliquées pour rien. Notamment quand tu fais ça :
Alors que tu ajoute des éléments à string 1 par 1. Tu peux faire :
Pour ce qui est de tes accès à la base, tu peux :
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
# Tout faire en base
Posté par Xavier Verne (site web personnel) . Évalué à 3.
Bonjour,
C'est plutôt non intuitif, mais tu pourrais tout faire en SQL pur… Surtout si tu cherches de la performance.
Tu charges chaque mot dans une table, avec un traitement de type sql_loader et ensuite tu manipules tout en base de données avec des GROUP BY et des DISTINCT et des COUNT pour avoir respectivement les mots, une seule ligne dans une table même si ton mot est répété, et le nombre d'occurences… Et tu as toutes les primitives en SQL pour manipuler les chaînes de caractères…
Deuxième chose contre intuitive : sur Oracle et dans certains cas sur POSTGres, un DROP TABLE et CREATE AS SELECT c'est beaucoup plus rapide que des INSERT et des UPDATES.
SQL c'est ensembliste, et comme tu te fiches de l'ordre des mots, et bien tu pourrais avoir des perfs proches du C (parce que l'implémentation de l'exécuteur de requête et de parcours de bases de données, c'est du bas niveau).
J'ai fait ça des années, et d'ailleurs avec quelques astuces mêmes pour des algorithmes plutôt procéduraux, les résultats sont bluffants.
[^] # Re: Tout faire en base
Posté par groumly . Évalué à 3.
C'est probablement vrai ce que tu dit (encore que debugger du sql pur ou du python, c'est pas vraiement la meme chose), mais en l'occurence il fait du sqlite, donc je doute qu'il ait des gains comparable a ce qu'il aurait sur un vrai sgbd des familles.
Linuxfr, le portail francais du logiciel libre et du neo nazisme.
[^] # Re: Tout faire en base
Posté par steph1978 . Évalué à 3.
SQLite est un SGBD. Je vois pas sur quoi tu te base pour dire qu'il est moins performant qu'un autre….
[^] # Re: Tout faire en base
Posté par Marotte ⛧ . Évalué à 2.
Ce qu’il dit, je pense, c’est que les gains apportés par le fait de faire le plus possible de chose en SQL (et je dois dire qu’on peut vraiment faire un tas de trucs insoupçonnés dans les différentes variantes SQL…), par le moteur SQL, ça prends plus de sens pour un SGBD client/serveur… ça évite des « aller-retours »…
# Version 0.3
Posté par Marotte ⛧ . Évalué à 2. Dernière modification le 21 décembre 2015 à 23:36.
Merci encore pour toutes vos suggestions. Il faut souligner qu’elles étaient toutes plutôt pertinentes… J’ai pu appliquer beaucoup de vos conseils même si j’ai dû abandonner certains (je pense notamment au islice du module itertools, qui est exactement ce que je cherchais à faire…) parce que je suis parti sur d’autres types de variables et une nouvelle approche du problème.
D’une point de vue fonctionnel je désire ne plus saucissoner l’input à l’arrache mais essayer de découper sur les phrases en me basant sur les règles typographiques (une phrase finit par un point, '«' ouvre un dialogue (ou pas…), etc…). Ça fait nettement moins d’enregistrements en base !
D’un point de vue technique j’ai utilisé une classe afin que le code principal soit plus clair.
Donc voici le code, j’ai viré les commentaires inutiles et il est de toute façon plus concis.
D’un point de vu performance je trouve que ça tourne pas mal (à partir d’une base inexistante) :
Les fichiers sont des classiques de la littérature issus de sites comme http://www.ebooksgratuits.com/ (principalement), convertis from EPUB par un script choppé sur github. Les fichiers ayant été ensuite légèrement toilettés à la main est au grep avant traitement… Ça représente 6,6M de texte UTF-8.
Voici le résultat de quelques requêtes effectuées sur la base suite au traitement de ces fichiers :
J’ai déjà noté que je devrais appliquer mon expression régulière "discard" dans la fonction read() plutôt qu’au niveau store()…
[^] # Re: Version 0.3
Posté par Marotte ⛧ . Évalué à 2.
J’ai zappé le time :/
[^] # Re: Version 0.3
Posté par Marotte ⛧ . Évalué à 2. Dernière modification le 22 décembre 2015 à 00:06.
Je devrais je pense créer un re.reset pour celle là…
[^] # Re: Version 0.3
Posté par kantien . Évalué à 5. Dernière modification le 22 décembre 2015 à 11:19.
ou tester l'appartenance à une liste de caractères
si un jour tu veux rajouter d'autres caractères dans le test, ce sera plus rapide et plus lisible. ;-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Version 0.3
Posté par Marotte ⛧ . Évalué à 2.
En modifiant un peu le code voilà ce que ça donne sur un extrait de bouchot standard :
Ça donne des informations intéressantes sur les domaines des liens POSTé :)
[^] # Re: Version 0.3
Posté par barmic . Évalué à 5.
Tu fais toujours pour chaque caractère :
Tu pourrais le faire une fois pour toute à la fin.
Pour le stockage je persiste à dire que tu peux faire ça bien plus efficacement en effectuant toute les lectures en une fois puis en faisant tous les ajouts/mise à jour ensuite (faire 1+N requêtes plutôt que N+N/2).
Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)
[^] # Re: Version 0.3
Posté par Tit . Évalué à 3. Dernière modification le 24 décembre 2015 à 12:23.
Je comprend pas les résultats que tu obtiens, si j'ai bien compris tu as pris plusieurs classiques de la littérature française et les mots ou groupes de mots les plus courants sont:
ça ne me paraît pas possible! ou il y a un bug, ou je n'ai pas compris ce que tu faisais. Je m'attendais à ce que les mots les plus courants soient "le", "la", "et" etc. Que "M." arrive en tête, suivi de "Ah" ça me semble être n'importe quoi.
[^] # Re: Version 0.3
Posté par Dr BG . Évalué à 5.
Ça ne semble donner que ce qui se termine par une ponctuation (point, point-virgule, point d'exclamation, etc.).
[^] # Re: Version 0.3
Posté par Marotte ⛧ . Évalué à 2.
Oui, le but c’est d’avoir la fréquence des phrases.
J’ai finalement modifié le script pour qu’il ne considère que c’est la fin d’un phrase seulement si ça termine par un point (.?!…:) suivi d’une espace ou d’un retour à la ligne. Ceci pour éviter de découper une URL (ou encore un sigle écrit avec des points, tel que U.R.S.S).
Ce n’est pas vraiment adapté à la tribune car il n’y a pas forcément un point à la fin de chaque post… Je pourrais ajouter un point à la fin de chaque post avant de les passer en entrée du script si nécessaire mais je m’oriente plus vers l’analyse de textes respectant la typographie française (littérature, article de jounrnal…)
[^] # Re: Version 0.3
Posté par j_m . Évalué à 2.
Qu'est-ce que tu attends de cette information? En général les phrases ne sont jamais répétées. Sauf si elles contiennent très peu de mots. Ou si ce sont des citations.
Si tu veux identifier des expressions qui se répètent, là il te faudrait plutôt utiliser une fonction qui calcul le degré de cooccurence entre deux ou plusieurs mots dans des phrases.
# Ça avance
Posté par Marotte ⛧ . Évalué à 2.
À la base j’aurais pas dû poster ça dans un journal mais dans un forum. Le fait que ce soit un journal m’incite à continuer à poster mon code…
Voici donc la dernière version de Reader.py, script dont le but est de prendre n’importe quel fichier texte en entrée, le découper en « phrases » et stocker la fréquence d’apparition de ces « phrases » dans une base SQLite.
La sortie en partant d’une base inexistante et en la renseignant à partir de classiques de la littérature française, posts du bouchot et contenu issu de Wikipédia (histoire d’avoir des entrées assez hétérogènes) :
On peut voir que l’intégration du contenu Wikipédia amène à certains biais (et c’est pas Me Soleil qui s’en plaindra…) mais que « Ah ! » et « Oh ! » restent des valeurs sûres :
Vous pouvez reprendre une activité normale.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.