Cara modern untuk menyaring wadah STL?

98

Kembali ke C ++ setelah bertahun-tahun C # Saya bertanya-tanya apa cara modern - baca: C ++ 11 - untuk memfilter array, yaitu bagaimana kita bisa mencapai sesuatu yang mirip dengan kueri Linq ini:

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

Untuk memfilter vektor elemen ( stringsdemi pertanyaan ini)?

Saya sangat berharap algoritma gaya STL lama (atau bahkan ekstensi seperti boost::filter_iterator) yang membutuhkan metode eksplisit untuk didefinisikan digantikan sekarang?

ATV
sumber
Apakah ini mengambil semua elemen yang telah filterPropertydisetel ke true?
Joseph Mansfield
Maaf ya Beberapa kriteria filter umum ..
ATV
3
Ada juga beberapa pustaka yang mencoba meniru metode LINQ .NET: Linq ++ dan cpplinq . Saya belum pernah bekerja dengan mereka tetapi perkiraan saya adalah mereka mendukung kontainer STL.
Dirk
1
Anda harus lebih jelas tentang apa yang Anda inginkan, karena kumpulan orang yang kompeten di C ++ dan C # kecil. Jelaskan apa yang Anda ingin lakukan.
Yakk - Adam Nevraumont

Jawaban:

118

Lihat contoh dari cplusplus.com untuk 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_ifmengevaluasi ekspresi lambda untuk setiap elemen di foosini dan jika mengembalikannya truemenyalin nilai ke bar.

Ini std::back_insertermemungkinkan kita untuk benar-benar memasukkan elemen baru di akhir bar(menggunakan push_back()) dengan iterator tanpa harus mengubah ukurannya terlebih dahulu ke ukuran yang diperlukan.

Sebastian Hoffmann
sumber
30
Apakah ini benar-benar yang paling dekat dengan LINQ yang ditawarkan C ++? Ini sangat bersemangat (IOW tidak malas), dan sangat bertele-tele.
usr
1
@usr Gula sintaksis IMO-nya, perulangan for sederhana juga berfungsi (dan sering kali memungkinkan seseorang menghindari penyalinan).
Sebastian Hoffmann
1
Contoh OP tidak menggunakan gula sintaksis LINQ. Manfaatnya adalah evaluasi malas dan komposabilitas.
usr
1
@usr Yang masih dapat dicapai dengan loop-for sederhana dengan mudah, std::copy_iftidak lebih dari loop-for
Sebastian Hoffmann
14
@Paranaix Semuanya bisa dikatakan hanya gula sintaksis di atas perakitan. Intinya adalah, jangan menulis untuk loop, ketika algoritme dapat dengan jelas disusun dengan cara yang dapat dibaca menggunakan operasi primitif (seperti filter). Banyak bahasa menawarkan fitur seperti itu - sayangnya dalam C ++ masih kludgy.
BartoszKP
47

Pendekatan yang lebih efisien, jika Anda tidak benar-benar membutuhkan salinan baru dari daftar, adalah remove_if, yang sebenarnya menghapus elemen dari penampung asli.

djhaskin987
sumber
7
@ATV Saya suka remove_ifkhususnya karena ini adalah cara menggunakan filter saat ada mutasi, yang lebih cepat daripada menyalin daftar yang sama sekali baru. Jika saya melakukan filter di C ++, saya akan menggunakan ini lagi copy_if, jadi saya pikir itu menambahkan.
djhaskin987
16
Untuk vektor, setidaknya, remove_iftidak mengubah size(). Anda harus mengikatnya eraseuntuk itu .
rampion
5
@rampion Ya .. hapus / hapus. Keindahan lain yang sering membuat saya merasa seperti sedang melubangi kaset saat bekerja di C ++ (berlawanan dengan bahasa modern) belakangan ini ;-)
ATV
1
Penghapusan eksplisit adalah fitur. Anda tidak harus menghapus dalam semua kasus. Terkadang iterator cukup untuk melanjutkan. Dalam kasus seperti itu, penghapusan implisit akan menghasilkan overhead yang tidak perlu. Selain itu, tidak setiap penampung dapat diubah ukurannya. std :: array misalnya tidak memiliki metode penghapusan sama sekali.
Martin Fehrs
32

Di C ++ 20, gunakan tampilan filter dari pustaka rentang: (memerlukan #include <ranges>)

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

dengan malas mengembalikan elemen genap dalam vec.

(Lihat [range.adaptor.object] / 4 dan [range.filter] )


Ini sudah didukung oleh GCC 10 ( demo langsung ). Untuk Clang dan versi GCC yang lebih lama, pustaka range-v3 asli juga dapat digunakan, dengan #include <range/v3/view/filter.hpp>(atau #include <range/v3/all.hpp>) dan ranges::viewsnamespace alih-alih std::ranges::views( demo langsung ).

LF
sumber
Anda harus memberikan #include and use namespace yang diperlukan agar jawaban Anda dapat dikompilasi. Juga, kompiler apa yang mendukung ini untuk hari ini?
gsimard
2
@gsim Lebih baik sekarang?
LF
1
Jika ada yang mencoba melakukan ini di macOS: Mulai Mei 2020, libc ++ tidak mendukung ini.
dax
25

Saya pikir Boost.Range juga layak disebutkan. Kode yang dihasilkan cukup mirip dengan aslinya:

#include <boost/range/adaptors.hpp>

// ...

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

Satu-satunya downside adalah harus secara eksplisit mendeklarasikan tipe parameter lambda. Saya menggunakan dectype (elements) :: value_type karena ini menghindari harus mengeja tipe yang tepat, dan juga menambahkan butiran genericity. Alternatifnya, dengan lambda polimorfik C ++ 14, jenisnya dapat dengan mudah ditetapkan sebagai otomatis:

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

filteredElements akan menjadi rentang, cocok untuk traversal, tetapi pada dasarnya ini adalah tampilan penampung asli. Jika yang Anda butuhkan adalah penampung lain yang berisi salinan elemen yang memenuhi kriteria (sehingga tidak tergantung dari masa pakai penampung asli), ini akan terlihat seperti:

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));
pengguna2478832
sumber
12

Saran saya untuk C ++ setara dengan C #

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

Tentukan fungsi template yang Anda berikan predikat lambda untuk melakukan pemfilteran. Fungsi template mengembalikan hasil yang difilter. misalnya:

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

untuk digunakan - memberikan contoh sepele:

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
sumber
11

Kode pjm yang ditingkatkan mengikuti saran garis bawah-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;
}

Pemakaian:

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

auto filteredVec = filter(myVec, [](int a) { return a > 5; });
Alex P.
sumber