Un modo elegante per dividere il testo in parole combinate con la punteggiatura adiacente e determinare quale segno di punteggiatura è

1

In primo luogo, mi rendo conto che il titolo della domanda è tanto terribile quanto il codice di esempio che inserirò di seguito, quindi ti prego di sopportare me mentre spiego il problema più chiaramente, e se hai un'idea migliore per il titolo - sii il mio ospite e modificarlo.

Immagina un lungo testo in chiaro. Consiste di parole separate da segni di punteggiatura e / o spazi. Quello che devo fare è convertirlo in una lista di parole + segni di punteggiatura che separano questa parola da quella successiva. E la svolta è che ho anche bisogno di determinare quale segno di punteggiatura è (o qual è l'ultimo se ce n'è più di uno di seguito). Quindi, ho bisogno di trasformare il testo in una collezione di strutture:

{
   wordFollowedByPunctuation: String;
   punctuationMark: PunctuationType; // E. g. {Point, Comma, Colon, Space, ...}
}

Se tutti i segni di punteggiatura fossero caratteri singoli, sarebbe facile poiché potremmo utilizzare l'analisi del carattere a passaggio singolo. Ho un prototipo funzionante, anche se terribile, in C ++ (usando Qt - QString e QChar - per supporto Unicode).

Ecco la struttura TextFragment : convertirò il testo in una raccolta di questi elementi:

struct TextFragment
{
    enum Delimiter {
        Space,
        Comma,
        Point,
        ExclamationMark,
        QuestionMark,
        Dash,
        Colon,
        Semicolon,
        Ellipsis,
        Bracket,
        Newline
    };

    inline TextFragment(const QString& text, Delimiter delimiter) : _text(text), _delimitier(delimiter) {}

    const QString _text;
    const Delimiter _delimitier;
};

Ed ecco l'analisi vera e propria:

const QString text = readText(device);

    struct Delimiter {
        QChar delimiterCharacter;
        TextFragment::Delimiter delimiterType;

        inline bool operator< (const Delimiter& other) const {
            return delimiterCharacter < other.delimiterCharacter;
        }
    };

    static const std::set<Delimiter> delimiters {
        {' ', TextFragment::Space},
        {'.', TextFragment::Point},
        {':', TextFragment::Colon},
        {';', TextFragment::Semicolon},
        {',', TextFragment::Comma},
        // TODO: dash should be ignored unless it has an adjacent space!
        {'-', TextFragment::Dash},
        // TODO:
        // {"...", TextFragment::Ellipsis},
        {'⋯', TextFragment::Ellipsis},
        {'…', TextFragment::Ellipsis},
        {'!', TextFragment::ExclamationMark},
        {'\n', TextFragment::Newline},
        {'?', TextFragment::QuestionMark},

        {')', TextFragment::Bracket},
        {'(', TextFragment::Bracket},
        {'[', TextFragment::Bracket},
        {']', TextFragment::Bracket},
        {'{', TextFragment::Bracket},
        {'}', TextFragment::Bracket}
    };

    std::vector<TextFragment> fragments;

    QString buffer;
    bool wordEnded = false;
    TextFragment::Delimiter lastDelimiter = TextFragment::Space;
    for (QChar ch: text)
    {
        if (ch == '\r')
            continue;

        const auto it = delimiters.find({ch, TextFragment::Space});
        if (it == delimiters.end()) // Not a delimiter
        {
            if (wordEnded) // This is the first letter of a new word
            {
                fragments.emplace_back(buffer, lastDelimiter);
                wordEnded = false;
                buffer = ch;
            }
            else
                buffer += ch;
        }
        else // This is a delimiter. Append it to the current word.
        {
            lastDelimiter = it->delimiterType;
            wordEnded = true;
            buffer += ch;
        }
    }

    return fragments;

Qui abbiamo una macchina a stati che si sforza di non somigliarne uno, ed è peggio per quello. Funziona. Ma ha un problema più grande dello stile di codifica: i alcuni delimitatori sono multi-carattere, e semplicemente non possono gestirli. Un esempio è l'ellissi costituita da 3 punti: "..." Voglio distinguerlo da un singolo punto. Un altro esempio è che voglio distinguere tra un trattino e un trattino. Un trattino separa parti di una parola composta, e. g. "up-to-date", e per quanto mi riguarda non è un segno di punteggiatura. Un trattino, d'altra parte, è: "Joe - e il suo fidato bastardo - era sempre il benvenuto." Ora, è un carattere di trattino speciale, ma in semplici testi non Unicode entrambi sono comunemente rappresentati da un trattino ("-"). Quindi l'unico modo che vedo per distinguerli è solo "spazio + trattino + spazio" o "spazio + trattino + altro segno di punteggiatura" come trattino.

Esempio: la frase

If only he had tried... well, it doesn't matter now.

dovrebbe risultare

{"If ", Space},
{"only ", Space},
{"he ", Space},
{"had ", Space},
{"tried... ", Ellipsis},
{"well, ", Comma},
{"it ", Space},
{"doesn't ", Space},
{"matter ", Space},
{"now", Point}

Per come la vedo io, ho bisogno di una sorta di parsing esaustivo al posto del mio avido prototipo (e, naturalmente, i separatori stessi dovrebbero essere rappresentati da stringhe, non da personaggi). Qual è il modo più semplice per farlo? Posso usare espressioni regolari per questo (sono terribile con loro)?

    
posta Violet Giraffe 18.05.2016 - 10:25
fonte

1 risposta

2

trattino è una punteggiatura. Considera il testo "dobbiamo aggiustarlo - fino ad ora non ci siamo preoccupati" e "correggere gli errori fino a martedì incluso". Affidarsi agli spazi non ti aiuterà con i dattilografi sciatti.

Generalmente, gestisci il tuo carattere singolo di testo alla volta e, una volta individuato l'inizio di una punteggiatura multi-carattere, elaborerai il testo successivo in quel punto. per esempio. quando trovi un '.' Quindi, rileggendo per determinare nei prossimi 2 caratteri, è anche ".", nel qual caso si combinano i 3 in una singola punteggiatura "ellissi". Il problema diventa quello di leggere in anticipo nello stream e, se non si consumano i caratteri successivi, rimetterli nello stream per il ciclo di elaborazione principale con cui lavorare. Questo tipo di problema è il motivo per cui i buffer di flusso hanno funzioni come putback () e peek () .

    
risposta data 18.05.2016 - 10:54
fonte

Leggi altre domande sui tag