Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

288

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 employee;

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

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

С++ для начинающих

289

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.

С++ для начинающих

290

Если элемент отображения вставляется в отображение с помощью операции взятия индекса, то значением этого элемента становится значение по умолчанию для его типа данных. Для встроенных арифметических типов 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());

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

С++ для начинающих

291

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

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 новую пару ключ/значение;

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

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

С++ для начинающих

292

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]; // правильно