Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
237
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

С++ для начинающих

272

// фрагмент программы vector<string> words;

while (( pos = textline.find_first_of( ' ', pos )) != string::npos )

{

words.push_back( textline.substr( prev_pos, pos-prev_pos));

prev_pos = ++pos;

}

Функция substr() возвращает копию подстроки. Первый ее аргумент обозначает первую позицию, второй длину подстроки. (Второй аргумент можно опустить, тогда подстрока включит в себя остаток исходной строки, начиная с указанной позиции.)

В нашей реализации допущена ошибка: последнее слово не будет помещено в контейнер. Почему? Возьмем строку:

seaspawn and seawrack

После каждого из первых двух слов поставлен пробел. Два вызова функции find_first_of() вернут позиции этих пробелов. Третий же вызов вернет string::npos, и цикл закончится. Таким образом, последнее слово останется необработанным.

Вот полный текст функции, названной нами separate_words(). Помимо сохранения слов в векторе строк, она вычисляет координаты каждого слова номер строки и колонки (нам эта информация потребуется впоследствии).

С++ для начинающих

273

typedef pair<short,short> location; typedef vector<location> loc; typedef vector<string> text; typedef pair<text* ,loc*> text_loc;

text_loc*

separate_words( const vector<string> *text_file )

{

//words: содержит набор слов

//locations: содержит информацию о строке и позиции

//каждого слова

vector<string>

*words = new vector<string>;

vector<location>

* locations = new vector<location>;

short line_pos = 0; // текущий номер строки // iterate through each line of text

for ( ; line_pos < text_file->size(); ++line_pos )

//textline: обрабатываемая строка

//word_pos: позиция в строке

short word_pos = 0;

string textline = (*text_file) [ line_pos ];

string::size_type pos = 0, prev_pos = 0;

while (( pos = textline.find_first_of( ' ', pos )) != string::npos )

{

//сохраним слово words->push_back(

textline.substr( prev_pos, pos - prev_pos ));

//сохраним информацию о его строке и позиции locations->push_back(

make_pair( line_pos, word_pos ));

// сместим позицию для следующей итерации

++word_pos; prev_pos = ++pos;

}

// обработаем последнее слово words->push_back(

textline.substr( prev_pos, pos - prev_pos ));

locations->push_back(

make_pair( line_pos, word_pos ));

}

return new text_loc( words, locations );

}

int main()

{

vector<string> *text_file = retrieve_text();

text_loc *text_locations = separate_words( text_file ); // ...

Теперь функция main()выглядит следующим образом:

}

С++ для начинающих

274

Вот часть распечатки, выданной тестовой версией separate_words():

textline: Alice Emma has long flowing red hair. Her Daddy says

eol: 52 pos: 5 line: 0 word: 0 substring: Alice eol: 52 pos: 10 line: 0 word: 1 substring: Emma eol: 52 pos: 14 line: 0 word: 2 substring: has eol: 52 pos: 19 line: 0 word: 3 substring: long eol: 52 pos: 27 line: 0 word: 4 substring: flowing eol: 52 pos: 31 line: 0 word: 5 substring: red eol: 52 pos: 37 line: 0 word: 6 substring: hair. eol: 52 pos: 41 line: 0 word: 7 substring: Her eol: 52 pos: 47 line: 0 word: 8 substring: Daddy last word on line substring: says

...

textline: magical but untamed. "Daddy, shush, there is no such thing,"

eol: 60 pos: 7 line: 3 word: 0 substring: magical eol: 60 pos: 11 line: 3 word: 1 substring: but eol: 60 pos: 20 line: 3 word: 2 substring: untamed eol: 60 pos: 28 line: 3 word: 3 substring: "Daddy, eol: 60 pos: 35 line: 3 word: 4 substring: shush, eol: 60 pos: 41 line: 3 word: 5 substring: there eol: 60 pos: 44 line: 3 word: 6 substring: is

eol: 60 pos: 47 line: 3 word: 7 substring: no eol: 60 pos: 52 line: 3 word: 8 substring: such last word on line substring: thing,":

...

textline: Shy1y, she asks, "I mean, Daddy: is there?"

