Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]
.pdfС++ для начинающих |
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]; // правильно
С++ для начинающих |
293 |
Вконце концов build_word_map() возвращает построенное отображение: return word_map;
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 );
// ...
Вот как выглядит вызов этой функции из main():
}
6.12.2. Поиск и извлечение элемента отображения
Оператор взятия индекса является простейшим способом извлечения элемента.
// map<string,int> word_count;
Например:
int count = word_count[ "wrinkles" ];
Однако этот способ работает так, как надо, только при условии, что запрашиваемый ключ действительно содержится в отображении. Иначе оператор взятия индекса поместит в отображение элемент с таким ключом. В данном случае в word_count занесется пара
string( "wrinkles" ), 0
Класс map предоставляет две операции для того, чтобы выяснить, содержится ли в нем определенное значение ключа.
∙ count(keyValue): функция-член count() возвращает количество элементов с данным ключом. (Для отображения оно равно только 0 или 1). Если count()
int count = 0;
if ( word_count.count( "wrinkles" ))
вернула 1, мы можем смело использовать индексацию: count = word_count[ "wrinkles" ];
С++ для начинающих |
294 |
∙find(keyValue): функция-член find() возвращает итератор, указывающий на
int count = 0;
map<string,int>::iterator it = word_count.find( "wrinkles" ); if ( it != word_count.end() )
элемент, если ключ найден, и итератор end() в противном случае. Например: count = (*it).second;
Значением итератора является указатель на объект pair, в котором first содержит ключ, а second – значение. (Мы вернемся к этому в следующем подразделе.)
6.12.3. Навигация по элементам отображения
После того как мы построили отображение, хотелось бы распечатать его содержимое. Мы можем сделать это, используя итератор, начальное и конечное значение которого получают с помощью функций-членов begin() и end(). Вот текст функции
void
display_map_text( map<string,loc*> *text_map )
{
typedef map<string,loc*> tmap; tmap::iterator iter = text_map->begin(), iter_end = text_map->end();
while ( iter != iter_end )
{
cout << "word: " << (*iter).first << " ("; int loc_cnt = 0;
loc *text_locs = (*iter).second; loc::iterator liter = text_locs->begin(),
liter_end = text_locs->end();
while (liter != liter_end ) { if ( loc_cnt )
cout << ','; else ++loc_cnt;
cout << '(' << (*liter).first
<< ',' << (*liter).second << ')';
++liter;
}
cout << ")\n"; ++iter;
}
cout << endl;
display_map_text():
}
Если наше отображение не содержит элементов, данная функция не нужна. Проверить, пусто ли оно, можно с помощью функции-члена size():
С++ для начинающих |
295 |
if ( text_map->size() )
display_map_text( text_map );
Но более простым способом, без подсчета элементов, будет вызов функции-члена
if ( ! text_map->empty() )
empty():
display_map_text( text_map );
6.12.4. Словарь
Вот небольшая программа, иллюстрирующая построение отображения, поиск в нем и обход элементов. Здесь используются два отображения. Первое, необходимое для преобразования слов, содержит два элемента типа string. Ключом является слово, которое нуждается в специальной обработке, а значением – слово, заменяющее ключ. Для простоты мы задали пары ключ/значение непосредственно в тексте программы (вы можете модифицировать программу так, чтобы она читала их из стандартного ввода или из файла). Второе отображение используется для подсчета произведенных замен. Текст программы выглядит следующим образом:
#include <vector>
#include <iostream>
С++ для начинающих 296
#include <string>
int main()
{
map< string, string > trans_map;
typedef map< string, string >::value_type valType;
// первое упрощение:
// жестко заданный словарь
trans_map.insert( va1Type( "gratz", "grateful" ));
trans_map.insert( va1Type( "'em", |
"them" |
)); |
trans_map.insert( va1Type( "cuz", |
"because" |
)); |
trans_map.insert( va1Type( "nah", |
"no" |
)); |
trans_map.insert( va1Type( "sez", |
"says" |
)); |
trans_map.insert( va1Type( "tanx", |
"thanks" |
)); |
trans_map.insert( va1Type( "wuz", |
"was" |
)); |
trans_map.insert( va1Type( "pos", |
"suppose" |
)); |
// напечатаем словарь |
|
|
map< string,string >::iterator it; |
|
|
cout << "Наш словарь подстановок: \n\n"; for ( it = trans_map.begin();
it != trans_map.end(); ++it )
cout << "ключ: " << (*it).first << "\t"
<< "значение: " << ("it).second << "\n"; cout << "\n\n";
// второе упрощение: жестко заданный текст string textarray[14]={ "nah", "I", "sez", "tanx",
"cuz", "I", "wuz", "pos", "to", "not", "cuz", "I", "wuz", "gratz" };
vector< string > text( textarray, textarray+14 ); vector< string >::iterator iter;
// напечатаем текст
cout << "Исходный вектор строк:\n\n"; int cnt = 1;
for ( iter = text-begin(); iter != text.end(); ++iter,++cnt )
cout << *iter << ( cnt % 8 ? " " : "\n" );
cout << "\n\n\n";
// map для сбора статистики map< string,int > stats;
typedef map< string,int >::value_type statsValType; // здесь происходит реальная работа
for ( iter=text.begin(); iter != text.end(); ++iter ) if (( it = trans_map.find( *iter ))
!= trans_map.end() )
{
if ( stats.count( *iter )) stats [ *iter ] += 1;
else stats.insert( statsVa1Type( *iter, 1 )); *iter = (*it).second;
}
// напечатаем преобразованный текст
cout << "Преобразованный вектор строк:\n\n"; cnt = 1;
for ( iter = text.begin(); iter != text.end(); ++iter, ++cnt )
cout << *iter << ( cnt % 8 ? " " : "\n" ); cout << "\n\n\n";
// напечатаем статистику
cout << "И напоследок статистика:\n\n"; map<string,int,less<string>,allocator>::iterator siter;
for (siter=stats.begin(); siter!=stats.end(); ++siter) cout << (*siter).first << " "
С++ для начинающих |
297 |
}
Вот результат работы программы:
Наш словарь подстановок:
key: 'em |
value: them |
key: cuz |
value: because |
key: gratz |
value: grateful |
key: nah |
value: no |
key: pos |
value: suppose |
key: sez |
value: says |
key: tanx |
value: thanks |
key: wuz |
value: was |
Исходный вектор строк:
nah I sez tanx cuz I wuz pos to not cuz I wuz gratz
Преобразованный вектор строк:
no I says thanks because I was suppose to not because I was grateful
И напоследок статистика:
cuz было заменено 2 раз(а) gratz было заменено 1 раз(а) nah было заменено 1 раз(а) pos было заменено 1 раз(а) sez было заменено 1 раз(а) tanx было заменено 1 раз(а) wuz было заменено 2 раз(а)
6.12.5. Удаление элементов map
Существуют три формы функции-члена erase() для удаления элементов отображения.
Для единственного элемента используется erase() с ключом или итератором в качестве аргумента, а для последовательности эта функция вызывается с двумя итераторами.
string removal_word;
cout << "введите удаляемое слово: "; cin >> removal_word;
if ( text_map->erase( remova1_word ))
cout << "ok: " << remova1_word << " удалено\n";
Например, мы могли бы позволить удалять элементы из text_map таким образом: else cout << "увы: " << remova1_word << " не найдено!\n";
Альтернативой является проверка: действительно ли слово содержится в text_map?
С++ для начинающих |
298 |
||
|
|
map<string,loc*>::iterator where; |
|
|
|
|
|
|
|
where = text_map.find( remova1_word ); |
|
|
|
if ( where == text_map->end() ) |
|
|
|
cout << "увы: " << remova1_word << " не найдено!\n"; |
|
|
|
else { |
|
|
|
text_map->erase( where ); |
|
|
|
cout << "ok: " << remova1_word << " удалено!\n"; |
|
|
|
} |
|
|
|
||
|
|
|
|
В нашей реализации text_map с каждым словом сопоставляется множество позиций, что |
|
||
несколько усложняет их хранение и извлечение. Вместо этого можно было бы иметь по |
|
||
одной позиции на слово. Но контейнер map не допускает дублирующиеся ключи. Нам |
|
||
следовало бы воспользоваться классом multimap, который рассматривается в разделе |
|
||
6.15. |
|
||
Упражнение 6.20 |
|
||
Определите отображение, где ключом является фамилия, а значением – вектор с именами |
|
||
детей. Поместите туда как минимум шесть элементов. Реализуйте возможность делать |
|
||
запрос по фамилии, добавлять имена и распечатывать содержимое. |
|
||
Упражнение 6.21 |
|
||
Измените программу из предыдущего упражнения так, чтобы вместе с именем ребенка |
|
||
записывалась дата его рождения: пусть вектор-значение хранит пары строк – имя и дата. |
|
||
Упражнение 6.22 |
|
||
Приведите хотя бы три примера, в которых нужно использовать отображение. Напишите |
|
||
определение объекта map для каждого примера и укажите наиболее вероятный способ |
|
||
вставки и извлечения элементов. |
|
6.13. Построение набора стоп-слов
Отображение состоит из пар ключ/значение. Множество (set), напротив, содержит неупорядоченную совокупность ключей. Например, бизнесмен может составить “черный список” bad_checks, содержащий имена лиц, в течение последних двух лет присылавших фальшивые чеки. Множество полезно тогда, когда нужно узнать, содержится ли определенное значение в списке. Скажем, наш бизнесмен, принимая чек от кого-либо, может проверить, есть ли его имя в bad_checks.
Для нашей поисковой системы мы построим набор стоп-слов – слов, имеющих семантически нейтральное значение (артикли, союзы, предлоги), таких, как the, and, into, with, but и т.д. (это улучшает качество системы, однако мы уже не сможем найти первое предложение из знаменитого монолога Гамлета: “To be or not to be?”). Прежде чем добавлять слово к word_map, проверим, не содержится ли оно в списке стоп-слов. Если содержится, проигнорируем его.
6.13.1. Определение объекта set и заполнение его элементами
Перед использованием класса set необходимо включить соответствующий заголовочный файл:
С++ для начинающих |
299 |
#include <set>
Вот определение нашего множества стоп-слов:
set<string> exclusion_set;
exclusion_set.insert( "the" );
Отдельные элементы могут добавляться туда с помощью операции insert(). Например: exclusion_set.insert( "and" );
Передавая insert() пару итераторов, можно добавить целый диапазон элементов. Скажем, наша поисковая система позволяет указать файл со стоп-словами. Если такой
typedef set< string >::difference_type diff_type; set< string > exclusion_set;
ifstream infile( "exclusion_set" ); if ( ! infile )
{
static string default_excluded_words[25] = { "the","and","but","that","then","are","been", "can"."can't","cannot","could","did","for", "had","have","him","his","her","its","into", "were","which","when","with","would"
};
cerr << "предупреждение! невозможно открыть файл стоп-слов! -- " << "используется стандартный набор слов \n";
copy( default_excluded_words, default_excluded_words+25, inserter( exclusion_set, exclusion_set.begin() ));
}
else {
istream_iterator<string,diff_type> input_set(infile),eos; copy( input_set, eos, inserter( exclusion_set,
exclusion_set.begin() ));
файл не задан, берется некоторый набор слов по умолчанию:
}
В этом фрагменте кода встречаются два элемента, которые мы до сих пор не рассматривали: тип difference_type и класс inserter. difference_type – это тип результата вычитания двух итераторов для нашего множества строк. Он передается в качестве одного из параметров шаблона istream_iterator.
copy() –один из обобщенных алгоритмов. (Мы рассмотрим их в главе 12 и в Приложении.) Первые два параметра – пара итераторов или указателей – задают диапазон. Третий параметр является либо итератором, либо указателем на начало контейнера, в который элементы копируются.
Проблема с этой функцией вызвана ограничением, вытекающим из ее реализации: количество копируемых элементов не может превосходить числа элементов в контейнере- адресате. Дело в том, что copy() не вставляет элементы, она только присваивает
С++ для начинающих |
300 |
каждому элементу новое значение. Однако ассоциативные контейнеры не позволяют явно задать размер. Чтобы скопировать элементы в наше множество, мы должны заставить copy() вставлять элементы. Именно для этого служит класс inserter (детально он рассматривается в разделе 12.4).
6.13.2. Поиск элемента
Две операции, позволяющие отыскать в наборе определенное значение, – это find() и count(). find() возвращает итератор, указывающий на найденный элемент, или значение, равное end(), если он отсутствует. count() возвращает 1 при наличии элемента и 0 в противном случае. Добавим проверку на
if ( exclusion_set.count( textword )) continue;
существование в функцию build_word_map():
// добавим отсутствующее слово
6.13.3. Навигация по множеству
Для проверки наших кодов реализуем небольшую функцию, выполняющую поиск по одному слову (поддержка языка запросов будет добавлена в главе 17). Если слово найдено, мы будем показывать каждую строку, в которой оно содержится. Слово может повторяться в строке, например:
tomorrow and tomorrow and tomorrow
однако такая строка будет представлена только один раз.
Одним из способов не учитывать повторное вхождение слова в строку является
//получим указатель на вектор позиций loc ploc = (*text_map)[ query_text ];
//переберем все позиции
//вставим все номера строк в множество set< short > occurrence_lines; loc::iterator liter = ploc->begin(),
liter_end = ploc->end();
while ( liter != liter_end ) { occurrence_lines.insert( occurrence_lines.end(),
(*liter).first ); ++liter;
использование множества, как показано в следующем фрагменте кода:
}
С++ для начинающих |
301 |
Контейнер set не допускает дублирования ключей. Поэтому можно гарантировать, что occurrence_lines не содержит повторений. Теперь нам достаточно перебрать данное
register int size = occurrence_lines.size(); cout << "\n" << query_text
<<" встречается " << size
<<" раз(а):")
<<"\n\n";
set< short >::iterator it=occurrence_lines.begin(); for ( ; it != occurrence_lines.end(); ++it ) {
int line = -it;
cout << "\t( строка "
<<line + 1 << " ) "
<<(*text_file)[line] << endl;
множество, чтобы показать все номера строк, где встретилось данное слово:
}
(Полная реализация query_text() представлена в следующем разделе.)
Класс set поддерживает операции size(), empty() и erase() точно таким же образом, как и класс map, описанный выше. Кроме того, обобщенные алгоритмы предоставляют набор специфических функций для множеств, например set_union() (объединение) и set_difference() (разность). (Они использованы при реализации языка запросов в главе 17.)
Упражнение 6.23
Добавьте в программу множество слов, в которых заключающее 's' не подчиняется общим правилам и не должно удаляться. Примерами таких слов могут быть Pythagoras, Brahms и Burne_Jones. Включите в функцию suffix_s() из раздела 6.10 проверку этого набора.
Упражнение 6.24
Определите вектор, содержащий названия книг, которые вы собираетесь прочесть в ближайшие шесть виртуальных месяцев, и множество, включающее названия уже прочитанных произведений. Напишите программу, которая выбирает для вас книгу из вектора при условии, что вы ее еще не прочитали. Выбранное название программа должна заносить в множество прочитанных. Однако вы могли отложить книгу; следовательно, нужно обеспечить возможность удалять ее название из множества прочитанных. По окончании шести виртуальных месяцев распечатайте список прочитанного и непрочитанного.
6.14. Окончательная программа
Ниже представлен полный текст программы, разработанной в этой главе, с двумя модификациями: мы инкапсулировали все структуры данных и функции в класс TextQuery (в последующих главах мы обсудим подобное использование классов), кроме того, текст был изменен, так как наш компилятор поддерживал стандарт С++ не полностью.