• # Version 0.3

    Posté par . En réponse au journal Analyse de texte. É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.

    #!/usr/bin/env python3
    """Reader.py: Read text for sentences frequencies."""
    __author__ = "M4rotte"
    __copyright__ = "Copyright 2015, Institut Marotte for Mouling"
    __license__ = "GPL"
    __version__ = "0.3"
    import sys
    import re
    import sqlite3
    from collections import Counter
    class Reader:
     def __init__(self):
     self.sb = ''
     self.characters = Counter()
     self.sentences = Counter()
     self.start = re.compile(r'^(\n|)[\-–—«ÉÀÇÔÎÖÏA-Za-z]$')
     self.stop = re.compile(r'(.|\n)+[.!?...:]( |)$')
     self.nb_insert = 0
     self.nb_update = 0
     self.discard = re.compile(r'^(([0-9]+)([\\]+)|^//).')
     def read(self,f):
     """Read input and populate `sentences`"""
     while True:
     c = f.read(1)
     if c == '\xa0':
     c = ' '
     self.sb += c
     self.sb = self.sb.replace(' ',' ')
     if not c:
     break
     if self.start.match(self.sb):
     continue
     if self.stop.match(self.sb):
     self.sentences[self.sb] += 1
     self.sb = ''
     continue
     if self.sb == ' ' or self.sb == '\n' or self.sb == '«' or self.sb == '»':
     self.sb = '' 
     def store(self,dbfile):
     """ Update the database from `sentences` """
     connection = sqlite3.connect(dbfile)
     cursor = connection.cursor()
     cursor.execute('create table if not exists sentences (sentence text primary key unique, freq int)')
     ## For futur use :) # cursor.execute('create virtual table if not exists fts_strings using fts4(string text, freq int, tokenize=unicode61 "remove_diacritics=0" "tokenchars= ")')
     # Store `sentences` into database
     for s in self.sentences.most_common():
     if not self.discard.match(s[0]):
     try:
     cursor.execute("insert into sentences values (?, ?)",(s[0], s[1]))
     self.nb_insert += 1
     except sqlite3.IntegrityError:
     self.nb_update += 1
     cursor.execute("update sentences set freq=freq+? where sentence = ?",(s[1],s[0]))
     sys.stderr.write('Database update: '+str(self.nb_insert)+' inserts '+str(self.nb_update)+' updates. \n')
     connection.commit()
     connection.commit()
    try:
     reader = Reader()
     reader.read(sys.stdin)
     reader.store('reader.sqlite')
    except KeyboardInterrupt as error:
     print ('\n'+repr(error),file=sys.stderr)
     sys.stderr.flush()
     sys.stdout.flush()
     exit(1)

    D’un point de vu performance je trouve que ça tourne pas mal (à partir d’une base inexistante) :

    $ time for f in *.txt; do ./Reader.py < "$f"; done;
    Database update: 7537 inserts 0 updates. 
    Database update: 7569 inserts 76 updates. 
    Database update: 7402 inserts 35 updates. 
    Database update: 2496 inserts 2 updates. 
    Database update: 11022 inserts 72 updates. 
    Database update: 11052 inserts 69 updates. 
    Database update: 5825 inserts 44 updates. 
    Database update: 7148 inserts 64 updates. 
    Database update: 10743 inserts 108 updates.
    

    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 :

    sqlite> select * from sentences order by freq desc limit 10;
    M.|213
    Ah !|184
    « Ah !|181
    - Oh !|112
    - Ah !|98
    Oh !|91
    « Oh !|85
    – Ah !|67
    Eh bien !|54
    - Tiens !|38
    
    sqlite> select * from sentences where sentence like '%?' order by freq desc limit 10;
    Hein ?|33
    - Hein ?|27
    – Pourquoi ?|18
    « Eh bien ?|13
    pourquoi ?|12
    quoi ?|11
    « Pourquoi ?|11
    – Pourquoi cela ?|10
    – Comment cela ?|9
    « Comment ?|9
    
    sqlite> select * from sentences where sentence like '%moule%' order by freq desc limit 10;
    « Il n’y a plus de ténors, disait-il, le moule en est brisé.|1
    Mme Arnoux fit exhiber les moules pour les ouvrages plus difficiles.|1
    

    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()...