Сучасний спосіб фільтрувати контейнер STL?


99

Повертаючись до 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 #, невеликий. Опишіть, що ви хочете для цього.
Якк - Адам Неврамонт

Відповіді:


119

Див. Приклад із 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, що може запропонувати С ++? Це нетерпляче (IOW не лінь), і дуже багатослівне.
usr

1
@usr Його синтаксичний цукор IMO, простий цикл for, добре виконує роботу (і часто дозволяє уникати копіювання).
Себастьян Гофман

1
У прикладі OP не використовується синтаксичний цукор LINQ. Переваги - ледача оцінка та складність.
usr

1
@usr, чого можна легко досягти за допомогою простого for-loop, std::copy_ifце не більше, ніж for-loop
Себастьян Гофманн

15
@Paranaix Все, що можна сказати, це просто синтаксичний цукор над складанням. Справа в тому, щоб не писати для циклів, коли алгоритм можна чітко скласти читабельним способом за допомогою примітивних операцій (наприклад, фільтра). Багато мов пропонують таку функцію - у C ++, на жаль, вона все ще є важкою.
BartoszKP

47

Більш ефективний підхід, якщо вам насправді не потрібна нова копія списку, є remove_if, який фактично видаляє елементи з оригінального контейнера.


7
@ATV Мені подобається, remove_ifзокрема, тому що це спосіб використовувати фільтр за наявності мутації, що швидше, ніж копіювання цілого нового списку. Якби я робив фільтр на C ++, я б використав це copy_if, тому, думаю, це додає.
djhaskin987

16
Для вектора, принаймні, remove_ifне змінює size(). Вам потрібно прикувати його з eraseдля цього .
чемпіон

5
@rampion Так .. стерти / видалити. Ще одна красуня, яка часто викликає у мене відчуття, ніби я сьогодні пробиваю діри у стрічці, працюючи на C ++ (на відміну від сучасних мов) ;-)
ATV

1
Явне стирання - це особливість. Стирати не обов’язково у всіх випадках. Іноді для продовження досить ітераторів. У таких випадках неявне стирання призведе до непотрібних накладних витрат. Крім того, не кожна ємність може бути змінена. Наприклад, std :: array взагалі не має методу стирання.
Мартін Ферс,

32

У 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; });

filteredElements був би діапазоном, придатним для обходу, але в основному це вигляд оригінального контейнера. Якщо вам потрібен інший контейнер, заповнений копіями елементів, що відповідають критеріям (щоб він не залежав від терміну служби оригінального контейнера), це може виглядати так:

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- код згідно з підкресленими пропозиціями:

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.