Mengapa std :: set tidak memiliki fungsi anggota "berisi"?

103

Saya banyak menggunakan std::set<int>dan seringkali saya hanya perlu memeriksa apakah set seperti itu berisi angka atau tidak.

Menurut saya wajar untuk menulis:

if (myset.contains(number))
   ...

Tetapi karena kekurangan containsanggota, saya perlu menulis yang rumit:

if (myset.find(number) != myset.end())
  ..

atau yang tidak begitu jelas:

if (myset.count(element) > 0) 
  ..

Apakah ada alasan untuk keputusan desain ini?

Jabberwocky
sumber
7
Sebagian besar pustaka standar bekerja dengan iterator sehingga biasanya fungsi mengembalikan iterator adalah apa yang Anda harapkan. Tidak terlalu sulit untuk menulis sebuah fungsi untuk mengabstraksikannya. Kemungkinan besar kompilator akan menyebariskannya karena seharusnya hanya satu atau 2 baris kode dan Anda akan mendapatkan kinerja yang sama.
NathanOliver
3
Masalah lain (yang lebih mendasar) dengan count()pendekatan ini adalah bahwa ia melakukan lebih banyak pekerjaan daripada yang countains()harus dilakukan.
Leo Heinsaar
11
The alasan mendasar di balik keputusan desain adalah bahwa contains()yang mengembalikan sebuah boolakan kehilangan informasi berharga tentang di mana unsur ini dalam koleksi . find()mempertahankan dan mengembalikan informasi tersebut dalam bentuk iterator, oleh karena itu merupakan pilihan yang lebih baik untuk pustaka generik seperti STL. (Itu tidak berarti bahwa a bool contains()tidak terlalu bagus untuk dimiliki atau bahkan diperlukan.)
Leo Heinsaar
3
Sangat mudah untuk menulis contains(set, element)fungsi gratis menggunakan antarmuka publik set. Oleh karena itu, antarmuka himpunan secara fungsional lengkap; menambahkan metode kenyamanan hanya meningkatkan antarmuka tanpa mengaktifkan fungsi tambahan apa pun, yang bukan merupakan cara C ++.
Toby Speight
3
Apakah kita menutup semuanya hari ini? Bagaimana pertanyaan ini "Didasarkan terutama pada opini"?
Tn. Alien

Jawaban:

148

Saya rasa itu mungkin karena mereka berusaha membuat std::setdan std::multisetsemirip mungkin. (Dan jelas countmemiliki arti yang sangat masuk akal untuk std::multiset.)

Secara pribadi saya pikir ini adalah kesalahan.

Tidak terlalu buruk jika Anda berpura-pura bahwa countitu hanya salah eja containsdan menulis tes sebagai:

if (myset.count(element)) 
   ...

Itu masih memalukan.

Martin Bonner mendukung Monica
sumber
5
Kebetulan, itu persis sama dengan peta dan multimaps (yang sama jeleknya, tapi tidak seburuk semua perbandingan ini .end()).
Matteo Italia
8
Atau, mereka mungkin tidak melihat kebutuhan untuk anggota tambahan, contains()karena itu akan mubazir karena untuk setiap std::set<T> sdan T t, hasil dari s.contains(t)sama persis dengan hasil static_cast<bool>(s.count(t)). Karena menggunakan nilai dalam ekspresi kondisional secara implisit akan mentransmisikannya bool, mereka mungkin merasa bahwa itu count()memenuhi tujuan dengan cukup baik.
Waktu Justin - Kembalikan Monica
2
Salah eja? if (myset.ICanHaz(element)) ...: D
Stéphane Gourichon
3
@MartinBonner Tidak masalah jika alasan untuk meninggalkannya bodoh. Juga tidak masalah jika percakapan itu bukan 100% alasan terakhir. Jawaban Anda di sini hanyalah tebakan yang masuk akal tentang bagaimana menurut Anda seharusnya . Percakapan, dan jawaban dari seseorang tidak hanya terlibat di dalamnya, tetapi ditugaskan untuk mengusulkannya (meskipun mereka tidak melakukannya) sangat dekat dengan kebenaran daripada tebakan ini, tidak peduli bagaimana Anda melihatnya. Pada minimal Anda harus di setidaknya menyebutkan dalam jawaban ini, yang akan menjadi perbaikan besar dan akan menjadi hal yang bertanggung jawab untuk melakukan.
Jason C
2
@JasonC: Bisakah Anda melanjutkan dan mengedit bagian di bawah? Saya tidak begitu mengerti maksud yang ingin Anda sampaikan, dan komentar mungkin bukan cara terbaik untuk menjelaskannya. Terima kasih!
Martin Bonner mendukung Monica
44

Untuk dapat menulis if (s.contains()), contains()harus mengembalikan bool(atau tipe yang dapat dikonversi bool, yang merupakan cerita lain), seperti binary_searchhalnya.

The alasan mendasar di balik keputusan desain tidak untuk melakukannya dengan cara ini adalah bahwa contains()yang mengembalikan boolakan kehilangan informasi berharga tentang di mana unsur ini dalam koleksi . find()mempertahankan dan mengembalikan informasi tersebut dalam bentuk iterator, oleh karena itu merupakan pilihan yang lebih baik untuk pustaka generik seperti STL. Ini selalu menjadi prinsip panduan Alex Stepanov, seperti yang sering dia jelaskan (misalnya, di sini ).

Mengenai count()pendekatan secara umum, meskipun sering kali merupakan solusi yang baik, masalah dengan itu adalah bahwa ia melakukan lebih banyak pekerjaan daripada yang contains() harus dilakukan .

Itu tidak berarti bahwa a bool contains()bukanlah sesuatu yang sangat bagus untuk dimiliki atau bahkan diperlukan. Beberapa waktu yang lalu kami berdiskusi panjang tentang masalah yang sama ini di grup ISO C ++ Standard - Future Proposals.

Leo Heinsaar
sumber
5
Dan menarik untuk dicatat bahwa diskusi itu diakhiri dengan konsensus yang mendekati yang diinginkan dan Anda diminta untuk menulis proposal untuk itu.
PJTraill
@PJTraill Benar, dan alasan saya tidak melanjutkan adalah karena contains(), jelas, akan berinteraksi kuat dengan container dan algoritme yang ada, yang akan sangat dipengaruhi oleh konsep dan rentang - pada waktu yang diperkirakan akan masuk ke C ++ 17 - dan Saya yakin (sebagai hasil dari diskusi serta beberapa pertukaran email pribadi) bahwa lebih baik menunggu mereka terlebih dahulu. Tentu saja, pada tahun 2015 tidak jelas bahwa baik konsep maupun rentang tidak akan berhasil mencapai C ++ 17 (pada kenyataannya, ada harapan besar bahwa mereka akan berhasil). Saya tidak yakin itu layak untuk dikejar sekarang.
Leo Heinsaar
1
Karena std::set(yang ditanyakan pertanyaannya), saya tidak melihat bagaimana cara countmelakukan lebih banyak pekerjaan daripada yang containsharus dilakukan. Implementasi glibc countadalah (secara kasar) return find(value) == end() ? 0 : 1;. Terlepas dari detail tentang operator terner vs hanya kembali != end()(yang saya harapkan akan dihapus oleh pengoptimal), saya tidak dapat melihat bagaimana ada pekerjaan lagi.
Martin Bonner mendukung Monica
4
"... contains () yang mengembalikan bool akan kehilangan informasi berharga tentang di mana elemen tersebut berada dalam kumpulan " - Jika pengguna memanggil myset.contains()(jika ada), itu akan menjadi indikasi yang cukup kuat bahwa informasi itu tidak berharga ( kepada pengguna dalam konteks itu).
Keith Thompson
1
Mengapa count()melakukan lebih banyak pekerjaan daripada yang contains()harus dilakukan std::set? Ini unik jadi count()bisa saja return contains(x) ? 1 : 0;yang persis sama.
Timmmm
22

Itu kurang karena tidak ada yang menambahkannya. Tidak ada yang menambahkannya karena kontainer dari STL yang stddimasukkan perpustakaan dirancang untuk menjadi antarmuka yang minimal. (Perhatikan bahwa std::stringtidak berasal dari STL dengan cara yang sama).

Jika Anda tidak keberatan dengan sintaks yang aneh, Anda dapat memalsukannya:

template<class K>
struct contains_t {
  K&& k;
  template<class C>
  friend bool operator->*( C&& c, contains_t&& ) {
    auto range = std::forward<C>(c).equal_range(std::forward<K>(k));
    return range.first != range.second;
    // faster than:
    // return std::forward<C>(c).count( std::forward<K>(k) ) != 0;
    // for multi-meows with lots of duplicates
  }
};
template<class K>
containts_t<K> contains( K&& k ) {
  return {std::forward<K>(k)};
}

menggunakan:

if (some_set->*contains(some_element)) {
}

Pada dasarnya, Anda dapat menulis metode ekstensi untuk sebagian besar stdjenis C ++ menggunakan teknik ini.

Jauh lebih masuk akal untuk hanya melakukan ini:

if (some_set.count(some_element)) {
}

tapi saya terhibur dengan metode metode ekstensi.

Hal yang sangat menyedihkan adalah bahwa menulis efisien containsbisa lebih cepat pada multimapatau multiset, karena mereka hanya perlu menemukan satu elemen, sementara countharus menemukan masing-masing dan menghitungnya .

Sebuah multiset yang berisi 1 miliar salinan 7 (Anda tahu, seandainya kehabisan) bisa sangat lambat .count(7), tetapi bisa sangat cepat contains(7).

Dengan metode ekstensi di atas, kita dapat mempercepat kasus ini dengan menggunakan lower_bound, membandingkan end, dan kemudian membandingkan dengan elemen. Namun, melakukan itu untuk meong yang tidak berurutan serta meong yang dipesan akan membutuhkan SFINAE yang mewah atau kelebihan muatan khusus kontainer.

Yakk - Adam Nevraumont
sumber
2
1 miliar eksemplar 7? Dan di sini saya pikir std::settidak dapat berisi duplikat dan karena itu std::set::countakan selalu kembali 0atau 1.
nwp
5
@nwp std::multiset::countcan
milleniumbug
2
@nwp Kekurangan saya di backtickssekitar kata "set" adalah karena saya tidak mengacu std::setsecara spesifik. Untuk membuat Anda merasa lebih baik, saya akan menambahkan multi
Yakk - Adam Nevraumont
3
Sepertinya saya merindukan lelucon tentang "meong" yang seharusnya menjadi rujukan.
user2357112 mendukung Monica
2
@ user2357112 meow adalah placeholder untuk "set atau peta". Tanyakan pada STL mengapa.
Yakk - Adam Nevraumont
12

Anda mencari kasus tertentu dan tidak melihat gambaran yang lebih besar. Sebagaimana dinyatakan dalam dokumentasi, std::set memenuhi persyaratan konsep AssociativeContainer . Untuk konsep itu tidak masuk akal untuk memiliki containsmetode, karena tidak banyak berguna untuk std::multisetdan std::multimap, tetapi countberfungsi dengan baik untuk semuanya. Meskipun metode containsdapat ditambahkan sebagai alias untuk countuntuk std::set, std::mapdan versi hash mereka (seperti lengthuntuk size()di std::string), tapi terlihat seperti pencipta perpustakaan tidak melihat kebutuhan nyata untuk itu.

