C ++ собирать алгоритм?

Время от времени мне нужно перебирать подмножество элементов контейнера или просто извлекать их и пренебрегать остальными. Я заканчиваю тем, что использую boost::range::adaptors::filtered создать эту ленивую коллекцию.

for(auto&& i : container | filtered(predicate)) {
// .. do stuff
}

Есть ли причина отсутствия алгоритма сбора (как в сборе Руби) в STL (у нас есть только copy_if, который не совпадает)? Или какая-либо причина против использования этого?

Возможная реализация может быть:

template<class Container, class Predicate>
Container collect(Container&& c, Predicate&& p) {
Container n;
for(auto&& i : c) {
if(p(i)) {
n.push_back(i);
}
}
return n;
}

но lazy_collect также может быть полезно, чтобы избежать копирования.

Все ответы ниже великолепны. Я хотел бы отметить их всех. Я не знал о std::back_inserter, Собирать вещи теперь так же просто, как:

boost::copy( orig | filtered(predicate), std::back_inserter(collection));

3

Решение

Стандартные алгоритмы не работают напрямую с контейнерами (создавая или уничтожая их или изменяя их размеры). Они работают с диапазонами итераторов, и единственное изменение, которое вносит алгоритм, — это назначение через итераторы.

Итак, предложенная вами операция полностью выходит за рамки стандартных алгоритмов. Возможно, в стандарте «должен» быть большой дополнительный набор общих контейнерных операций, включая эту, но «философия STL» заключается в работе с итераторами.

Не ленивая операция, которую вы предлагаете, может быть выполнена с помощью std::back_inserter а также std::copy_if (опционально с помощью итераторов перемещения) или move_if (если вы катите это самостоятельно). std::copy_if отсутствовал в C ++ 03, но это был случайный недосмотр.

Ленивая версия не может быть сделана простым соединением стандартных компонентов — нет чистого способа объединить «выходные данные» одного алгоритма прямо во «входные данные» другого алгоритма без промежуточного хранения. Вот почему Boost предоставляет такое интересное разнообразие итераторов, как и диапазоны.

Я не знаю, почему они не были включены в C ++ 11, но одна вещь, которую стоит отметить в C ++ 11, это то, что было довольно поздно. Предложения были отклонены из-за нехватки времени, поэтому причина может быть такой простой, как «это никогда не считалось достаточно важным, чтобы сделать предложение, учитывая известную существующую рабочую нагрузку».

Что касается вашей конкретной реализации, не все контейнеры имеют push_backи поэтому вы, вероятно, не должны использовать Container в качестве имени параметра шаблона. PushBackSequence будет более описательным из требований.

6

Другие решения

Как насчет этого:

#include <algorithm>
#include <iterator>

std::copy_if(container.begin(), container.end(), std::back_inserter(result), []{...})

куда container это контейнер, из которого вы хотите забрать, и result это контейнер, в котором будет храниться коллекция

5

Из комментариев:

При использовании copy_if вам нужно заранее знать, сколько места вам понадобится

Это не правда. Ты можешь использовать std::copy_if с задняя вставка:

#include <algorithm> // For std::copy_if
#include <iterator> // For std::back_inserter
#include <vector>
#include <iostream>

int main()
{
std::vector<int> v(10);
std::iota(begin(v), end(v), 0); // Fills the vector with 0..9

std::vector<int> out;
std::copy_if(begin(v), end(v), back_inserter(out), [] (int x)
//                             ^^^^^^^^^^^^^^^^^^
{
return x > 4;
});

for (auto x : out) { std::cout << x << " "; }
}

Вот живой пример.

И если вам нужна функция, которая работает непосредственно с контейнером, а не с диапазоном, определенным парой итераторов, вы можете написать следующий помощник:

template<typename C1, typename C2, typename P>
void cont_copy_if(C1&& src, C2&& dst, P&& p)
{
std::copy_if(
begin(std::forward<C1>(src)),
end(std::forward<C1>(src)),
back_inserter(std::forward<C2>(dst)),
std::forward<P>(p)
);
}

Какой бы вы просто использовали этот способ:

int main()
{
std::vector<int> v(10);
std::iota(begin(v), end(v), 0);

std::list<int> out;
//  ^^^^^^^^^
//  You could use a different destination container

cont_copy_if(v, out, [] (int x) { return x > 4; });
//  ^^^^^^^^^^^^

for (auto x : out) { std::cout << x << " "; }
}

И конечно живой пример.

5