Признавая конец предложения

0

Я пытаюсь прочитать текстовый файл и ввести его в векторную строку по строке. Мне нужно, чтобы он остановился в конце каждого предложения, затем выберите ключевые слова в предложении. Я понимаю, как найти ключевые слова, но не как заставить его перестать вводить строки в конце. Я использую цикл while для проверки каждой строки, и я рассматривал возможность использования ряда утверждений if, таких как

if(std::vector<string>::iterator i == ".") i == "\0"

код, который я выполняю наложение вектора до сих пор:

std::string c;
ifstream infile;
infile.open("example.txt");
while(infile >> c){
    a.push_back(c);
}




Хорошо, поэтому я воспользовался возможностью загружать каждое слово текстового файла в токены, принимая во внимание "" в качестве разделителя и имея список специальных слов для случая:

    const int MAX_PER_LINE = 512;
    const int MAX_TOK = 20;
    const char* const DELIMETER = " -";
    const char* const SPECIAL ="!?.";
    const char* const ignore[]  = {"Mr.", "Ms.","Mrs.","sr.", "Ave.", "Rd."};

а потом

             if(!file.good()){
         return 1;
     }
     //parsing algorithm paraphrased from cs.dvc.edu/HowTo_Parse.html
     while(!file.eof()){
     char line[MAX_PER_LINE];

     file.getline(line, MAX_PER_LINE);
     int n = 0;
     const char* token[MAX_TOK] = {};
     token[0] = strtok(line, DELIMETER);
     if(token[0]){
         for(n = 1; n < MAX_TOK; ++n){
             token[n] = strtok(0, DELIMETER);
             if(!token[n]) break;
         }
     }
     //for(int i = 0; i < n; ++i){
     for(int i = 0; i < n; ++i){
         cout << "Token[" << i << "] =" << token[i] << endl;
         cout << endl; 
     }
     }

