Я пытаюсь прочитать текстовый файл и ввести его в векторную строку по строке. Мне нужно, чтобы он остановился в конце каждого предложения, затем выберите ключевые слова в предложении. Я понимаю, как найти ключевые слова, но не как заставить его перестать вводить строки в конце. Я использую цикл 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] ничего не имеет перед ним ( для первого токена) или капитализируется и следует за токеном со специальным случаем, чтобы загрузить его в новый токен.
любая помощь будет принята с благодарностью.
Написание собственного ограничителя предложений подходит для небольших проектов или проектов без интернационализации. Для расширенных текстовых решений на границах текста я бы рекомендовал 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;
}
Поиск слов, которые заканчиваются в течение некоторого периода, довольно тривиален, просто проверьте, если 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.