Bagaimana cara menghindari loop "for" dengan kondisi "if" di dalamnya dengan C ++?

111

Dengan hampir semua kode yang saya tulis, saya sering berurusan dengan masalah pengurangan set pada koleksi yang pada akhirnya berakhir dengan kondisi "jika" yang naif di dalamnya. Berikut contoh sederhananya:

for(int i=0; i<myCollection.size(); i++)
{
     if (myCollection[i] == SOMETHING)
     {
           DoStuff();
     }
}

Dengan bahasa fungsional, saya dapat memecahkan masalah dengan mengurangi koleksi ke koleksi lain (dengan mudah) dan kemudian melakukan semua operasi pada set saya yang dikurangi. Dalam pseudocode:

newCollection <- myCollection where <x=true
map DoStuff newCollection

Dan pada varian C lainnya, seperti C #, saya bisa menguranginya dengan klausa where

foreach (var x in myCollection.Where(c=> c == SOMETHING)) 
{
   DoStuff();
}

Atau lebih baik (setidaknya di mataku)

myCollection.Where(c=>c == Something).ToList().ForEach(d=> DoStuff(d));

Memang, saya melakukan banyak pencampuran paradigma dan gaya berbasis opini / subjektif, tetapi saya merasa bahwa saya kehilangan sesuatu yang sangat mendasar yang memungkinkan saya menggunakan teknik yang disukai ini dengan C ++. Bisakah seseorang mencerahkan saya?

Darkenor
sumber
7
Di luar fungsionalitas pustaka standar C ++, Anda dapat mencoba std::copy_if, tetapi pilihannya tidak malas
milleniumbug
14
Anda mungkin tertarik dengan range-v3 . Ini juga harus datang ke C ++ sebagai TS dan mudah-mudahan distandarisasi dalam rilis mendatang.
NathanOliver
12
Saya merasa perlu untuk menunjukkan bahwa bagian ifdalam yang forAnda sebutkan tidak hanya secara fungsional setara dengan contoh lain tetapi juga mungkin akan lebih cepat dalam banyak kasus. Juga untuk seseorang yang mengklaim menyukai gaya fungsional, apa yang Anda promosikan tampaknya bertentangan dengan konsep kemurnian pemrograman fungsional yang sangat disukai karena DoStuffjelas memiliki efek samping.
Pharap
60
Saya tidak pernah benar-benar mengerti mengapa orang menghubungkan semua logika ke dalam satu baris membuatnya terlihat lebih baik atau lebih mudah dibaca. Cuplikan C ++ Anda di bagian paling atas sejauh ini adalah yang paling mudah dibaca oleh saya dari semua kemungkinan Anda. Dan karena efisiensi tidak akan berubah, saya tidak mengerti mengapa Anda memilih untuk tidak menulisnya, kecuali Anda dibayar berdasarkan jumlah baris kode yang Anda hapus.
Cody Gray
10
@CodyGray Setuju: itu hanya gula sintaksis. Dan judul pertanyaan menyesatkan, karena sangat berbeda dalam menghindari percabangan dan menyembunyikannya di bawah abstraksi.
edmz

Jawaban:

99

IMHO itu lebih lurus ke depan dan lebih mudah dibaca menggunakan for loop dengan if di dalamnya. Namun, jika ini mengganggu Anda, Anda dapat menggunakan cara for_each_ifseperti di bawah ini:

template<typename Iter, typename Pred, typename Op> 
void for_each_if(Iter first, Iter last, Pred p, Op op) {
  while(first != last) {
    if (p(*first)) op(*first);
    ++first;
  }
}

Usecase:

std::vector<int> v {10, 2, 10, 3};
for_each_if(v.begin(), v.end(), [](int i){ return i > 5; }, [](int &i){ ++i; });

Demo Langsung

