Определение объекта map и заполнение его элементами
Чтобы определить объект класса map, мы должны указать, как минимум, типы ключа и значения. Например:
map<string,int> word_count;
Здесь задается объект word_count типа map, для которого ключом служит объект типа string, а ассоциированным с ним значением – объект типа int. Аналогично
class employee;
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 ));
Чтобы вставить элементы из некоторого диапазона, можно использовать метод insert(), принимающий в качестве параметров два итератора. Например:
map< string, int > word_count;
// ... заполнить
map< string,int > word_count_two;
// скопируем все пары ключ/значение
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;
typedef vector< location > loc;
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]; // правильно
В конце концов build_word_map() возвращает построенное отображение:
return word_map;
Вот как выглядит вызов этой функции из main():
int main()
{
// считываем файл и выделяем слова
vector<string, allocator> *text_file = retrieve_text();
text_loc *text_locations = separate_words( text_file );
// обработаем слова
// ...
// построим отображение слов на векторы позиций
map<string,lос*,less<string>,allocator>
*text_map = build_word_map( text_locatons );
// ...
}