Slava
sumber
8
Perhatikan bahwa itu stringadalah monster: ia ada sebelum STL, di mana ia memiliki lengthdan semua metode yang berbasis indeks, dan kemudian "di-container" agar sesuai dengan model STL ... tanpa menghapus metode yang ada karena alasan kompatibilitas ke belakang . Lihat GotW # 84: Monoliths Unstrung => stringbenar-benar melanggar prinsip desain "jumlah minimum fungsi anggota".
Matthieu M.
5
Tapi kemudian pertanyaannya menjadi "Mengapa layak memiliki konsep AssociativeContainer seperti itu?" - dan aku tidak yakin kalau dipikir-pikir.
Martin Bonner mendukung Monica
24
Menanyakan apakah multiset, multimap, atau peta berisi sesuatu yang sangat masuk akal bagi saya. Sebenarnya, containssama dalam upaya pada satu set / map, tetapi bisa dibuat lebih cepat dari countpada multiset / multimap.
Yakk - Adam Nevraumont
5
AssociativeContainer tidak membutuhkan kelas untuk tidak memiliki containsmetode.
user2357112 mendukung Monica
6
@Slava Itu seperti mengatakan size()dan empty()merupakan duplikat, namun banyak wadah memiliki keduanya.
Barry
10

Meskipun saya tidak tahu mengapa std::settidak ada containstetapi countyang hanya pernah kembali 0atau 1, Anda dapat menulis containsfungsi pembantu template seperti ini:

template<class Container, class T>
auto contains(const Container& v, const T& x)
-> decltype(v.find(x) != v.end())
{
    return v.find(x) != v.end();
}

Dan gunakan seperti ini:

    if (contains(myset, element)) ...
rustyx.dll
sumber
3
-1, karena ini secara langsung bertentangan dengan fakta bahwa sebenarnya containsmetode tersebut ada, hanya dinamai dengan cara yang bodoh.
Matteo Italia
4
"STL berusaha untuk menawarkan antarmuka minimal" batuk std::string batuk
bolov
6
@bolov: maksud Anda? std.::stringBUKAN bagian dari STL! Ini adalah bagian dari perpustakaan standar dan memiliki template retroaktif ...
MFH
3
@MatteoItalia countbisa lebih lambat karena secara efektif perlu melakukan dua finddetik untuk mendapatkan awal dan akhir rentang jika kode dibagikan multiset.
Mark Ransom
2
OP sudah tahu itu mubazir, tetapi tampaknya ingin kode tersebut dibaca secara eksplisit contains. Saya tidak melihat ada yang salah dengan itu. @MarkRansom, SFINAE kecil adalah untuk mencegah templat ini mengikat hal-hal yang tidak semestinya.
rustyx
7

Alasan sebenarnya setadalah misteri bagi saya, tetapi satu kemungkinan penjelasan untuk desain yang sama ini mapadalah untuk mencegah orang menulis kode yang tidak efisien secara tidak sengaja:

if (myMap.contains("Meaning of universe"))
{
    myMap["Meaning of universe"] = 42;
}

Yang akan menghasilkan dua mappencarian.

Sebaliknya, Anda dipaksa untuk mendapatkan iterator. Ini memberi Anda petunjuk mental bahwa Anda harus menggunakan kembali iterator:

auto position = myMap.find("Meaning of universe");
if (position != myMap.cend())
{
    position->second = 42;
}

yang hanya menggunakan satu mappencarian.

Ketika kita menyadarinya setdan mapdibuat dari daging yang sama, kita dapat menerapkan prinsip ini juga pada set. Artinya, jika kita ingin bertindak atas item di sethanya jika ada di set, desain ini dapat mencegah kita menulis kode seperti ini:

struct Dog
{
    std::string name;
    void bark();
}

operator <(Dog left, Dog right)
{
    return left.name < right.name;
}

std::set<Dog> dogs;
...
if (dogs.contain("Husky"))
{
    dogs.find("Husky")->bark();
}