101010
sumber
10
Itu sangat pintar. Saya juga setuju bahwa ini tidak langsung dan saya mungkin akan menggunakan kondisi if saat pemrograman C ++ yang dikonsumsi oleh orang lain. Tapi itulah yang saya butuhkan untuk penggunaan pribadi saya! :)
Darkenor
14
@Default Meneruskan pasangan iterator daripada kontainer lebih fleksibel dan idiomatik C ++.
Mark B
8
@Slava, secara umum rentang tidak akan mengurangi jumlah algoritme. Misalnya, Anda masih perlu find_ifdan findapakah mereka bekerja pada rentang atau pasangan iterator. (Ada beberapa pengecualian, seperti for_eachdan for_each_n). Cara untuk menghindari penulisan algos baru untuk setiap bersin adalah dengan menggunakan operasi yang berbeda dengan algos yang ada, misalnya, alih-alih for_each_ifmenyematkan kondisi ke dalam callable yang diteruskan for_each, misalnyafor_each(first, last, [&](auto& x) { if (cond(x)) f(x); });
Jonathan Wakely
9
Saya harus setuju dengan kalimat pertama: Solusi standar untuk-jika jauh lebih mudah dibaca dan digunakan. Saya pikir sintaks lambda dan penggunaan template yang didefinisikan di tempat lain hanya untuk menangani loop sederhana akan mengganggu atau mungkin membingungkan pengembang lain. Anda mengorbankan lokalitas dan kinerja untuk ... apa? Mampu menulis sesuatu dalam satu baris?
pengguna1354557
45
Cough @Darkenor, umumnya pemrograman yang " sangat pintar" harus dihindari karena mengganggu omong kosong semua orang termasuk diri Anda di masa depan.
Ryan
48

Boost menyediakan rentang yang dapat digunakan dengan berbasis rentang. Rentang memiliki keuntungan bahwa mereka tidak menyalin struktur data yang mendasari, mereka hanya menyediakan 'tampilan' (yaitu begin(), end()untuk rentang dan operator++(), operator==()untuk iterator). Ini mungkin menarik minat Anda: http://www.boost.org/libs/range/doc/html/range/reference/adaptors/reference/filtered.html

#include <boost/range/adaptor/filtered.hpp>
#include <iostream>
#include <vector>

struct is_even
{
    bool operator()( int x ) const { return x % 2 == 0; }
};

int main(int argc, const char* argv[])
{
    using namespace boost::adaptors;

    std::vector<int> myCollection{1,2,3,4,5,6,7,8,9};

    for( int i: myCollection | filtered( is_even() ) )
    {
        std::cout << i;
    }
}
lorro
sumber
1
Bolehkah saya menyarankan untuk menggunakan contoh OP sebagai gantinya, yaitu is_even=> condition, input=> myCollectiondll.
Default
Ini adalah jawaban yang sangat bagus dan pasti yang ingin saya lakukan. Saya akan menunda untuk menerima kecuali seseorang dapat menemukan cara yang sesuai standar untuk melakukannya yang menggunakan eksekusi malas / ditangguhkan. Suara positif.
Darkenor
5
@Darkenor: Jika Boost adalah masalah bagi Anda (misalnya, Anda dilarang menggunakannya karena kebijakan perusahaan & kebijaksanaan manajer), saya dapat memberikan definisi yang disederhanakan filtered()untuk Anda - yang mengatakan, lebih baik menggunakan lib yang didukung daripada beberapa kode ad-hoc.
lorro
Sangat setuju dengan Anda. Saya menerimanya karena cara yang memenuhi standar yang datang lebih dulu karena pertanyaannya diarahkan ke C ++ itu sendiri, bukan pustaka boost. Tapi ini sangat bagus. Juga - ya, sayangnya saya telah bekerja di banyak tempat yang melarang Boost karena alasan yang tidak masuk akal ...
Darkenor
@LeeClagett:? .
lorro
44

Daripada membuat algoritme baru, seperti jawaban yang diterima, Anda dapat menggunakan algoritme yang sudah ada dengan fungsi yang menerapkan kondisi:

std::for_each(first, last, [](auto&& x){ if (cond(x)) { ... } });

Atau jika Anda benar-benar menginginkan algoritme baru, setidaknya gunakan kembali algoritme tersebut for_eachdaripada menduplikasi logika iterasi:

template<typename Iter, typename Pred, typename Op> 
  void
  for_each_if(Iter first, Iter last, Pred p, Op op) {
    std::for_each(first, last, [&](auto& x) { if (p(x)) op(x); });
  }
Jonathan Wakely
sumber
Jauh lebih baik dan lebih jelas untuk menggunakan perpustakaan standar.
anonim
4
Karena std::for-each(first, last, [&](auto& x) {if (p(x)) op(x); });benar-benar lebih sederhana dari for (Iter x = first; x != last; x++) if (p(x)) op(x);}?
pengguna253751
2
@immibis menggunakan kembali pustaka standar memiliki manfaat lain, seperti pemeriksaan validitas iterator, atau (dalam C ++ 17) menjadi lebih mudah untuk diparalelkan, cukup dengan menambahkan satu argumen lagi: std::for_each(std::execution::par, first, last, ...);Seberapa mudah menambahkan hal-hal itu ke loop tulisan tangan?
Jonathan Wakely
1
#pragma omp paralel untuk
Mark K Cowan
2
@ tandai maaf, beberapa kekhasan acak dari kode sumber atau rantai build Anda membuat ekstensi compiler non-standar paralel yang sangat rapuh menghasilkan peningkatan kinerja nol tanpa diagnostik.
Yakk - Adam Nevraumont
21

