Ассоциативный массив неявно решает еще одну задачу. Если мы выполняем вызов ops.at("foo")
, то в данном случае "foo
" является корректным значением ключа, но мы не сохранили операцию с таким именем. В подобных случаях массив сгенерирует исключение, которое мы отлавливаем в нашем примере. При его перехвате мы генерируем другое исключение, чтобы представить более подробное сообщение об ошибке. Пользователь будет лучше понимать, что означает полученное исключение, сообщающее о некорректном аргументе (invalid argument
), в отличие от исключения, гласящего о выходе за пределы контейнера. Обратите внимание: пользователь функции evaluate_rpn
может быть незнаком с ее реализацией и поэтому не знает о том, что мы применяем ассоциативный массив.
Дополнительная информация
Поскольку функция evaluate_rpn
принимает итераторы, ей можно легко передавать разные входные данные, не только стандартный поток ввода. Это позволяет довольно просто протестировать ее, а также адаптировать к различным источникам данных, получаемых от пользователя.
Передача в эту функцию итераторов строкового потока или вектора строк, например, выглядит следующим образом. При этом код функции evaluate_rpn
остается без изменений:
int main()
{
stringstream s {"3 2 1 + * 2 /"};
cout << evaluate_rpn(istream_iterator
vector
cout << evaluate_rpn(begin(v), end(v)) << '\n';
}
Подсчитываем частоту встречаемости слов с применением контейнера std::map
Контейнер std::map
очень полезен в тех случаях, когда нужно разбить данные на категории и собрать соответствующую статистику. Прикрепляя изменяемые объекты к каждому ключу, который представляет собой категорию объектов, вы легко можете реализовать, к примеру, гистограмму, показывающую частоту встречаемости слов. Этим мы и займемся.
Как это делается
В этом примере мы считаем все данные, которые пользователь передает через стандартный поток ввода и которые, скажем, могут оказаться текстовым файлом. Мы разобьем полученный текст на слова, чтобы определить частоту встречаемости каждого слова.
1. Как обычно, включим все заголовочные файлы для тех структур данных, которые планируем использовать:
#include
#include
#include
#include
#include
2. Чтобы сэкономить немного времени на наборе, объявляем об использовании пространства имен std
:
using namespace std;
3. Задействуем одну вспомогательную функцию, с помощью которой будем обрезать прикрепившиеся знаки препинания (например, запятые, точки и двоеточия):
string filter_punctuation(const string &s)
{
const char *forbidden {".,:; "};
const auto idx_start (s.find_first_not_of(forbidden));
const auto idx_end (s.find_last_not_of(forbidden));
return s.substr(idx_start, idx_end - idx_start + 1);
}
4. Теперь начнем писать саму программу. Создадим ассоциативный массив, в котором будут связаны каждое встреченное нами слово и счетчик, показывающий, насколько часто это слово встречается. Дополнительно введем переменную, которая будет содержать величину самого длинного встреченного нами слова, чтобы в конце работы программы перед выводом на экран мы могли красиво выровнять полученную таблицу:
int main()
{
map
int max_word_len {0};
5. Когда мы выполняем преобразование из std::cin
в переменную типа std::string
, поток ввода обрезает лишние пробельные символы. Таким образом мы получаем входные данные слово за словом:
string s;
while (cin >> s) {
6. Текущее слово может содержать запятые, точки или двоеточие, поскольку может находиться в середине или в конце предложения. Избавимся от этих знаков с помощью вспомогательной функции, которую определили ранее:
auto filtered (filter_punctuation(s));
7. В том случае, если текущее слово оказалось самым длинным из всех встреченных нами, обновляем переменную max_word_len
:
max_word_len = max
8. Теперь увеличим значение счетчика в нашем ассоциативном массиве words
. Если слово встречается в первый раз, то оно неявно добавляется в массив перед выполнением операции инкремента:
++words[filtered];
}