Упражнение 12.27. Классы TextQuery и QueryResult используют только те возможности, которые уже были описаны ранее. Не заглядывая вперед, напишите собственные версии этих классов.
Упражнение 12.28. Напишите программу, реализующую текстовые запросы, не определяя классы управления данными. Программа должна получать файл и взаимодействовать с пользователем, запрашивая слова, искомые в этом файле. Используйте контейнеры vector, map и set для хранения данных из файла и создания результатов запросов.
Упражнение 12.29. Перепишите цикл взаимодействия с пользователем, используя цикл do while (см. раздел 5.4.4). Объясните, какая версия предпочтительней и почему.
Начнем с определения класса TextQuery. Пользователь создает объекты этого класса, предоставляя поток istream для чтения входного файла. Этот класс предоставляет также функцию query(), которая получает строку и возвращает объект класса QueryResult, представляющий строки, в которых присутствует искомое слово.
Переменные-члены класса должны учитывать совместное использование с объектами класса QueryResult. Класс QueryResult совместно использует вектор, представляющий входной файл и наборы, содержащие номера строк, связанные с каждым словом во вводе. Следовательно, у нашего класса есть две переменные-члена: указатель shared_ptr на динамически созданный вектор, содержащий входной файл, а также карта строк и указателей shared_ptr. Карта ассоциирует каждое слово в файле с динамически созданным набором, содержащим номера строк, в которых присутствует это слово.
Чтобы сделать код немного понятней, определим также тип-член (см. раздел 7.3.1) для обращения к номерам строк, которые являются индексами вектора строк:
class QueryResult; //
//
class TextQuery {
public:
using line_no = std::vector
TextQuery(std::ifstream&);
QueryResult query(const std::string&) const;
private:
std::shared_ptr
//
//
std::map
std::shared_ptr
};
Самая трудная часть этого кода — разобраться в именах классов. Как обычно, для кода из файла заголовка применяется часть std::, указывающая имя библиотеки (см. раздел 3.1). Но в данном случае частое повторение имени std:: делает код немного менее понятным для чтения. Рассмотрим пример:
std::map
Его будет проще понять, переписав так:
map
TextQuery()Конструктор TextQuery() получает поток ifstream, позволяющий читать строки по одной:
//
TextQuery::TextQuery(ifstream &is): file(new vector
string text;
while (getline(is, text)) { //
file->push_back(text); //
int n = file->size() - 1; //
istringstream line(text); //
string word;
while (line >> word) { //
//
//
auto &lines = wm[word]; //
if (!lines) //
//
lines.reset(new set
lines->insert(n); //
}
}
}
Список инициализации конструктора резервирует новый вектор для содержания текста из входного файла. Функция getline() используется для чтения из файла по одной строке за раз и их помещения в вектор. Поскольку file — это указатель shared_ptr, используем оператор -> для обращения к его значению, чтобы вызвать функцию push_back() для того элемента вектора, на который указывает указатель file.