Ide untuk menghindari

for(...)
    if(...)

konstruksi sebagai antipattern terlalu luas.

Tidak masalah untuk memproses beberapa item yang cocok dengan ekspresi tertentu dari dalam loop, dan kode tidak bisa lebih jelas dari itu. Jika pemrosesan menjadi terlalu besar untuk muat di layar, itu adalah alasan yang baik untuk menggunakan subrutin, tetapi tetap saja kondisional paling baik ditempatkan di dalam loop, yaitu

for(...)
    if(...)
        do_process(...);

jauh lebih disukai daripada

for(...)
    maybe_process(...);

Ini menjadi antipattern ketika hanya satu elemen yang cocok, karena akan lebih jelas untuk mencari elemen terlebih dahulu, dan melakukan pemrosesan di luar loop.

for(int i = 0; i < size; ++i)
    if(i == 5)

adalah contoh ekstrim dan jelas dari ini. Lebih halus, dan dengan demikian lebih umum, adalah pola pabrik seperti

for(creator &c : creators)
    if(c.name == requested_name)
    {
        unique_ptr<object> obj = c.create_object();
        obj.owner = this;
        return std::move(obj);
    }

Ini sulit dibaca, karena tidak jelas bahwa kode body hanya akan dijalankan satu kali. Dalam kasus ini, akan lebih baik untuk memisahkan pencarian:

creator &lookup(string const &requested_name)
{
    for(creator &c : creators)
        if(c.name == requested_name)
            return c;
}

creator &c = lookup(requested_name);
unique_ptr obj = c.create_object();

Masih ada ifdalam a for, tetapi dari konteksnya menjadi jelas apa yang dilakukannya, tidak perlu mengubah kode ini kecuali pencarian berubah (mis. Menjadi a map), dan segera jelas bahwa create_object()hanya dipanggil sekali, karena itu tidak dalam satu lingkaran.

Simon Richter
sumber
Saya suka ini, sebagai gambaran umum yang bijaksana dan seimbang, meskipun dalam arti tertentu menolak untuk menjawab pertanyaan yang diajukan. Saya menemukan bahwa for( range ){ if( condition ){ action } }-style memudahkan untuk membaca sesuatu satu bagian pada satu waktu dan hanya menggunakan pengetahuan tentang konstruksi bahasa dasar.
PJTraill
@PJTraill, cara pertanyaan itu diungkapkan mengingatkan saya pada kata-kata kasar Raymond Chen terhadap antipattern for-if , yang telah dikulturkan dengan kargo dan entah bagaimana menjadi absolut. Saya sangat setuju bahwa for(...) if(...) { ... }seringkali itu merupakan pilihan terbaik (itulah mengapa saya memenuhi syarat rekomendasi untuk membagi tindakan menjadi subrutin).
Simon Richter
1
Terima kasih untuk tautannya, yang memperjelas hal-hal untuk saya: nama " untuk-jika " menyesatkan, dan harus seperti " untuk-semua-jika-satu " atau " penghindaran-pencarian ". Ini mengingatkan saya pada cara Abstraksi inversi dijelaskan oleh Wikipedia pada tahun 2005 seperti ketika seseorang " membuat konstruksi sederhana di atas yang kompleks " - sampai saya menulis ulang! Sebenarnya saya bahkan tidak akan terburu-buru untuk memperbaiki formulir pencarian-proses-keluar for(…)if(…)…jika itu adalah satu-satunya tempat pencarian terjadi.
PJTraill
17

Berikut adalah fungsi cepat yang relatif minimal filter.

Itu butuh predikat. Ini mengembalikan objek fungsi yang membutuhkan iterable.

Ini mengembalikan iterable yang dapat digunakan dalam satu for(:)lingkaran.

template<class It>
struct range_t {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
  bool empty() const { return begin()==end(); }
};
template<class It>
range_t<It> range( It b, It e ) { return {std::move(b), std::move(e)}; }

