После удаления знаков препинания нам необходимо превратить все прописные буквы в строчные, чтобы избежать проблем с поиском в таких, например, строках:
Home is where the heart is.
A home is where they have to let you in.
Несомненно, запрос слова home должен найти обе строки.
Мы должны также обеспечить минимальную поддержку учета словоформ: отбрасывать окончания слов, чтобы слова dog и dogs, love, loving и loved рассматривались системой как одинаковые.
В следующем разделе мы вернемся к описанию стандартного класса string и рассмотрим многочисленные операции над строками, которые он поддерживает, в контексте дальнейшей разработки нашей поисковой системы.
6.8. Выделяем слова в строке
Нашей первой задачей является разбиение строки на слова. Мы будем вычленять слова, находя разделяющие их пробелы с помощью функции find(). Например, в строке
Alice Emma has long flowing red hair.
насчитывается шесть пробелов, следовательно, эта строка содержит семь слов.
Класс string имеет несколько функций поиска. find() – наиболее простая из них. Она ищет образец, заданный как параметр, и возвращает позицию его первого символа в строке, если он найден, или специальное значение string::npos в противном случае. Например:
#include string
#include iostream
int main() {
string name( "AnnaBelle" );
int pos = name.find( "Anna" );
if ( pos == string::npos )
cout "Anna не найдено!\n";
else cout "Anna найдено в позиции: " pos endl;
}
Хотя позиция подстроки почти всегда имеет тип int, более правильное и переносимое объявление типа результата, возвращаемого find(), таково:
string::size_type
Например:
string::size_type pos = name.find( "Anna" );
Функция find() делает не совсем то, что нам надо. Требуемая функциональность обеспечивается функцией find_first_of(), которая возвращает позицию первого символа, соответствующего одному из заданных в строке-параметре. Вот как найти первый символ, являющийся цифрой:
#include string
#include iostream
int main() {
string numerics( "0123456789" );
string name( "r2d2" );
string:: size_type pos = name.find_first_of( numerics );
cout "найдена цифра в позиции: "
pos "\tэлемент равен "
name[pos] endl;
}
В этом примере pos получает значение 1 (напоминаем, что символы строки нумеруются с 0).
Но нам нужно найти все вхождения символа, а не только первое. Такая возможность реализуется передачей функции find_first_of() второго параметра, указывающего позицию, с которой начать поиск. Изменим предыдущий пример. Можете ли вы сказать, что в нем все еще не вполне удовлетворительно?
#include string
#include iostream
int main() {
string numerics( "0123456789" );
string name( "r2d2" );
string::size_type pos = 0;
// где-то здесь ошибка!
while (( pos = name.find_first_of( numerics, pos ))
!= string::npos )
cout "найдена цифра в позиции: "
pos "\tэлемент равен "
name[pos] endl;
}
В начале цикла pos равно 0, поэтому поиск идет с начала строки. Первое вхождение обнаружено в позиции 1. Поскольку найденное значение не совпадает с string::npos, выполнение цикла продолжается. Для второго вызова find_first_of()значение pos равно 1. Поиск начнется с 1-й позиции. Вот ошибка! Функция find_first_of() снова найдет цифру в первой позиции, и снова, и снова... Получился бесконечный цикл. Нам необходимо увеличивать pos на 1 в конце каждой итерации:
// исправленная версия цикла
while (( pos = name.find_first_of( numerics, pos ))
!= string::npos )
{
cout "найдена цифра в позиции: "
pos "\tэлемент равен "
name[pos] endl;
// сдвинуться на 1 символ
++pos;
}
Чтобы найти все пустые символы (к которым, помимо пробела, относятся символы табуляции и перевода строки), нужно заменить строку numerics в этом примере строкой, содержащей все эти символы. Если же мы уверены, что используется только символ пробела и никаких других, то можем явно задать его в качестве параметра функции:
// фрагмент программы
while (( pos = textline.find_first_of( ' ', pos ))
!= string::npos )
// ...
Чтобы узнать длину слова, введем еще одну переменную: