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?
std::copy_if
, tetapi pilihannya tidak malasif
dalam yangfor
Anda 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 karenaDoStuff
jelas memiliki efek samping.Jawaban:
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_if
seperti di bawah ini:Usecase:
Demo Langsung
sumber
find_if
danfind
apakah mereka bekerja pada rentang atau pasangan iterator. (Ada beberapa pengecualian, sepertifor_each
danfor_each_n
). Cara untuk menghindari penulisan algos baru untuk setiap bersin adalah dengan menggunakan operasi yang berbeda dengan algos yang ada, misalnya, alih-alihfor_each_if
menyematkan kondisi ke dalam callable yang diteruskanfor_each
, misalnyafor_each(first, last, [&](auto& x) { if (cond(x)) f(x); });
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 danoperator++()
,operator==()
untuk iterator). Ini mungkin menarik minat Anda: http://www.boost.org/libs/range/doc/html/range/reference/adaptors/reference/filtered.htmlsumber
is_even
=>condition
,input
=>myCollection
dll.filtered()
untuk Anda - yang mengatakan, lebih baik menggunakan lib yang didukung daripada beberapa kode ad-hoc.Daripada membuat algoritme baru, seperti jawaban yang diterima, Anda dapat menggunakan algoritme yang sudah ada dengan fungsi yang menerapkan kondisi:
Atau jika Anda benar-benar menginginkan algoritme baru, setidaknya gunakan kembali algoritme tersebut
for_each
daripada menduplikasi logika iterasi:sumber
std::for-each(first, last, [&](auto& x) {if (p(x)) op(x); });
benar-benar lebih sederhana darifor (Iter x = first; x != last; x++) if (p(x)) op(x);}
?std::for_each(std::execution::par, first, last, ...);
Seberapa mudah menambahkan hal-hal itu ke loop tulisan tangan?Ide untuk menghindari
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
jauh lebih disukai daripada
Ini menjadi antipattern ketika hanya satu elemen yang cocok, karena akan lebih jelas untuk mencari elemen terlebih dahulu, dan melakukan pemrosesan di luar loop.
adalah contoh ekstrim dan jelas dari ini. Lebih halus, dan dengan demikian lebih umum, adalah pola pabrik seperti
Ini sulit dibaca, karena tidak jelas bahwa kode body hanya akan dijalankan satu kali. Dalam kasus ini, akan lebih baik untuk memisahkan pencarian:
Masih ada
if
dalam afor
, tetapi dari konteksnya menjadi jelas apa yang dilakukannya, tidak perlu mengubah kode ini kecuali pencarian berubah (mis. Menjadi amap
), dan segera jelas bahwacreate_object()
hanya dipanggil sekali, karena itu tidak dalam satu lingkaran.sumber
for( range ){ if( condition ){ action } }
-style memudahkan untuk membaca sesuatu satu bagian pada satu waktu dan hanya menggunakan pengetahuan tentang konstruksi bahasa dasar.for(...) if(...) { ... }
seringkali itu merupakan pilihan terbaik (itulah mengapa saya memenuhi syarat rekomendasi untuk membagi tindakan menjadi subrutin).for(…)if(…)…
jika itu adalah satu-satunya tempat pencarian terjadi.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.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:
yang cukup bagus, dan cetakan
Contoh langsung .
Ada tambahan yang diusulkan untuk C ++ yang disebut Rangesv3 yang melakukan hal semacam ini dan banyak lagi.
boost
juga memiliki rentang filter / iterator yang tersedia. boost juga memiliki pembantu yang membuat tulisan di atas jauh lebih singkat.sumber
Salah satu gaya yang cukup sering digunakan untuk disebutkan, tetapi belum disebutkan, adalah:
Keuntungan:
DoStuff();
saat kompleksitas kondisi meningkat. Logikanya,DoStuff();
harus di tingkat atas darifor
loop, dan itu.SOMETHING
s koleksi, tanpa memerlukan pembaca untuk memverifikasi bahwa tidak ada setelah penutupan}
dariif
blok.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 menghindaricontinue
, yang menghindaribreak
selain di aswitch
, yang menghindarireturn
selain di akhir fungsi.sumber
for
loop yang berjalan ke banyak baris, dua baris "jika tidak, lanjutkan" jauh lebih jelas, logis, dan dapat dibaca. Segera mengatakan, "lewati ini jika" setelahfor
pernyataan terbaca dengan baik dan, seperti yang Anda katakan, tidak membuat indentasi aspek fungsional yang tersisa dari loop. Jikacontinue
lebih jauh ke bawah, bagaimanapun, beberapa kejelasan dikorbankan (yaitu jika beberapa operasi akan selalu dilakukan sebelumif
pernyataan).Sepertinya
for
pemahaman khusus C ++ bagi saya. Kepadamu?sumber
auto const
tidak ada sangkut pautnya dengan urutan iterasi. Jika Anda mencari berbasis jarakfor
, Anda akan melihat bahwa itu pada dasarnya melakukan loop standar daribegin()
keend()
dengan dereferensi implisit. Tidak ada cara yang dapat merusak jaminan pemesanan (jika ada) dari kontainer yang diiterasi; itu akan ditertawakan dari muka bumistd::future
s,std::function
s, 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.Jika DoStuff () akan bergantung pada saya entah bagaimana di masa depan maka saya akan mengusulkan varian bit-masking bebas cabang yang dijamin ini.
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
diikuti dengan a
sumber
Selain itu, jika Anda tidak peduli menata ulang collection, std :: partition adalah murah.
sumber
std::partition
menata ulang wadahnya.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 dia
atauc
.sumber
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.
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.
sumber
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:
Tetapi jika Anda bersedia melepaskan indeks, Anda akan mendapatkan:
yang IMHO lebih bagus.
PS - Pustaka rentang sebagian besar menggunakan standar C ++ di C ++ 20.
sumber
Saya hanya akan menyebut Mike Acton, dia pasti akan berkata:
Jika Anda harus melakukan itu, Anda memiliki masalah dengan data Anda. Sortir data Anda!
sumber