Выбрать главу

Различаем в пользовательском вводе операнды и операторы 

В основном цикле функции evaluate_rpn мы получаем текущий токен строки из итератора и затем смотрим, является ли он операндом. Если строка может быть преобразована в переменную типа double, то данное число тоже операнд. Все остальные токены, которые нельзя легко преобразовать в число (например, "+"), мы считаем операторами.

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

stringstream ss {*it};

if (double val; ss >> val) {

  // Это число!

} else {

  // Это что-то другое. Это операция!

}

Оператор потока >> говорит нам, является ли рассматриваемый объект числом. Сначала мы оборачиваем строку в std::stringstream. Затем используем способность объекта класса stringstream преобразовать объект типа std::string в переменную типа double, что включает в себя разбор. Если он не работает, то мы узнаем об этом, поскольку не получится преобразовать в число некий объект, который числом не является.

Выбираем и применяем нужную математическую операцию 

Разобравшись, что текущий токен, полученный от пользователя, не является числом, мы предполагаем, что это операция наподобие + или *. Затем обращаемся к ассоциативному массиву ops с целью найти требуемую операцию и получить функцию, принимающую два операнда и возвращающую сумму, произведение или другой подходящий результат.

Сам тип такого массива выглядит относительно сложно:

map<string, double (*)(double, double)> ops { ... };

Он соотносит строки и значения типа double (*)(double, double). Что означает вторая часть данного выражения? Это описание типа читается как «указатель на функцию, которая принимает два числа типа double и возвращает одно». Представьте, будто часть (*) представляет собой имя функции, как, например, double sum(double, double), что гораздо проще прочитать. Идея заключается в следующем: наше лямбда-выражение [](double, double) { return /* какое-то число типа double */ } можно преобразовать в указатель на функцию, фактически соответствующий описанию этого указателя. Лямбда-выражения, которые не захватывают переменных из внешнего контекста, могут быть преобразованы в указатели на функции.

Таким образом, это удобный способ запросить у ассоциативного массива корректную операцию:

const auto & op (ops.at(*it));

const double result {op(l, r)};

Ассоциативный массив неявно решает еще одну задачу. Если мы выполняем вызов 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<string>{s}, {}) << '\n';

  vector<string> v {"3", "2", "1", "+", "*", "2", "/"};

  cout << evaluate_rpn(begin(v), end(v)) << '\n';

}

  Используйте итераторы везде, где это имеет смысл. Так вы сможете многократно применять свой код.

Подсчитываем частоту встречаемости слов с применением контейнера std::map

Контейнер std::map очень полезен в тех случаях, когда нужно разбить данные на категории и собрать соответствующую статистику. Прикрепляя изменяемые объекты к каждому ключу, который представляет собой категорию объектов, вы легко можете реализовать, к примеру, гистограмму, показывающую частоту встречаемости слов. Этим мы и займемся.