теперь я ищу, что положить в оператор if, чтобы он проверял каждый токен для специального случая, или если они следуют за токеном со специальным случаем, чтобы загрузить их в новый токен набора. Я знаю код psuedo по большей части, но я не знаю, какой синтаксис должен был бы сказать, что это будет похоже на то, что (token [i] содержит специальный случай или токен [i] ничего не имеет перед ним ( для первого токена) или капитализируется и следует за токеном со специальным случаем, чтобы загрузить его в новый токен.

любая помощь будет принята с благодарностью.

  • 0
    Точка в конце предложения обычно прикрепляется к слову. Он не будет отображаться в виде собственной строки в векторе.
  • 0
    Делать это хорошо - нетривиальная задача. @DavidSchwartz уже дал такое же хорошее указание, как и легко доступное, но иногда оно будет ошибаться, например, предложения, содержащие сокращения. Например, признав, что "мистер Вонг отправился на улицу С. Брод 119" как одно предложение вместо трех вряд ли будет легко.
Показать ещё 7 комментариев
Теги:

2 ответа

2

Написание собственного ограничителя предложений подходит для небольших проектов или проектов без интернационализации. Для расширенных текстовых решений на границах текста я бы рекомендовал ICU BreakIterator. Основываясь на стандартизации unicode.org, они предоставляют границы символов, слов, строк и предложений. У них есть библиотеки с открытым исходным кодом в C++ (как и в Java, я думаю). См. Эту страницу, и у нее есть ссылка на страницу загрузки библиотеки.

Это позволит избежать повторного использования колеса и избежать потенциальных проблем позже. Большинство таких издательских программных продуктов, как QuarkXPress и т.д., Используют эту библиотеку.

EDIT: Я пытался найти быстрый учебник для использования ICI BreakIterator на границах предложений, но я нашел пример границ слов - (Расчет границы границ будет очень похож, возможно, нужно просто заменить createWordInstance на createSentenceInstance ниже)

void listWordBoundaries(const UnicodeString& s) {
    UErrorCode status = U_ZERO_ERROR;
    BreakIterator* bi = BreakIterator::createWordInstance(Locale::getUS(), status);


    bi->setText(s);
    int32_t p = bi->first();
    while (p != BreakIterator::DONE) {
        printf("Boundary at position %d\n", p);
        p = bi->next();
    }
    delete bi;
}
0

Поиск слов, которые заканчиваются в течение некоторого периода, довольно тривиален, просто проверьте, если word.back() == '.' , Вам также нужно будет word.empty() проверить word.empty(), так как back() - неопределенное поведение, если строка пуста. Если ваш компилятор не поддерживает С++ 11, вы также можете сделать более длинный путь, со word[word.size() - 1] == '.' ,

Вот базовый пример, который наивно разделяет предложения, используя любое слово, которое заканчивается на ".":

#include <iostream>
#include <string>
#include <vector>

int main(int argc, char** argv) {
    if (argc == 1) {
        std::cerr << "Usage: " << argv[0] << " [text to split]\n"
            << "Splits the input text into one sentence per line." << std::endl;
        return 1;
    }

    std::vector<std::string> sentences;
    std::string current_sentence;
    for (int i = 1; i < argc; ++i) {
        std::string word(argv[i]);
        current_sentence.append(word);
        current_sentence.push_back(' ');
        /* use word.back() == '.' for C++11 */
        if (!word.empty() && word[word.size() - 1] == '.') {
            sentences.push_back(current_sentence);
            current_sentence.clear();
        }
    }
    if (!current_sentence.empty()) {
        sentences.push_back(current_sentence);
    }

    for (size_t i = 0; i < sentences.size(); ++i) {
        std::cout << sentences[i] << std::endl;
    }
    return 0;
}

Выполнить:

$ g++ test.cpp
$ ./a.out This is a test. And a second sentence. So we meet again Mr. Bond.
This is a test. 
And a second sentence. 
So we meet again Mr. 
Bond.

Обратите внимание, как он думает, это конец предложения.

Я не уверен в умном способе справиться с этим, но один (хрупкий) вариант состоит в том, чтобы составить список слов, которые не являются концом предложений, а затем проверить, включено ли это слово в список:

#include <algorithm>
#include <iostream>
#include <set>
#include <string>
#include <vector>

const std::string tmp[] = {
    "dr.",
    "mr.",
    "mrs.",
    "ms.",
    "rd.",
    "st."
};
const std::set<std::string> ABBREVIATIONS(tmp, tmp + sizeof(tmp) / sizeof(tmp[0]));

bool has_period(const std::string& word) {
    return !word.empty() && word[word.size() - 1] == '.';
}

bool is_abbreviation(std::string word) {
    /* Convert to lowercase, so we don't need to check every possible
     * variation of each word. Remove this (and update the set initialization)
     * if you don't care about handling poor grammar. */
    std::transform(word.begin(), word.end(), word.begin(), ::tolower);

    /* Check if the word is an abbreviation. */
    return ABBREVIATIONS.find(word) != ABBREVIATIONS.end();
}

int main(int argc, char** argv) {
    if (argc == 1) {
        std::cerr << "Usage: " << argv[0] << " [text to split]\n"
            << "Splits the input text into one sentence per line." << std::endl;
        return 1;
    }

    std::vector<std::string> sentences;
    std::string current_sentence;
    for (int i = 1; i < argc; ++i) {
        std::string word(argv[i]);
        current_sentence.append(word);
        current_sentence.push_back(' ');
        if (has_period(word) && !is_abbreviation(word)) {
            sentences.push_back(current_sentence);
            current_sentence.clear();
        }
    }
    if (!current_sentence.empty()) {
        sentences.push_back(current_sentence);
    }

    for (size_t i = 0; i < sentences.size(); ++i) {
        std::cout << sentences[i] << std::endl;
    }
    return 0;
}

В С++ 11 вы можете сделать его более эффективным с помощью unordered_set и проще с помощью std::string::back и более легкой инициализации (std::set<std::string> PERIOD_WORDS = { "dr.", "mr.", "mrs."/*etc.*/}).

Выполнение этой версии:

$ g++ test.cpp
$ ./a.out This is a test. And a second sentence. So we meet again Mr. Bond.
This is a test. 
And a second sentence. 
So we meet again Mr. Bond.

Но, конечно, он все еще не поймал ни одного случая, в котором мы явно не программировали:

$ ./a.out Example Ave. is just north of here.
Example Ave. 
is just north of here. 

И даже если бы мы добавили это, было бы чрезвычайно сложно обнаружить такие случаи, как "Я живу на примере Ave.", где предложение заканчивается аббревиатурой. Надеюсь, что это полезно в начале.


EDIT: Я просто прочитал предложение, в котором была опубликована статья Википедии, связанная с комментариями, и было бы относительно легко включить правило:

(c) Если следующий токен капитализируется, он заканчивает предложение.

Что-то вроде:

#include <algorithm>
#include <iostream>
#include <set>
#include <string>
#include <vector>

const std::string tmp[] = {
    "ave.",
    "dr.",
    "mr.",
    "mrs.",
    "ms.",
    "rd.",
    "st."
};
const std::set<std::string> PERIOD_WORDS(tmp, tmp + sizeof(tmp) / sizeof(tmp[0]));

bool has_period(const std::string& word) {
    return !word.empty() && word[word.size() - 1] == '.';
}

bool is_abbreviation(std::string word) {
    /* Convert to lowercase, so we don't need to check every possible
     * variation of each word. Remove this (and update the set initialization)
     * if you don't care about handling poor grammar. */
    std::transform(word.begin(), word.end(), word.begin(), ::tolower);

    /* Check if the word is a word that ends with a period. */
    return PERIOD_WORDS.find(word) != PERIOD_WORDS.end();
}

bool is_capitalized(const std::string& word) {
    return !word.empty() && std::isupper(word[0]);
}

int main(int argc, char** argv) {
    if (argc == 1) {
        std::cerr << "Usage: " << argv[0] << " [text to split]\n"
            << "Splits the input text into one sentence per line." << std::endl;
        return 1;
    }

    std::vector<std::string> sentences;
    std::string current_sentence;
    for (int i = 1; i < argc; ++i) {
        std::string word(argv[i]);
        std::string next_word(i + 1 < argc ? argv[i + 1] : "");
        current_sentence.append(word);
        current_sentence.push_back(' ');
        if (next_word.empty()
            || has_period(word)
            && (!is_abbreviation(word) || is_capitalized(next_word))) {
            sentences.push_back(current_sentence);
            current_sentence.clear();
        }
    }

    for (size_t i = 0; i < sentences.size(); ++i) {
        std::cout << sentences[i] << std::endl;
    }
    return 0;
}

И тогда даже такие случаи, как эта работа:

$ ./a.out Example Ave. is just north of here. I live on Example Ave. Test test test.
Example Ave. is just north of here. 
I live on Example Ave. 
Test test test.

Но он по-прежнему не может справиться с определенными случаями:

$ ./a.out Mr. Adams lives on Example Ave. Example Ave. is just north of here. I live on Example Ave. Test test test.
Mr. 
Adams lives on Example Ave. 
Example Ave. is just north of here. 
I live on Example Ave. 
Test test test. 
  • 0
    Это спасибо.
  • 0
    Как вы можете использовать Argv [i] таким образом?
Показать ещё 4 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню