В некоторых ситуациях существующих алгоритмов STL недостаточно. Но ничто не запрещает нам реализовать собственный алгоритм. Прежде чем решать конкретную задачу, следует тщательно ее обдумать, чтобы понять: многие задачи можно решить путем обобщения. Если мы будем регулярно добавлять в наши библиотеки новый код по мере решения собственных задач, то можем помочь коллегам-программистам, у которых появятся аналогичные задачи. Идея заключается в том, чтобы знать, когда ваш код является достаточно обобщенным и когда не нужно обобщать его еще больше, в противном случае у нас получится новый язык общего назначения.
В этом примере мы реализуем алгоритм, который назовем split
Как это делается
В данном примере мы реализуем собственный алгоритм split
1. Сначала включим некоторые части библиотеки STL и объявим об использовании пространства имен std
#include
#include
#include
#include
#include
using namespace std;
2. Алгоритм, показанный в этом разделе, предназначен для разбиения диапазонов данных. Он принимает начальный и конечный итераторы, а также итератор вывода, что поначалу делает его похожим на алгоритмы std::copy
std::transform
. Другими его параметрами являются split_val
и bin_func
. Параметр split_val
— значение, которое мы ищем во входном диапазоне данных; оно представляет собой точку разбиения, по которой мы отделяем входной диапазон данных. Параметр bin_func
— функция, преобразующая пару итераторов, которые отмечают начало и конец этого поддиапазона. Мы проитерируем по входному диапазону данных с помощью std::find
, так что будем перескакивать между включениями значений split_val
. При разбиении отдельной строки на отдельные слова мы станем перескакивать от пробела к пробелу. При нахождении искомого значения останавливаемся, чтобы сформировать фрагмент и передать его в выходной диапазон данных:template
InIt split(InIt it, InIt end_it, OutIt out_it, T split_val,
F bin_func)
{
while (it != end_it) {
auto slice_end (find(it, end_it, split_val));
*out_it++ = bin_func(it, slice_end);
if (slice_end == end_it) { return end_it; }
it = next(slice_end);
}
return it;
}
3. Воспользуемся новым алгоритмом. Создадим строку, которую затем разобьем на части. Элементом, отмечающим конец последнего фрагмента и начало следующего, будет дефис '-'
int main()
{
const string s {"a-b-c-d-e-f-g"};
4. Когда алгоритм вызывает функцию bin_func
auto binfunc ([](auto it_a, auto it_b) {
return string(it_a, it_b);
});
5. Выходной диапазон данных представляет собой список строк. Мы вызовем алгоритм split
list
split(begin(s), end(s), back_inserter(l), '-', binfunc);
6. Чтобы увидеть полученное, выведем на экран новый список разбитых строк:
copy(begin(l), end(l), ostream_iterator
}
7. Компиляция и запуск программы дадут следующий результат. Он не содержит дефисов и показывает, что включает отдельные слова (которые в нашем примере являются отдельными символами):
$ ./split
a
b
c
d
e
f
g
Как это работает
Алгоритм split
std::transform
, поскольку принимает начальный и конечный итераторы входного диапазона данных и итератор вывода. Он выполняет какие-то действия с входным диапазоном и в конечном счете присваивает его итератору вывода. Помимо этого, он принимает элемент split_val
и бинарную функцию. Снова взглянем на полную реализацию, чтобы полностью понять его:template
InIt split(InIt it, InIt end_it, OutIt out_it, T split_val, F bin_func)
{
while (it != end_it) {
auto slice_end (find(it, end_it, split_val));
*out_it++ = bin_func(it, slice_end);
if (slice_end == end_it) { return end_it; }
it = next(slice_end);
}
return it;
}