7.10. Написание собственного алгоритма
7.10. Написание собственного алгоритма
Проблема
Для диапазона требуется выполнить алгоритм, и ни один из стандартных алгоритмов не удовлетворяет требованиям.
Решение
Напишите алгоритм в виде шаблона функции и с помощью имен параметров шаблона укажите свои требования к итератору. В примере 7.10 показан измененный стандартный алгоритм сору.
Пример 7.10. Написание собственного алгоритма
#include <iostream>
#include <istream>
#include <iterator>
#include <string>
#include <functional>
#include <vector>
#include <list>
#include "utils.h" // Для printContainer(): см. 7.10
using namespace std;
template<typename In, typename Out, typename UnPred>
Out copyIf(In first, In last, Out result, UnPred pred) {
for ( ; first != last; ++first)
if (pred(*first)) *results = *first;
return(result);
}
int main() {
cout << "Введите несколько строк: ";
istream_iterator<string> start(cin);
istream_iterator<string> end; // Здесь создается "маркер"
vector<string> v(start, end);
list<string> lst;
copyIf(v.begin(), v.end(), back_inserter<list<string> >(lst),
bind2nd(less<string>(), "cookie"));
printContainer(lst);
}
Запуск примера 7.10 будет выглядеть примерно так.
Введите несколько строк: apple banana danish eclaire
^Z
-----
apple banana
Вы видите, что он копирует в результирующий диапазон только те значения, которые меньше, чем «cookie».
Обсуждение
Стандартная библиотека содержит шаблон функции сору, который копирует элементы из одного диапазона в другой, но нет стандартной версии, которая принимает предикат и выполняет условное копирование элементов (т.е. алгоритм copy_if), так что пример 7.10 делает именно это. Его поведение довольно просто: при наличии диапазона-источника и начала диапазона-приемника производится копирование в целевой диапазон элементов, для которых унарный функтор-предикат возвращает true.
Этот алгоритм прост, но в его реализации есть еще кое-что, что привлекает внимание. Посмотрев на объявление, вы увидите, что в нем присутствует три параметра шаблона.
template<typename In, typename Out, typename UnPred>
Out copyIf(In first, In last, Out result UnPred pred) {
Первый параметр шаблона In — это тип входного итератора. Так как это входной диапазон, все, что должен иметь возможность сделать с ним copyIf, — это извлечь разыменованное значение этого итератора и перевести итератор на следующий элемент. Это дает описание категории итератора ввода (категории итераторов описаны в рецепте 7.1), так что с помощью указания имени параметра шаблона In мы объявляем именно этот тип итератора. Стандартного соглашения здесь нет (In и Out — это мои соглашения, которые я описал в первом рецепте этой главы), но вы легко можете придумать свои собственные соглашения об именах: InIter, Input_T или даже InputIterator. Второй параметр шаблона Out — это тип итератора, который указывает на диапазон, в который будут копироваться элементы, copyIf должен иметь возможность записать разыменованное значение в выходной итератор и увеличить его значение, что дает нам описание оператора вывода. Объявив требования к итераторам с помощью имен параметров шаблона, вы делаете соглашения о вызовах алгоритма понятными без документации. Но зачем использовать две разные категории итераторов?
Имеется, по крайней мере, две причины использования в copyIf двух различных категорий итераторов. Во-первых, операции с каждым диапазоном несколько отличаются друг от друга, и так как мне никогда не потребуется возвращаться назад по входному диапазону или присваивать ему значения, все, что мне требуется, — это итератор ввода. Аналогично мне никогда не потребуется читать из выходного диапазона, так что все, что здесь требуется, — это итератор вывода. Имеются требования к каждому из итераторов, которые не применимы к другому итератору, так что нет никакого смысла использовать для обоих диапазонов, например, два двунаправленных итератора. Во-вторых, использование различных типов итераторов позволяет вызывающему коду читать из одного типа диапазона и записывать в другой. В примере 7.10 я читаю из vector и записываю в list.
vector<string> v(start, end);
list<string> lst;
copyIf(v.begin(), v.end(), back_inserter<list<string> >(lst),
bind2nd(less<string>(), "cookie"));
Если попробовать сделать то же самое, использовав в алгоритме один и тот же тип итераторов, то он просто не скомпилируется.
В примере 7.10 я в качестве начала выходного диапазона передаю back_inserter, а не, скажем, итератор, возвращаемый lst.begin. Это делается потому, что lst не содержит элементов, и в этом алгоритме (как и в стандартном алгоритме копирования) целевой диапазон должен быть достаточно большим, чтобы вместить все элементы, которые будут в него скопированы. В противном случае увеличение итератора вывода в copyIf приведет к неопределенному поведению. back_inserter возвращает итератор вывода, который при его увеличении вызывает для контейнера метод push_back. В результате этого при каждом увеличении выходного итератора размер lst увеличивается на один. Более подробно шаблон класса back_inserter я описываю в рецепте 7.5.
При написании собственного алгоритма для работы с диапазонами (т.е. со стандартными контейнерами) вы должны работать с аргументами-итераторами, а не с аргументами-контейнерами. У вас может возникнуть желание объявить copyIf так, чтобы он принимал два контейнера, а не итератор исходного и результирующего диапазонов, но это менее обобщенное решение, чем диапазоны. Во-первых, если передавать аргументы-контейнеры, то станет невозможно работать с подмножеством элементов контейнера. Далее, в теле copyIf появится зависимость от методов контейнеров begin и end, которые дадут требуемый диапазон, и возвращаемый тип будет зависеть от типа контейнера, используемого в качестве выходного. Это означает, что использование в copyIf нестандартных диапазонов, таких как встроенные массивы или собственные контейнеры, работать не будет. Именно по этим и некоторым другим причинам все стандартные алгоритмы оперируют с диапазонами.
Наконец, если вы пишете свой алгоритм, дважды убедитесь, что стандартные алгоритмы вас не устраивают. На первый взгляд они могут казаться очень простыми алгоритмами, но их кажущаяся простота проистекает из их обобщенности, и в девяти случаях из десяти их можно расширить так, что они подойдут для новых задач. Иногда следует стремиться к повторному использованию стандартных алгоритмов, так как это дает гарантию переносимости и эффективности.
Смотри также
Рецепт 7.5.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Создание собственного сертификата
Создание собственного сертификата Наиболее быстрым способом создания собственного цифрового сертификата является использование программы SelfCert.exe, входящей в состав Microsoft Office 2000/ХР. Запустив эту утилиту, мы получим диалоговое окно, позволяющее задать имя создаваемого
Глава 11 Создание собственного веб-сайта
Глава 11 Создание собственного веб-сайта • Основы HTML• Создание сайтов в визуальном режиме WYSIWYG• Размещение сайта в ИнтернетеПознакомившись с Интернетом и узнав о его возможностях, пользователи, как правило, рано или поздно задумываются: «Как же все-таки делаются те
Глава 6 Создание собственного сайта
Глава 6 Создание собственного сайта – Создание.– Размещение.– Раскрутка.Чтобы успешно работать в Сети, совсем не обязательно наличие собственного сайта. Вполне можно быть пассивным пользователем Интернета: искать информацию, посещать разнообразные чужие ресурсы,
3.2.4. Создание собственного сервера пакетов
3.2.4. Создание собственного сервера пакетов Данный параграф больше рассчитан на администраторов сетей, которые понимают, что они делают. Обычным пользователям его можно прочитать разве что для "общего развития", хотя приведенный способ можно удачно использовать не только
7.4.2. Создание собственного LiveCD
7.4.2. Создание собственного LiveCD Ничего сложного в создании собственного LiveCD нет. Ведь во времена 6-й версии Fedora Core Дэвид Цойтен (David Zeuthen) разработал инструментарий livecd, позволяющий создавать LiveCD даже самим неподготовленным пользователям. Создание LiveCD заключается в вводе
4. Формализованное понятие алгоритма
4. Формализованное понятие алгоритма Алгоритм может существовать только тогда, когда в то же самое время существует некоторый математический объект. Формализованное понятие алгоритма связано с понятием рекурсивных функций, нормальных алгоритмов Маркова, машин
1. Понятие вспомогательного алгоритма
1. Понятие вспомогательного алгоритма Алгоритм решения задачи проектируется путем декомпозиции всей задачи в отдельные подзадачи. Обычно подзадачи реализуются в виде подпрограмм.Подпрограмма – это некоторый вспомогательный алгоритм, многократно использующийся в
Глава 8 Создание собственного веб-сайта
Глава 8 Создание собственного веб-сайта Основы HTMLСоздание сайтов в визуальном режиме WYSIWYGРазмещение сайта в ИнтернетеПознакомившись с Интернетом и узнав о его возможностях, пользователи, как правило, рано или поздно задумываются: «Как же все-таки делаются те красивые
5.4. Создание собственного видеофильма
5.4. Создание собственного видеофильма У каждого пользователя, особенно если он пользуется видеокамерой, со временем на компьютере скапливается множество видеофайлов. Все видеоролики можно объединить в один или несколько тематических фильмов, наложить спецэффекты,
2. Представление чисел в ЭВМ. Формализованное понятие алгоритма
2. Представление чисел в ЭВМ. Формализованное понятие алгоритма 32-разрядные процессоры могут работать с оперативной памятью емкостью до 232-1, а адреса могут записываться в диапазоне 00000000 – FFFFFFFF. Однако в реальном режиме процессор работает с памятью до 220-1, а адреса
6. Понятие вспомогательного алгоритма
6. Понятие вспомогательного алгоритма Алгоритм решения задачи проектируется путем декомпозиции всей задачи в отдельные подзадачи. Обычно подзадачи реализуются в виде подпрограмм.Подпрограмма – это некоторый вспомогательный алгоритм, многократно использующийся в