Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

Упражнение 6.19

string generate_salutation( string generic1, string lastname, string generic2, string::size_type

pos,

Напишите функцию:

int length );

которая в строке

string generic1( "Dear Ms Daisy:" );

заменяет Daisy и Ms (миссис). Вместо Daisy подставляется параметр lastname, а вместо Ms подстрока

string generic2( "MrsMsMissPeople" );

длины length, начинающаяся с pos.

string

lastName( "Anna P" );

string greetings =

Например, вызов

generate_salutation( generici, lastName, generic2, 5, 4 );

вернет строку:

Dear Miss AnnaP:

6.12. Строим отображение позиций слов

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

В map хранятся пары ключ/значение. Ключ играет роль индекса для доступа к ассоциированному с ним значению. В нашей программе каждое уникальное слово текста будет служить ключом, а значением станет вектор, содержащий пары (номер строки, номер колонки). Для доступа применяется оператор взятия индекса. Например:

string query( "pickle" ); vector< location > *locat;

// возвращается location<vector>*, ассоциированный с "pickle"

locat = text_map[ query ];

Ключом здесь является строка, а значение имеет тип location<vector>*.

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

#include <map>

Какие основные действия производятся над ассоциативными контейнерами? Их заполняют элементами или проверяют на наличие определенного элемента. В следующем подразделе мы покажем, как определить пару ключ/значение и как поместить такие пары в контейнер. Далее мы расскажем, как сформулировать запрос на поиск элемента и извлечь значение, если элемент существует.

6.12.1. Определение объекта map и заполнение его элементами

Чтобы определить объект класса map, мы должны указать, как минимум, типы ключа и значения. Например:

map<string,int> word_count;

Здесь задается объект word_count типа map, для которого ключом служит объект типа

class

emp loy ee;

string, а ассоциированным с ним значением – объект типа int. Аналогично map<int,employee*> personnel;

определяет personnel как отображение ключа типа int (уникальный номер служащего) на указатель, адресующий объект класса employee.

typedef pair<short,short> location;

typedef vector<location> loc;

Для нашей поисковой системы полезно такое отображение:

map<string,loc*> text_map;

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

map<string,loc*,

// ключ, значение

less<string>,

// оператор сравнения

allocator>

// распределитель памяти по

умолчанию

определение: text_map;

По умолчанию сортировка ассоциативных контейнеров производится с помощью операции “меньше”. Однако можно указать и другой оператор сравнения (см. раздел 12.3 об объектах-функциях).

После того как отображение определено, необходимо заполнить его парами

#include <map> #include <string>

map<string,int> word_count;

word_count[ string("Anna") ] = 1;

word_count[ string("Danny") ] = 1;

word_count[ string("Beth") ] = 1;

ключ/значение. Интуитивно хочется написать примерно так:

// и так далее ...

Когда мы пишем:

word_count[ string("Anna") ] = 1;

на самом деле происходит следующее:

1.Безымянный временный объект типа string инициализируется значением "Anna" и передается оператору взятия индекса, определенному в классе map.

2.Производится поиск элемента с ключом "Anna" в массиве word_count. Такого элемента нет.

3.В word_count вставляется новая пара ключ/значение. Ключом является, естественно, строка "Anna". Значением – 0, а не 1.

4.После этого значению присваивается величина 1.

Если элемент отображения вставляется в отображение с помощью операции взятия индекса, то значением этого элемента становится значение по умолчанию для его типа данных. Для встроенных арифметических типов – 0.

Следовательно, если инициализация отображения производится оператором взятия индекса, то каждый элемент сначала получает значение по умолчанию, а затем ему явно присваивается нужное значение. Если элементы являются объектами класса, у которого инициализация по умолчанию и присваивание значения требуют больших затрат времени, программа будет работать правильно, но недостаточно эффективно.

// предпочтительный метод вставки одного элемента

word_count.insert( map<string,i nt>::

value_type( string("Anna"), 1 )

Для вставки одного элемента предпочтительнее использовать следующий метод:

);

В контейнере map определен тип value_type для представления хранимых в нем пар

map< string,int

>::

ключ/значение. Строки

value_type( string("Anna"), 1 )

создают объект pair, который затем непосредственно вставляется в map. Для удобства чтения можно использовать typedef:

typedef map<string,int>::value_type valType;

Теперь операция вставки выглядит проще:

word_count.insert( valType( string("Anna"), 1 ));

Чтобы вставить элементы из некоторого диапазона, можно использовать метод

map< string, int > word_count; // ... заполнить

map< string,int > word_count_two;

// скопируем все пары ключ/значение

insert(), принимающий в качестве параметров два итератора. Например: word_count_two.insert(word_count.begin(),word_count.end());

Мы могли бы сделать то же самое, просто проинициализировав одно отображение

// инициализируем копией всех пар ключ/значение

другим:

map< string, int > word_count_two( word_count );

Посмотрим, как можно построить отображение для хранения нашего текста. Функция separate_words(), описанная в разделе 6.8, создает два объекта: вектор строк, хранящий все слова текста, и вектор позиций, хранящий пары (номер строки, номер колонки) для каждого слова. Таким образом, первый объект дает нам множество

значений ключей нашего отображения, а второй – множество ассоциированных с ними значений.

separate_words() возвращает эти два вектора как объект типа pair, содержащий

указатели на них. Сделаем эту

пару аргументом функции build_word_map(), в

 

// typedef для удобства чтения

 

 

typedef pair< short,short >

 

 

location;

loc;

 

typedef vector< location >

 

typedef vector< string >

text;

 

typedef pair< text*,loc* >

 

 

text_loc;

 

 

extern map< string, loc* >*

 

результате которой будет получено соответствие между словами и позициями: build_word_map( const text_loc *text_locations );

Сначала выделим память для пустого объекта map и получим из аргумента-пары

map<string,loc*> *word_map = new map< string, loc* >;

vector<string> *text_words = text_locations- >first;

указатели на векторы:

vector<location> *text_locs = text_locations->second;

Теперь нам надо синхронно обойти оба вектора, учитывая два случая:

слово встретилось впервые. Нужно поместить в map новую пару ключ/значение;

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

Вот текст функции:

register int elem_cnt = text_words->size(); for ( int ix=0; ix < elem_cnt; ++ix )

{

string textword = ( *text_words )[ ix ];

//игнорируем слова короче трех букв

//или присутствующие в списке стоп-слов if ( textword.size() < 3 ||

exclusion_set.count( textword )) continue;

//определяем, занесено ли слово в отображение

//если count() возвращает 0 - нет: добавим его if ( ! word_map->count((*text_words)[-ix] ))

{

loc *ploc = new vector<location>; ploc->push_back( (*text_locs) [ix] ); word_map->insert(value_type((*text_words)

[ix],ploc));

}

else

// добавим дополнительные координаты (*word_map)[(*text_words)[ix]]->

push_back((*text_locs)[ix]);

}

(*word_map)[(*text_words)[ix]]-

>

Синтаксически сложное выражение push_back((*text_locs)[ix]);

//возьмем слово, которое надо обновить string word = (*text_words) [ix];

//возьмем значение из вектора позиций vector<location> *ploc = (*word_map)

[ word ];

//возьмем позицию - пару координат loc = (*text_locs)[ix];

//вставим новую позицию

будет проще понять, если мы разложим его на составляющие: ploc->push_back(loc);

Выражение все еще остается сложным, так как наши векторы представлены указателями. Поэтому вместо употребления оператора взятия индекса:

string word = text_words[ix]; // ошибка

мы вынуждены сначала разыменовать указатель на вектор:

string word = (*text_words) [ix]; // правильно