template<class It, class F>
struct filter_helper:range_t<It> {
  F f;
  void advance() {
    while(true) {
      (range_t<It>&)*this = range( std::next(this->begin()), this->end() );
      if (this->empty())
        return;
      if (f(*this->begin()))
        return;
    }
  }
  filter_helper(range_t<It> r, F fin):
    range_t<It>(r), f(std::move(fin))
  {
      while(true)
      {
          if (this->empty()) return;
          if (f(*this->begin())) return;
          (range_t<It>&)*this = range( std::next(this->begin()), this->end() );
      }
  }
};

template<class It, class F>
struct filter_psuedo_iterator {
  using iterator_category=std::input_iterator_tag;
  filter_helper<It, F>* helper = nullptr;
  bool m_is_end = true;
  bool is_end() const {
    return m_is_end || !helper || helper->empty();
  }

  void operator++() {
    helper->advance();
  }
  typename std::iterator_traits<It>::reference
  operator*() const {
    return *(helper->begin());
  }
  It base() const {
      if (!helper) return {};
      if (is_end()) return helper->end();
      return helper->begin();
  }
  friend bool operator==(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
    if (lhs.is_end() && rhs.is_end()) return true;
    if (lhs.is_end() || rhs.is_end()) return false;
    return lhs.helper->begin() == rhs.helper->begin();
  }
  friend bool operator!=(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
    return !(lhs==rhs);
  }
};
template<class It, class F>
struct filter_range:
  private filter_helper<It, F>,
  range_t<filter_psuedo_iterator<It, F>>
{
  using helper=filter_helper<It, F>;
  using range=range_t<filter_psuedo_iterator<It, F>>;

  using range::begin; using range::end; using range::empty;

  filter_range( range_t<It> r, F f ):
    helper{{r}, std::forward<F>(f)},
    range{ {this, false}, {this, true} }
  {}
};

template<class F>
auto filter( F&& f ) {
    return [f=std::forward<F>(f)](auto&& r)
    {
        using std::begin; using std::end;
        using iterator = decltype(begin(r));
        return filter_range<iterator, std::decay_t<decltype(f)>>{
            range(begin(r), end(r)), f
        };
    };
};

Saya mengambil jalan pintas. Perpustakaan yang sebenarnya harus membuat iterator yang nyata, bukan for(:)pseudo-fasad yang memenuhi syarat seperti yang saya lakukan.

Saat digunakan, tampilannya seperti ini:

int main()
{
  std::vector<int> test = {1,2,3,4,5};
  for( auto i: filter([](auto x){return x%2;})( test ) )
    std::cout << i << '\n';
}

yang cukup bagus, dan cetakan

1
3
5

Contoh langsung .

Ada tambahan yang diusulkan untuk C ++ yang disebut Rangesv3 yang melakukan hal semacam ini dan banyak lagi. boostjuga memiliki rentang filter / iterator yang tersedia. boost juga memiliki pembantu yang membuat tulisan di atas jauh lebih singkat.

Yakk - Adam Nevraumont
sumber
15

Salah satu gaya yang cukup sering digunakan untuk disebutkan, tetapi belum disebutkan, adalah:

for(int i=0; i<myCollection.size(); i++) {
  if (myCollection[i] != SOMETHING)
    continue;

  DoStuff();
}

Keuntungan:

  • Tidak mengubah tingkat indentasi DoStuff();saat kompleksitas kondisi meningkat. Logikanya, DoStuff();harus di tingkat atas dari forloop, dan itu.
  • Segera membuat jelas bahwa iterates loop atas SOMETHINGs koleksi, tanpa memerlukan pembaca untuk memverifikasi bahwa tidak ada setelah penutupan }dari ifblok.
  • Tidak memerlukan pustaka atau makro pembantu atau fungsi apa pun.

Kekurangan:

  • continue, Seperti pernyataan kontrol aliran lainnya, akan disalahgunakan dengan cara-cara yang mengarah pada kode-to-follow keras sehingga beberapa orang menentang setiap penggunaan mereka: ada gaya valid coding bahwa beberapa tindak yang menghindari continue, yang menghindari breakselain di a switch, yang menghindari returnselain di akhir fungsi.

