Современный способ фильтровать контейнер STL?


98

Возвращаясь к C ++ после многих лет работы с C #, мне было интересно, каким будет современный - читайте: C ++ 11 - способ фильтрации массива, то есть как мы можем достичь чего-то похожего на этот запрос Linq:

var filteredElements = elements.Where(elm => elm.filterProperty == true);

Чтобы отфильтровать вектор элементов ( stringsради этого вопроса)?

Я искренне надеюсь, что старые алгоритмы стиля STL (или даже подобные расширения boost::filter_iterator), требующие определения явных методов, к настоящему времени заменены?


Извлекаются ли все элементы, для которых filterPropertyустановлено значение true?
Джозеф Мэнсфилд

Извини да. Какой-то общий критерий фильтра ..
ATV

3
Есть также несколько библиотек, которые пытаются эмулировать методы LINQ .NET: Linq ++ и cpplinq . Я не работал с ними, но предполагаю, что они поддерживают контейнеры STL.
Дирк

1
Вам следует более четко представлять, чего вы хотите, поскольку набор людей, компетентных как в C ++, так и в C #, невелик. Опишите, что вы хотите сделать.
Якк - Адам Неврамонт

Ответы:


117

См. Пример с cplusplus.com для std::copy_if:

std::vector<int> foo = {25,15,5,-5,-15};
std::vector<int> bar;

// copy only positive numbers:
std::copy_if (foo.begin(), foo.end(), std::back_inserter(bar), [](int i){return i>=0;} );

std::copy_ifоценивает лямбда-выражение для каждого элемента fooздесь и, если оно возвращает, trueкопирует значение в bar.

std::back_inserterПозволяет фактически вставлять новые элементы в конце bar( с помощью push_back()) с итератора без необходимости изменять его размер до требуемого размера первого.


30
Это действительно самое близкое к LINQ, которое может предложить C ++? Это нетерпеливый (IOW не ленивый) и очень многословный.
usr

1
@usr Его синтаксический сахар IMO, простой цикл for также выполняет свою работу (и часто позволяет избежать копирования).
Себастьян Хоффманн

1
В примере OPs не используется синтаксический сахар LINQ. Преимущества - ленивая оценка и возможность комбинирования.
usr

1
@usr Которая все еще может быть легко достигнута с помощью простого цикла for, std::copy_ifэто не более чем цикл for
Себастьян Хоффманн

13
@Paranaix Все можно назвать синтаксическим сахаром над сборкой. Дело в том, чтобы не писать для циклов, когда алгоритм может быть четко составлен в удобочитаемом виде с использованием примитивных операций (например, фильтра). Многие языки предлагают такую ​​возможность - в C ++, к сожалению, это все еще неуклюже.
BartoszKP

47

Более эффективный подход, если вам на самом деле не нужна новая копия списка, заключается в том remove_if, что фактически удаляет элементы из исходного контейнера.


7
@ATV мне remove_ifособенно нравится, потому что это способ использования фильтра при наличии мутации, который быстрее, чем копирование целого нового списка. Если бы я делал фильтр на C ++, я бы использовал его снова copy_if, поэтому я думаю, что он добавляет.
djhaskin987

16
По крайней мере, для вектора remove_ifне меняет size(). Вам нужно приковать его с eraseдля этого .
rampion

5
@rampion Ага .. стереть / удалить. Еще одна красота, которая часто заставляет меня чувствовать, что я пробиваю дыры в ленте, когда сейчас работаю на C ++ (в отличие от современных языков) ;-)
ATV

1
Явное стирание - это особенность. Не обязательно стирать во всех случаях. Иногда итераторов достаточно для продолжения. В таких случаях неявное стирание приведет к ненужным накладным расходам. Кроме того, не каждый контейнер можно изменить. std :: array, например, вообще не имеет метода стирания.
Мартин Ферс,

31

В C ++ 20 используйте представление фильтра из библиотеки диапазонов: (требуется #include <ranges>)

// namespace views = std::ranges::views;
vec | views::filter([](int a){ return a % 2 == 0; })

лениво возвращает четные элементы в vec.

(См. [Range.adaptor.object] / 4 и [range.filter] )


Это уже поддерживается GCC 10 ( живая демонстрация ). Для Clang и более старых версий GCC также можно использовать исходную библиотеку range-v3 с #include <range/v3/view/filter.hpp>(или #include <range/v3/all.hpp>) и ranges::viewsпространством имен вместо std::ranges::views( live demo ).


Вы должны предоставить #include и использовать пространство имен, необходимое для компиляции вашего ответа. Кроме того, какой компилятор поддерживает это на сегодняшний день?
gsimard

2
@gsimard Теперь лучше?
LF

1
Если кто-то попытается сделать это в macOS: по состоянию на май 2020 года libc ++ не поддерживает это.
DAX

25

Я думаю, что упоминания заслуживает и 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, потому что он избегает указывать точный тип, а также добавляет зерно универсальности. В качестве альтернативы, с полиморфными лямбдами C ++ 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));

12

Мое предложение для эквивалента C ++ C #

var filteredElements = elements.Where(elm => elm.filterProperty == true);

Определите функцию-шаблон, в которую вы передадите лямбда-предикат для фильтрации. Функция шаблона возвращает отфильтрованный результат. например:

template<typename T>
vector<T> select_T(const 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); });

11

Улучшенный код pjm после предложений подчеркивания d :

template <typename Cont, typename Pred>
Cont filter(const Cont &container, Pred predicate) {
    Cont result;
    std::copy_if(container.begin(), container.end(), std::back_inserter(result), predicate);
    return result;
}

Применение:

std::vector<int> myVec = {1,4,7,8,9,0};

auto filteredVec = filter(myVec, [](int a) { return a > 5; });
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.