eol: 43 pos: 6 line: 5 word: 0 substring: Shyly, eol: 43 pos: 10 line: 5 word: 1 substring: she eol: 43 pos: 16 line: 5 word: 2 substring: asks, eol: 43 pos: 19 line: 5 word: 3 substring: "I eol: 43 pos: 25 line: 5 word: 4 substring: mean, eol: 43 pos: 32 line: 5 word: 5 substring: Daddy, eol: 43 pos: 35 line: 5 word: 6 substring: is last word on line substring: there?":

Прежде чем продолжить реализацию поисковой системы, вкратце рассмотрим оставшиеся функции-члены класса string, предназначенные для поиска. Функция

string river( "Mississippi" );

string::size_type first_pos = river.find( "is" );

rfind() ищет последнее, т.е. самое правое, вхождение указанной подстроки: string::size_type 1ast_pos = river.rfind( "is" );

find() вернет 1, указывая позицию первого вхождения подстроки "is", а rfind() 4 (позиция последнего вхождения "is").

find_first_not_of() ищет первый символ, не содержащийся в строке, переданной как параметр. Например, чтобы найти первый символ, не являющийся цифрой, можно написать:

С++ для начинающих

275

string elems( "0123456789" ); string dept_code( "03714p3" );

// возвращается позиция символа 'p'

string::size_type pos = dept_code.find_first_not_of(elems) ;

find_last_of() ищет последнее вхождение одного из указанных символов. find_last_not_of() последний символ, не совпадающий ни с одним из заданных. Все эти функции имеют второй необязательный параметр позицию в исходной строке, с которой начинается поиск.

Упражнение 6.13 Напишите программу, которая ищет в строке

"ab2c3d7R4E6"

цифры, а затем буквы, используя сначала find_first_of(), а потом find_first_not_of().

Упражнение 6.14 Напишите программу, которая подсчитывает все слова и определяет самое длинное и

string linel = "We were her pride of 10 she named us --"; string line2 = "Benjamin, Phoenix, the Prodigal"

string line3 = "and perspicacious pacific Suzanne";

самое короткое из них в строке sentence: string sentence = linel + line2 + line3;

Если несколько слов имеют длину, равную максимальной или минимальной, учтите их все.

6.9. Обрабатываем знаки препинания

После того как мы разбили каждую строку на слова, необходимо избавиться от знаков препинания. Пока из строки

magical but untamed. "Daddy, shush, there is no such thing,"

у нас получился такой набор слов:

magical but untamed. "Daddy, shush, there is

no

С++ для начинающих

276

such thing,"

Как нам теперь удалить ненужные знаки препинания? Для начала определим строку, содержащую все символы, которые мы хотим удалить:

string filt_elems( "\",.;:!?)(\\/" );

