С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 45
Текст из файла (страница 45)
Читаем текстовый файлПервая наша задача – прочитать текстовый файл, в котором будет производиться поиск.Нам нужно сохранить следующую информацию: само слово, номер строки и позицию встроке, где слово встречается.Как получить одну строку текста? Стандартная библиотека предоставляет для этогоistream&функцию getline():getline( istream &is, string str, char delimiter );getline()берет из входного потока все символы, включая пробелы, и помещает их вобъект типа string, до тех пор пока не встретится символ delimiter, не будет достигнутконец файла или количество полученных символов не станет равным величине,возвращаемой функцией-членом max_size()класса string.Мы будем помещать каждую такую строку в вектор.Мы вынесли код, читающий файл, в функцию, названную retrieve_text().
В объектетипа pair дополнительно сохраняется размер и номер самой длинной строки. (Полныйтекст программы приводится в разделе 6.14.)Вот реализация функции ввода файла:1515 Программа компилировалась компилятором, не поддерживающим значенийпараметров по умолчанию шаблонов.
Поэтому нам пришлось явно указать аллокатор:vector<string,allocator> *lines_of_text;Для компилятора, полностью соответствующего стандарту С++, достаточноотметить тип элементов:vector<string> *lines_of_text;266С++ для начинающих// возвращаемое значение - указатель на строковый векторvector<string,allocator>*retrieve_text(){string file_name;cout << "please enter file name: ";cin >> file_name;// откроем файл для ввода ...ifstream 1nfile( file_name.c_str(), ios::in );if ( ! infile ) {cerr << "oops! unable to open file "<< file_name << " -- bailing out!\n";exit( -1 );}else cout << '\n';vector<string, allocator> *1ines_of_text =new vector<string, allocator>;string textime;typedef pair<string::size_type, int> stats;stats maxline;intlinenum = 0;while ( getline( infile, textline, '\n' )) {cout << "line read: " << textline << '\n';if ( maxline.first < textline.size() ) {maxline.first = textline.size() ;maxline.second = linenum;}1ines_of_text->push_back( textline );linenum++;}return lines_of_text;}Вот как выглядит вывод программы (размер страницы книги недостаточен, чтобырасположить напечатанные строки во всю длину, поэтому мы сделали в тексте отступы,показывающие, где реально заканчивалась строка):please enter file name: a1ice_emmaline read: Alice Emma has long flowing red hair.
Her Daddy saysline read: when the wind blows through her hair, it looksalmost alive,line read: like a fiery bird in flight. A beautiful fiery bird,he tells her,line read: magical but untamed. "Daddy, shush, there is no suchthing, "line read: she tells him, at the same time wanting him to tellher more.line read: Shyly, she asks, "I mean. Daddy, is there?"number of lines: 6maximum length: 66longest line: like a fiery bird in flight. A beautiful fierybird, he tells her,267С++ для начинающихПосле того как все строки текста сохранены, нужно разбить их на слова. Сначала мыотбросим знаки препинания.
Например, возьмем строку из части “Anna Livia Plurrabelle”романа “Finnegans Wake”."For every tale there's a telling,and that's the he and she of it."В приведенном фрагменте есть следующие знаки препинания:"Forthere'stelling,that'sit."А хотелось бы получить:FortheretellingthatitМожно возразить, чтоthere'sдолжно превратиться вthere isно мы-то движемся в другом направлении: следующий шаг – это отбрасываниесемантически нейтральных слов, таких, как is, that, and, it и т.д. Так что для даннойстрочки из “Finnegans Wake” только два слова являются значимыми: tale и telling, итолько по этим словам будет выполняться поиск. (Мы реализуем набор стоп-слов спомощью контейнерного типа set, который подробно рассматривается в следующемразделе.)После удаления знаков препинания нам необходимо превратить все прописные буквы встрочные, чтобы избежать проблем с поиском в таких, например, строках:Home is where the heart is.A home is where they have to let you in.Несомненно, запрос слова home должен найти обе строки.Мы должны также обеспечить минимальную поддержку учета словоформ: отбрасыватьокончания слов, чтобы слова dog и dogs, love, loving и loved рассматривались системойкак одинаковые.268С++ для начинающихВ следующем разделе мы вернемся к описанию стандартного класса 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(), которая возвращает позицию первогосимвола, соответствующего одному из заданных в строке-параметре. Вот как найтипервый символ, являющийся цифрой:269С++ для начинающих#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 в конце каждой итерации:270С++ для начинающих// исправленная версия цикла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 )пробела и никаких других, то можем явно задать его в качестве параметра функции:// ...// фрагмент программы// pos: позиция на 1 большая конца слова// prev_pos: позиция начала словаstring::size_type pos = 0, prev_pos = 0;while (( pos = textline.find_first_of( ' ', pos ))!= string::npos ){// ...// запомнить позицию начала словаprev_pos = ++pos;Чтобы узнать длину слова, введем еще одну переменную:}На каждой итерации prev_pos указывает позицию начала слова, а pos – позициюследующего символа после его конца.
Соответственно, длина слова равна:pos - prev_pos; // длина словаПосле того как мы выделили слово, необходимо поместить его в строковый вектор. Этоможно сделать, копируя в цикле символы из textline с позиции prev_pos до pos -1.Функция substr() сделает это за нас:271С++ для начинающих// фрагмент программы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(). Помимо сохраненияслов в векторе строк, она вычисляет координаты каждого слова – номер строки и колонки(нам эта информация потребуется впоследствии).272С++ для начинающихtypedeftypedeftypedeftypedefpair<short,short>vector<location>vector<string>pair<text* ,loc*>273location;loc;text;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 textfor ( ; 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()выглядит следующим образом:}С++ для начинающихВот часть распечатки, выданной тестовой версией separate_words():textline: Alice Emma has long flowing red hair.