STL kabını filtrelemenin modern yolu?


99

Yıllarca C #'dan sonra C ++ 'ya geri dönersek, modern olanın - okuyun: C ++ 11 - bir diziyi filtreleme yolunun ne olacağını merak ediyordum, yani bu Linq sorgusuna benzer bir şeyi nasıl başarabiliriz:

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

Bir element vektörünü filtrelemek stringsiçin (bu soru uğruna)?

Açık yöntemlerin tanımlanmasını boost::filter_iteratorgerektiren eski STL tarzı algoritmaların (veya hatta benzeri uzantıların ) artık yerini alacağını içtenlikle umuyorum.


Bu, filterPropertyayarlanmış tüm öğeleri alır mı true?
Joseph Mansfield

Üzgünüm, evet. Bazı genel filtre kriterleri ..
ATV

3
NET'in LINQ yöntemlerini taklit etmeye çalışan bazı kitaplıklar da vardır: Linq ++ ve cpplinq . Onlarla çalışmadım ama tahminimce STL konteynerlerini destekliyorlar.
Dirk

1
Hem C ++ hem de C # konusunda yetkin kişiler küçük olduğu için ne istediğiniz konusunda daha net olmalısınız. Ne yapmasını istediğinizi açıklayın.
Yakk - Adam Nevraumont

Yanıtlar:


119

Cplusplus.com'daki örneğe bakın 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_iffooburadaki her eleman için lambda ifadesini değerlendirir ve geri dönerse truedeğeri 'e kopyalar bar.

Bu std::back_inserter, ilk önce gerekli boyuta yeniden boyutlandırmak zorunda kalmadan bir yineleyici ile bar(kullanma push_back()) sonunda yeni öğeler eklememizi sağlar.


30
Bu gerçekten C ++ 'nın sunduğu LINQ'ya en yakın olanı mı? Bu hevesli (IOW tembel değil) ve çok ayrıntılı.
usr

1
@usr IMO sözdizimsel şekeri, basit bir for döngüsü işi de yapar (ve genellikle kopyalamadan kaçınılmasına izin verir).
Sebastian Hoffmann

1
OPs örneği, LINQ sözdizimsel şekeri kullanmaz. Faydaları tembel değerlendirme ve birleştirilebilirliktir.
usr

1
@usr Basit bir for-loop ile kolayca elde edilebilen, bir for-loop'tan std::copy_iffazlası değildir
Sebastian Hoffmann

15
@Paranaix Her şeyin montaj yerine sözdizimsel şeker olduğu söylenebilir. Önemli olan, bir algoritmanın ilkel işlemler (filtre gibi) kullanılarak okunabilir bir şekilde açıkça oluşturulabildiğinde döngüler için yazmak değil. Pek çok dil böyle bir özellik sunar - C ++ 'da maalesef hala karışık.
BartoszKP

47

Listenin yeni bir kopyasına gerçekten ihtiyacınız yoksa, daha verimli bir yaklaşım, remove_iföğeleri orijinal kaptan kaldıran yöntemdir.


7
@ATV remove_ifÖzellikle hoşuma gidiyor çünkü mutasyon varlığında filtre kullanmanın yolu, bu tamamen yeni bir listeyi kopyalamaktan daha hızlı. C ++ 'da filtre yapıyor olsaydım, bunu kullanırdım copy_if, bu yüzden ekler düşünüyorum.
djhaskin987

16
Vektör için, en azından, remove_ifdeğişmez size(). Sen ile zincire bunu gerekir erasebunun için .
rampion

5
@rampion Evet .. sil / kaldır. Bugünlerde (modern dillerin aksine) C ++ ile çalışırken bir kasete delik açıyormuşum gibi hissetmeme neden olan bir başka güzellik ;-)
ATV

1
Açıkça silme bir özelliktir. Her durumda silmenize gerek yoktur. Bazen yineleyiciler devam etmek için yeterlidir. Bu gibi durumlarda örtük bir silme gereksiz ek yüklere neden olur. Ayrıca, her kapsayıcı yeniden boyutlandırılamaz. örneğin std :: array hiç silme metoduna sahip değildir.
Martin Fehrs

32

C ++ 20'de, aralık kitaplığından filtre görünümünü kullanın: (gerektirir #include <ranges>)

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

eşit öğeleri tembel olarak geri döndürür vec.

(Bkz. [Range.adaptor.object] / 4 ve [range.filter] )


Bu zaten GCC 10 ( canlı demo ) tarafından desteklenmektedir . GCC'nin Clang ve daha eski sürümleri için, orijinal range-v3 kitaplığı da #include <range/v3/view/filter.hpp>(veya #include <range/v3/all.hpp>) ve ( canlı demo ) ranges::viewsyerine ad alanı ile kullanılabilir .std::ranges::views


Cevabınızın derlenmesi için gereken #include bilgisini sağlamalı ve ad alanını kullanmalısınız. Ayrıca, bugün itibariyle hangi derleyici bunu destekliyor?
gsimard

2
@gsimard Şimdi daha mı iyi?
LF

1
Herhangi biri bunu macOS'ta yapmaya çalışırsa: Mayıs 2020 itibarıyla libc ++ bunu desteklemiyor.
dax

25

Bence Boost.Range de bir sözü hak ediyor. Ortaya çıkan kod, orijinaline oldukça yakındır:

#include <boost/range/adaptors.hpp>

// ...

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

Tek dezavantajı, lambda'nın parametre türünü açıkça bildirmek zorunda olmasıdır. Ben decltype (elements) :: value_type kullandım, çünkü tam türü hecelemek zorunda kalmaz ve aynı zamanda bir genellik zarı ekler. Alternatif olarak, C ++ 14'ün polimorfik lambdalarıyla, tür basitçe auto olarak belirtilebilir:

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

filteredElements, geçiş için uygun bir aralık olabilir, ancak temelde orijinal kabın bir görünümüdür. İhtiyacınız olan şey, kriterleri karşılayan öğelerin kopyalarıyla dolu başka bir kapsa (böylece orijinal kabın ömründen bağımsızdır), şöyle görünebilir:

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 ++ eşdeğeri C # için önerim

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

Filtrelemeyi yapmak için bir lambda yüklemini ilettiğiniz bir şablon işlevi tanımlayın. Şablon işlevi filtrelenmiş sonucu döndürür. Örneğin:

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

kullanmak - önemsiz örnekler vermek:

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

Alt çizgi-d önerilerinin ardından geliştirilmiş pjm kodu :

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

Kullanım:

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

auto filteredVec = filter(myVec, [](int a) { return a > 5; });
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.