(Обратная косая черта указывает на то, что следующий за ней символ должен в данном контексте восприниматься буквально, а не как специальная величина. Так, \" обозначает символ двойной кавычки, а не конец строки, а \\ символ обратной косой черты.)

Теперь можно применить функцию-член find_first_of() для поиска всех вхождений

while (( pos = word.find_first_of( filt_elems, pos ))

нежелательных символов:

!= string::npos )

Найденный символ удаляется с помощью функции-члена erase():

word.erase(pos,1);

Первый аргумент этой функции означает позицию подстроки, а второй ее длину. Мы удаляем один символ, находящийся в позиции pos. Второй аргумент является необязательным; если его опустить, будут удалены все символы от pos до конца строки.

Вот полный текст функции filter_text(). Она имеет два параметра: указатель на

void

filter_text( vector<string> *words, string filter )

{

vector<string>::iterator iter = words->begin(); vector<string>::iterator iter_end = words->end();

// Если filter не задан, зададим его сами if ( ! filter.size() )

filter.insert( 0, "\".," );

while ( iter != iter_end ) { string::size_type pos = 0;

// удалим каждый найденный элемент

while (( pos = (*iter).find_first_of( filter, pos )) != string::npos )

(*iter).erase(pos,1); iter++;

}

вектор строк, содержащий текст, и строку с символами, которые нужно убрать.

}

Почему мы не увеличиваем значение pos на каждой итерации? Что было бы, если бы мы написали:

С++ для начинающих

277

while (( pos = (*iter).find_first_of( filter, pos )) != string::npos )

{

(*iter).erase(pos,1); ++ pos; // неправильно...

}

Возьмем строку

thing,"

На первой итерации pos получит значение 5 , т.е. позиции, в которой находится запятая.

После удаления запятой строка примет вид

thing"

Теперь в 5-й позиции стоит двойная кавычка. Если мы увеличим значение pos, то пропустим этот символ.

string filt_elems( "\",.;:!?)(\\/" );

Так мы будем вызывать функцию filter_text(): filter_text( text_locations->first, filt_elems );

А вот часть распечатки, сделанной тестовой версией filter_text():

filter_text: untamed. found! : pos: 7. after: untamed

filter_text: "Daddy, found! : pos: 0. after: Daddy, found! : pos: 5. after: Daddy

filter_text: thing," found! : pos: 5. after: thing" found! : pos: 5. after: thing

filter_text: "I found! : pos: 0. after: I

filter_text: Daddy, found! : pos: 5. after: Daddy

filter_text: there?" found! : pos: 5. after: there" found! : pos: 5. after: there

С++ для начинающих

278

Упражнение 6.15

Напишите программу, которая удаляет все символы, кроме STL из строки:

"/.+(STL).$1/"

используя сначала erase(pos,count), а затем erase(iter,iter).

Упражнение 6.16

string sentence( "kind of" ); string s1 ( "whistle" )

Напишите программу, которая с помощью разных функций вставки из строк string s2 ( "pixie" )

составит предложение

"A whistling-dixie kind of walk"

6.10. Приводим слова к стандартной форме

Одной из проблем при разработке текстовых поисковых систем является необходимость распознавать слова в различных словоформах, такие, как cry, cries и cried, baby и babies, и, что гораздо проще, написанные заглавными и строчными буквами, например home и Home. Первая задача, распознавание словоформ, слишком сложна, поэтому мы приведем здесь ее заведомо неполное решение. Сначала заменим все прописные буквы

void

strip_caps( vector<string,allocator> *words )

{

vector<string,allocator>::iterator iter=words->begin() ; vector<string,allocator>::iterator iter_end=words->end() ;

string caps( "ABCDEFGHIJKLMNOPQRSTUVWXYZ" );

while ( iter != iter_end ) { string::size_type pos = 0;

while (( pos = (*iter).find_first_of( caps, pos )) != string::npos )

(*iter)[ pos ] = to1ower( (*iter)[pos] ); ++iter;

}

строчными:

}

Функция

to1ower( (*iter)[pos] );

С++ для начинающих

279

входит в стандартную библиотеку С. Она заменяет прописную букву соответствующей ей строчной. Для использования tolower() необходимо включить заголовочный файл:

#include <ctype.h>

(В этом файле объявлены и другие функции, такие, как isalpha(), isdigit(), ispunct(), isspace(), toupper(). Полное описание этих функций см. [PLAUGER92].

Стандартная библиотека С++ включает класс ctype, который инкапсулирует всю функциональность стандартной библиотеки Си, а также набор функций, не являющихся членами, например toupper(), tolower() и т.д. Для их использования нужно включить

заголовочный файл

#include <locale>

Однако наша реализация компилятора еще не поддерживала класс ctype, и нам пришлось использовать стандартную библиотеку Си.)

Проблема словоформ слишком сложна для того, чтобы пытаться решить ее в общем виде.

Но даже самый примитивный вариант способен значительно улучшить работу нашей поисковой системы. Все, что мы сделаем в данном направлении, – удалим букву 's' на

void suffix_text( vector<string,allocator> *words )

{

vector<string,allocator>::iterator iter = words->begin(), iter_end = words->end();

while ( iter != iter_end ) {

// оставим слова короче трех букв как есть if ( (*iter).size() <= 3 )

{ ++iter; continue; }

if ( (*iter)[ (*iter).size()-1 ] == 's' ) suffix_s( *iter );

//здесь мы могли бы обработать суффиксы

//ed, ing, 1y

++iter;

}

концах слов:

}

Слова из трех и менее букв мы пропускаем. Это позволяет оставить без изменения, например, has, its, is и т.д., однако слова tv и tvs мы не сможем распознать как одинаковые.

string::size_type pos() = word.size()-3; string ies( "ies" );

if ( ! word.compare( pos3, 3, ies )) { word.replace( pos3, 3, 1, 'у' ); return;

Если слово кончается на "ies", как babies и cries, необходимо заменить "ies" на "y":

С++ для начинающих

280

}

compare() возвращает 0, если две строки равны. Первый аргумент, pos3, обозначает начальную позицию, второй длину сравниваемой подстроки (в нашем случае 3). Третий аргумент, ies, – строка-эталон. (На самом деле существует шесть вариантов функции compare(). Остальные мы покажем в следующем разделе.)

replace() заменяет подстроку набором символов. В данном случае мы заменяем подстроку "ies" длиной в 3 символа единичным символом 'y'. (Имеется десять перегруженных вариантов функции replace(). В следующем разделе мы коснемся остальных вариантов.)

Если слово заканчивается на "ses", как promises или purposes, нужно удалить

string ses( "ses" );

if ( ! word.compare( pos3, 3, ses )) { word.erase( pos3+l, 2 );

return;

суффикс "es"16:

}

Если слово кончается на "ous", как oblivious, fulvous, cretaceous, или на "is", как genesis, mimesis, hepatitis, мы не будем изменять его. (Наша система несовершенна. Например, в слове kiwis надо убрать последнее 's'.) Пропустим и слова, оканчивающиеся на "ius" (genius) или на "ss" (hiss, lateness, less). Нам поможет

string::size_type spos = 0; string::size_type pos3 = word.size()-3;

// "ous", "ss", "is", "ius" string suffixes( "oussisius" );

if ( !

word.compare(

pos3,

3,

suffixes,

spos, 3

)

||

//

ous

!

word.compare(

pos3,

3,

suffixes,

spos+6,

3

) ||

//

ius

!word.compare( pos3+l, 2, suffixes, spos+2, 2 ) || // ss

!word.compare( pos3+l, 2, suffixes, spos+4, 2 ) ) // is

вторая форма функции compare(): return;

// удалим последнее 's'

Впротивном случае удалим последнее 's': word.erase( pos3+2 );

16 Конечно, в английском языке существуют исключения из правил. Наш эвристический алгоритм превратит crises (множ. число от crisis прим. перев.) в cris. Ошибочка!

С++ для начинающих

281

Имена собственные, например Pythagoras, Brahms, Burne-Jones, не подпадают под общие правила. Этот случай мы оставим как упражнение для читателя, когда будем рассказывать об ассоциативных контейнерах.

Но прежде чем перейти к ним, рассмотрим оставшиеся строковые операции.

Упражнение 6.17

Наша программа не умеет обрабатывать суффиксы ed (surprised), ly (surprisingly) и ing (surprisingly). Реализуйте одну из функций для этого случая:

(a) suffix_ed() (b) suffix_ly()

(c) suffix_ing()

6.11. Дополнительные операции со строками

Вторая форма функции-члена erase() принимает в качестве параметров два итератора, ограничивающих удаляемую подстроку. Например, превратим

string name( "AnnaLiviaPlurabelle" );

typedef string::size_type size_type; size_type startPos = name.find( 'L' ) size_type endPos = name.find_1ast_of( 'b' );

name.erase( name.begin()+startPos,

в строку "Annabelle": name.begin()+endPos );

Символ, на который указывает второй итератор, не входит в удаляемую подстроку.

Для третьей формы параметром является только один итератор; эта форма удаляет все символы, начиная с указанной позиции до конца строки. Например:

name.erase( name. begin()+4 );

оставляет строку "Anna".

Функция-член insert() позволяет вставить в заданную позицию строки другую строку или символ. Общая форма выглядит так:

string_object.insert( position, new_string );

position обозначает позицию, перед которой производится вставка. new_string может

string string_object( "Missisippi" ); string::size_type pos = string_object.find( "isi" );

быть объектом класса string, C-строкой или символом: string_object.insert( pos+1, 's' );