sumber
3
Saya berpendapat bahwa dalam forloop yang berjalan ke banyak baris, dua baris "jika tidak, lanjutkan" jauh lebih jelas, logis, dan dapat dibaca. Segera mengatakan, "lewati ini jika" setelah forpernyataan terbaca dengan baik dan, seperti yang Anda katakan, tidak membuat indentasi aspek fungsional yang tersisa dari loop. Jika continuelebih jauh ke bawah, bagaimanapun, beberapa kejelasan dikorbankan (yaitu jika beberapa operasi akan selalu dilakukan sebelum ifpernyataan).
anonim
11
for(auto const &x: myCollection) if(x == something) doStuff();

Sepertinya forpemahaman khusus C ++ bagi saya. Kepadamu?

bipll
sumber
Saya tidak berpikir kata kunci otomatis ada sebelum c ++ 11 jadi saya tidak akan mengatakan itu sangat klasik c ++. Jika saya dapat mengajukan pertanyaan di sini di komentar, akankah "auto const" memberi tahu kompiler bahwa ia dapat mengatur ulang semua elemen sesuai keinginan? Mungkin akan lebih mudah bagi kompiler untuk merencanakan menghindari percabangan jika itu masalahnya.
mathreadler
1
@mathreadler Semakin cepat orang berhenti mengkhawatirkan "c ++ klasik", semakin baik. C ++ 11 adalah peristiwa makroevolusi untuk bahasa tersebut & berusia 5 tahun: itu harus menjadi minimum yang kami perjuangkan. Bagaimanapun, OP menandai itu dan C ++ 14 (bahkan lebih baik!). Tidak, auto consttidak ada sangkut pautnya dengan urutan iterasi. Jika Anda mencari berbasis jarak for, Anda akan melihat bahwa itu pada dasarnya melakukan loop standar dari begin()ke end()dengan dereferensi implisit. Tidak ada cara yang dapat merusak jaminan pemesanan (jika ada) dari kontainer yang diiterasi; itu akan ditertawakan dari muka bumi
underscore_d
1
@mathreadler, sebenarnya ya, itu hanya memiliki arti yang cukup berbeda. Apa yang tidak ada adalah range-for ... dan fitur C ++ 11 lain yang berbeda. Apa yang saya maksudkan di sini adalah bahwa range-fors, std::futures, std::functions, bahkan penutupan anonim itu sangat baik C ++ ish dalam sintaksnya; setiap bahasa memiliki bahasa sendiri-sendiri dan ketika menggabungkan fitur-fitur baru, ia mencoba membuatnya meniru sintaks lama yang terkenal.
bipll
@underscore_d, kompilator diperbolehkan untuk melakukan transformasi apapun asalkan aturan seolah-olah dipatuhi, bukan?
bipll
1
Hmmm, dan apa yang mungkin dimaksud dengan itu?
bipll
7

Jika DoStuff () akan bergantung pada saya entah bagaimana di masa depan maka saya akan mengusulkan varian bit-masking bebas cabang yang dijamin ini.

unsigned int times = 0;
const int kSize = sizeof(unsigned int)*8;
for(int i = 0; i < myCollection.size()/kSize; i++){
  unsigned int mask = 0;
  for (int j = 0; j<kSize; j++){
    mask |= (myCollection[i*kSize+j]==SOMETHING) << j;
  }
  times+=popcount(mask);
}

for(int i=0;i<times;i++)
   DoStuff();

Dimana popcount adalah fungsi yang melakukan penghitungan populasi (menghitung jumlah bit = 1). Akan ada kebebasan untuk menempatkan batasan yang lebih maju dengan saya dan tetangga mereka. Jika itu tidak diperlukan, kita dapat menghapus loop dalam dan membuat ulang loop luar

for(int i = 0; i < myCollection.size(); i++)
  times += (myCollection[i]==SOMETHING);

diikuti dengan a

for(int i=0;i<times;i++)
   DoStuff();
mathreadler
sumber
6

Selain itu, jika Anda tidak peduli menata ulang collection, std :: partition adalah murah.

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

void DoStuff(int i)
{
    std::cout << i << '\n';
}

int main()
{
    using namespace std::placeholders;

    std::vector<int> v {1, 2, 5, 0, 9, 5, 5};
    const int SOMETHING = 5;

    std::for_each(v.begin(),
                  std::partition(v.begin(), v.end(),
                                 std::bind(std::equal_to<int> {}, _1, SOMETHING)), // some condition
                  DoStuff); // action
}
Loreto
sumber
Tapi std::partitionmenata ulang wadahnya.
celtschk
5

