Nowoczesny sposób na filtrowanie pojemnika STL?

103

Wracając do C ++ po latach C #, zastanawiałem się jaki byłby nowoczesny - czytaj: C ++ 11 - sposób filtrowania tablicy, czyli jak możemy osiągnąć coś podobnego do tego zapytania Linq:

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

Aby przefiltrować wektor elementów ( stringsze względu na to pytanie)?

Mam szczerą nadzieję, że stare algorytmy w stylu STL (lub nawet rozszerzenia, takie jak boost::filter_iterator) wymagające zdefiniowania jawnych metod, zostały już zastąpione?

ATV
źródło
Czy to pobiera wszystkie elementy, które zostały filterPropertyustawione na true?
Joseph Mansfield,
Przepraszam, tak. Jakieś ogólne kryterium filtrowania ...
ATV
3
Istnieją również biblioteki, które próbują emulować metody LINQ w .NET: Linq ++ i cpplinq . Nie pracowałem z nimi, ale przypuszczam, że obsługują kontenery STL.
Dirk
1
Powinieneś mieć większą jasność co do tego, czego chcesz, ponieważ zbiór osób kompetentnych zarówno w C ++, jak i C # jest niewielki. Opisz, co chcesz, żeby robiła.
Yakk - Adam Nevraumont

Odpowiedzi:

125

Zobacz przykład z cplusplus.com dla 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_ifoblicza wyrażenie lambda dla każdego elementu w tym foomiejscu i jeśli zwraca true, kopiuje wartość do bar.

std::back_inserterPozwala nam rzeczywiście wstawić nowe elementy na końcu bar(za pomocą push_back()) z iteracyjnej bez konieczności zmiany rozmiaru go do wymaganej wielkości pierwszy.

Sebastian Hoffmann
źródło
30
Czy to naprawdę najbliższe LINQ, które ma do zaoferowania C ++? To jest chętne (IOW nie leniwe) i bardzo rozwlekłe.
usr
1
@usr Jego cukier składniowy IMO, prosta pętla for również spełnia swoje zadanie (i często pozwala uniknąć kopiowania).
Sebastian Hoffmann
1
Przykład OP nie wykorzystuje żadnego cukru składniowego LINQ. Korzyści to leniwa ocena i kompozycja.
usr
1
@usr Które wciąż można łatwo osiągnąć za pomocą prostej pętli for, std::copy_ifto nic innego jak pętla for
Sebastian Hoffmann.
15
@Paranaix Można powiedzieć, że wszystko jest po prostu cukrem syntaktycznym w porównaniu z montażem. Chodzi o to, aby nie pisać dla pętli, gdy algorytm można wyraźnie skomponować w czytelny sposób za pomocą operacji pierwotnych (takich jak filtr). Wiele języków oferuje taką funkcję - niestety w C ++ jest nadal niezdarna.
BartoszKP
48

Bardziej wydajnym podejściem, jeśli faktycznie nie potrzebujesz nowej kopii listy, jest remove_ifusunięcie elementów z oryginalnego kontenera.

djhaskin987
źródło
7
@ATV remove_ifszczególnie mi się podoba, ponieważ jest to sposób na użycie filtra w obecności mutacji, co jest szybsze niż kopiowanie całej nowej listy. Gdybym robił filtrowanie w C ++, użyłbym tego od nowa copy_if, więc myślę, że dodaje.
djhaskin987
16
Przynajmniej dla wektora remove_ifnie zmienia size(). Musisz łańcucha ją eraseza to .
rampion
5
@rampion Yeah .. wymaż / usuń. Kolejne piękno, które często sprawia, że ​​czuję się jakbym dziurawiła taśmę podczas pracy w C ++ (w przeciwieństwie do współczesnych języków) w dzisiejszych czasach ;-)
ATV
1
Wyraźne wymazywanie jest funkcją. Nie musisz wymazywać we wszystkich przypadkach. Czasami wystarczą iteratory, aby kontynuować. W takich przypadkach niejawne wymazywanie spowodowałoby niepotrzebne obciążenie. Ponadto nie można zmieniać rozmiaru każdego kontenera. Na przykład std :: array nie ma żadnej metody kasowania.
Martin Fehrs
35

W C ++ 20 użyj widoku filtru z biblioteki zakresów: (wymaga #include <ranges>)

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

leniwie zwraca parzyste elementy vec.

(Zobacz [range.adaptor.object] / 4 i [range.filter] )


Jest to już obsługiwane przez GCC 10 ( demo na żywo ). W przypadku Clang i starszych wersji GCC można również użyć oryginalnej biblioteki range-v3 z #include <range/v3/view/filter.hpp>(lub #include <range/v3/all.hpp>) i ranges::viewsprzestrzenią nazw zamiast std::ranges::views( live demo ).

LF
źródło
Powinieneś podać #include i użyć przestrzeni nazw potrzebnej do kompilacji odpowiedzi. Jaki kompilator obsługuje to na dzień dzisiejszy?
gsimard
2
@gsimard Lepiej teraz?
LF
2
Jeśli ktoś spróbuje to zrobić w systemie macOS: od maja 2020 r. Libc ++ nie obsługuje tego.
dax
25

Myślę, że Boost.Range też zasługuje na wzmiankę. Wynikowy kod jest bardzo zbliżony do oryginału:

#include <boost/range/adaptors.hpp>

// ...

using boost::adaptors::filtered;
auto filteredElements = elements | filtered([](decltype(elements)::value_type const& elm)
    { return elm.filterProperty == true; });

Jedynym minusem jest jawne zadeklarowanie typu parametru lambdy. Użyłem decltype (elements) :: value_type, ponieważ pozwala to uniknąć konieczności przeliterowania dokładnego typu, a także dodaje ziarno ogólności. Alternatywnie, w przypadku polimorficznych lambd C ++ 14 typ można po prostu określić jako auto:

auto filteredElements = elements | filtered([](auto const& elm)
    { return elm.filterProperty == true; });

FilterElements byłby zakresem odpowiednim do przechodzenia, ale jest to w zasadzie widok oryginalnego kontenera. Jeśli potrzebujesz kolejnego kontenera wypełnionego kopiami elementów spełniających kryteria (tak, aby był niezależny od czasu życia oryginalnego kontenera), może to wyglądać następująco:

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));
user2478832
źródło
12

Moja sugestia dotycząca odpowiednika C ++ w C #

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

Zdefiniuj funkcję szablonu, do której przekazujesz predykat lambda w celu wykonania filtrowania. Funkcja szablonu zwraca przefiltrowany wynik. na przykład:

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

używać - podając trywialne przykłady:

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); });
pjm
źródło
11

Ulepszony kod pjm zgodny z sugestiami podkreślenia-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;
}

Stosowanie:

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

auto filteredVec = filter(myVec, [](int a) { return a > 5; });
Alex P.
źródło