Современный способ фильтрации контейнера STL?
Возвращаясь на С++ после нескольких лет С#, мне было интересно, что такое современное - читайте: С++ 11 - способ фильтрации массива будет, то есть как мы можем достичь чего-то подобного этому запросу Linq:
var filteredElements = elements.Where(elm => elm.filterProperty == true);
Чтобы фильтровать вектор элементов (strings
для этого вопроса)?
Я искренне надеюсь, что старые алгоритмы стиля STL (или даже расширения, такие как boost::filter_iterator
), требующие определения явных методов, теперь заменяются?
Ответы
Ответ 1
См. пример cplusplus.com для std::copy_if
:
std::vector<int> foo = {25,15,5,-5,-15};
std::vector<int> bar;
// copy only positive numbers:
auto it = std::copy_if (foo.begin(), foo.end(), std::back_inserter(bar), [](int i){return i>=0;} );
std::copy_if
вычисляет lambda выражение для каждого элемента в foo
здесь, и если он возвращает true
, он копирует значение в bar
.
std::back_inserter
позволяет нам фактически вставлять новые элементы в конце bar
(используя push_back()
) с помощью итератора без необходимости сначала изменять его размер до требуемого размера.
Ответ 2
Я думаю, Boost.Range заслуживает упоминания. Полученный код довольно близок к оригиналу:
#include <boost/range/adaptors.hpp>
// ...
using boost::adaptors::filtered;
auto filteredElements = elements | filtered([](decltype(elements)::value_type const& elm)
{ return elm.filterProperty == true; });
Единственный недостаток - явно указать тип параметра лямбда. Я использовал decltype (elements):: value_type, потому что он избегает необходимости указывать точный тип, а также добавляет зерно общности. Альтернативно, с полиморфными lambdas С++ 14, тип можно просто указать как auto:
auto filteredElements = elements | filtered([](auto const& elm)
{ return elm.filterProperty == true; });
filterElements будет диапазоном, подходящим для обхода, но в основном это представление исходного контейнера. Если вам нужен другой контейнер, заполненный копиями элементов, удовлетворяющих критериям (так что он не зависит от времени жизни исходного контейнера), он может выглядеть так:
using std::back_inserter; using boost::copy; using boost::adaptors::filtered;
decltype(elements) filteredElements;
copy(elements | filtered([](decltype(elements)::value_type const& elm)
{ return elm.filterProperty == true; }), back_inserter(filteredElements));
Ответ 3
Более эффективный подход, если вам действительно не нужен новый экземпляр списка, remove_if
, который фактически удаляет элементов из исходного контейнера.
Ответ 4
Мое предложение для С++-эквивалента С#
var filteredElements = elements.Where(elm => elm.filterProperty == true);
Определите функцию шаблона, к которой вы передаете предикат лямбды, чтобы выполнить фильтрацию. Функция шаблона возвращает результат фильтрации. например:
template<typename T>
vector<T> select_T(vector<T> inVec, function<bool(const T&)> predicate)
{
vector<T> result;
copy_if(inVec.begin(), inVec.end(), back_inserter(result), predicate);
return result;
}
для использования - давая тривиальные примеры:
std::vector<int> mVec = {1,4,7,8,9,0};
// filter out values > 5
auto gtFive = select_T<int>(mVec, [](auto a) {return (a > 5); });
// or > target
int target = 5;
auto gt = select_T<int>(mVec, [target](auto a) {return (a > target); });