Tentu semua ini hanyalah spekulasi belaka.

Martin Drozdik
sumber
1
Ya, tapi untuk set int ini tidak berlaku.
Jabberwocky
7
Kecuali orang bisa menulis dengan if (myMap.count("Meaning of universe"))baik, jadi ...?
Barry
@MichaelWzz Ups, kamu benar. Saya memodifikasi jawaban saya untuk memasukkan juga contoh yang ditetapkan. Namun, alasan untuk satu set int adalah misteri bagi saya.
Martin Drozdik
2
Ini tidak mungkin benar. Mereka dapat dengan mudah menulis kode Anda yang tidak efisien containsseperti halnya dengan count.
Martin Bonner mendukung Monica
2

Sejak c ++ 20,

bool contains( const Key& key ) const

tersedia.

Andy
sumber
0

Bagaimana dengan binary_search?

 set <int> set1;
 set1.insert(10);
 set1.insert(40);
 set1.insert(30);
 if(std::binary_search(set1.begin(),set1.end(),30))
     bool found=true;
Massimiliano Di Cavio
sumber
Itu tidak akan berhasil std::unordered_set, tetapi untuk std::setitu akan berhasil.
Jabberwocky
Itu normal, binary_search bekerja hanya untuk pohon biner.
Massimiliano Di Cavio
0

berisi () harus mengembalikan bool. Menggunakan kompiler C ++ 20 saya mendapatkan output berikut untuk kode:

#include<iostream>
#include<map>
using namespace std;

int main()
{
    multimap<char,int>mulmap;
    mulmap.insert(make_pair('a', 1)); //multiple similar key
    mulmap.insert(make_pair('a', 2)); //multiple similar key
    mulmap.insert(make_pair('a', 3)); //multiple similar key
    mulmap.insert(make_pair('b', 3));
    mulmap.insert({'a',4});
    mulmap.insert(pair<char,int>('a', 4));
    
    cout<<mulmap.contains('c')<<endl;  //Output:0 as it doesn't exist
    cout<<mulmap.contains('b')<<endl;  //Output:1 as it exist
}
bashar
sumber
-1

Alasan lain adalah bahwa hal itu akan memberikan kesan yang salah kepada programmer bahwa std :: set adalah himpunan dalam pengertian teori himpunan matematika. Jika mereka mengimplementasikannya, maka banyak pertanyaan lain akan mengikuti: jika sebuah std :: set memiliki berisi () untuk sebuah nilai, mengapa ia tidak memilikinya untuk set lain? Di mana union (), intersection () dan operasi dan predikat himpunan lainnya?

Jawabannya adalah, tentu saja, beberapa operasi himpunan sudah diimplementasikan sebagai fungsi di (std :: set_union () dll.) Dan lainnya diimplementasikan semudah berisi (). Fungsi dan objek fungsi bekerja lebih baik dengan abstraksi matematika daripada anggota objek, dan mereka tidak terbatas pada jenis wadah tertentu.

Jika seseorang perlu mengimplementasikan fungsionalitas set-matematika lengkap, dia tidak hanya memiliki pilihan wadah yang mendasarinya, tetapi juga dia memiliki pilihan detail implementasi, misalnya, apakah fungsi theory_union () -nya akan berfungsi dengan objek yang tidak dapat diubah, lebih cocok untuk pemrograman fungsional , atau apakah itu akan memodifikasi operannya dan menghemat memori? Apakah ini akan diimplementasikan sebagai objek fungsi dari awal atau akan lebih baik jika diimplementasikan adalah fungsi-C, dan gunakan std :: function <> jika diperlukan?

Seperti sekarang, std :: set hanyalah sebuah wadah, cocok untuk implementasi himpunan dalam pengertian matematika, tetapi ini hampir jauh dari himpunan teoretis seperti std :: vector dari vektor teoretis.

Mike Tyukanov
sumber