Saya kagum dengan kompleksitas solusi di atas. Saya akan menyarankan yang sederhana #define foreach(a,b,c,d) for(a; b; c)if(d)tetapi memiliki beberapa defisit yang jelas, misalnya, Anda harus ingat untuk menggunakan koma alih-alih titik koma dalam lingkaran Anda, dan Anda tidak dapat menggunakan operator koma di aatau c.

#include <list>
#include <iostream>

using namespace std; 

#define foreach(a,b,c,d) for(a; b; c)if(d)

int main(){
  list<int> a;

  for(int i=0; i<10; i++)
    a.push_back(i);

  for(auto i=a.begin(); i!=a.end(); i++)
    if((*i)&1)
      cout << *i << ' ';
  cout << endl;

  foreach(auto i=a.begin(), i!=a.end(), i++, (*i)&1)
    cout << *i << ' ';
  cout << endl;

  return 0;
}
Ian Parberry
sumber
3
Kompleksitas beberapa jawaban hanya tinggi karena pertama-tama mereka menunjukkan metode umum yang dapat digunakan kembali (yang hanya akan Anda lakukan sekali) dan kemudian menggunakannya. Tidak efektif jika Anda memiliki satu loop dengan kondisi if di seluruh aplikasi tetapi sangat efektif jika terjadi ribuan kali.
gnasher729
1
Seperti kebanyakan saran, hal ini mempersulit, bukan lebih mudah, untuk mengidentifikasi rentang dan kondisi pemilihan. Dan penggunaan makro meningkatkan ketidakpastian tentang kapan (dan seberapa sering) ekspresi dievaluasi, bahkan jika tidak ada kejutan di sini.
PJTraill
2

Solusi lain jika i: ​​s penting. Yang ini membuat daftar yang mengisi indeks yang akan dipanggil doStuff (). Sekali lagi, poin utamanya adalah menghindari percabangan dan menukarnya dengan biaya aritmatika yang dapat dialirkan.

int buffer[someSafeSize];
int cnt = 0; // counter to keep track where we are in list.
for( int i = 0; i < container.size(); i++ ){
   int lDecision = (container[i] == SOMETHING);
   buffer[cnt] = lDecision*i + (1-lDecision)*buffer[cnt];
   cnt += lDecision;
}

for( int i=0; i<cnt; i++ )
   doStuff(buffer[i]); // now we could pass the index or a pointer as an argument.

Garis "ajaib" adalah garis pemuatan penyangga yang secara aritmatik menghitung apakah untuk mempertahankan nilai dan tetap pada posisinya atau untuk menghitung posisi dan menambah nilai. Jadi kami menukar cabang potensial untuk beberapa logika dan aritmatika dan mungkin beberapa cache hits. Skenario umum saat ini akan berguna adalah jika doStuff () melakukan sedikit penghitungan pipelineable dan setiap cabang di antara panggilan dapat mengganggu pipeline tersebut.

Kemudian lakukan loop di atas buffer dan jalankan doStuff () sampai kita mencapai cnt. Kali ini kita akan menyimpan arus i di buffer sehingga kita dapat menggunakannya dalam panggilan ke doStuff () jika diperlukan.

mathreadler
sumber
1

Seseorang dapat mendeskripsikan pola kode Anda sebagai menerapkan beberapa fungsi ke subset rentang, atau dengan kata lain: menerapkannya ke hasil penerapan filter ke seluruh rentang.

Ini dapat dicapai dengan cara yang paling mudah dengan pustaka range-v3 Eric Neibler ; meskipun sedikit merusak pemandangan, karena Anda ingin bekerja dengan indeks:

using namespace ranges;
auto mycollection_has_something = 
    [&](std::size_t i) { return myCollection[i] == SOMETHING };
auto filtered_view = 
    views::iota(std::size_t{0}, myCollection.size()) | 
    views::filter(mycollection_has_something);
for (auto i : filtered_view) { DoStuff(); }

Tetapi jika Anda bersedia melepaskan indeks, Anda akan mendapatkan:

auto is_something = [&SOMETHING](const decltype(SOMETHING)& x) { return x == SOMETHING };
auto filtered_collection = myCollection | views::filter(is_something);
for (const auto& x : filtered_collection) { DoStuff(); }

yang IMHO lebih bagus.

PS - Pustaka rentang sebagian besar menggunakan standar C ++ di C ++ 20.

einpoklum
sumber
0

Saya hanya akan menyebut Mike Acton, dia pasti akan berkata:

Jika Anda harus melakukan itu, Anda memiliki masalah dengan data Anda. Sortir data Anda!

v.oddou
sumber