Std :: set neden "içerir" üye işlevine sahip değil?


103

Yoğun bir şekilde kullanıyorum std::set<int>ve çoğu zaman böyle bir setin bir sayı içerip içermediğini kontrol etmem gerekiyor.

Yazmayı doğal buluyorum:

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

Ancak bir containsüye olmadığı için hantal yazmam gerekiyor:

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

veya o kadar bariz değil:

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

Bu tasarım kararının bir nedeni var mı?


7
Standart kitaplığın çoğu yineleyicilerle çalışır, bu nedenle normalde yineleyiciler döndüren işlevler beklediğiniz şeydir. Bunu soyutlamak için bir fonksiyon yazmak zor olmasa da. Büyük olasılıkla derleyici onu satır içine alacaktır, çünkü sadece bir veya iki kod satırı olmalıdır ve aynı performansı alacaksınız.
NathanOliver

3
count()Yaklaşımla ilgili bir başka (daha temel) sorun, bir yaklaşımın yapması gerekenden daha fazla iş countains()yapmasıdır.
Leo Heinsaar

11
Temel nedeni bu tasarım kararın arkasında olduğunu contains()bir hangi döner boololacağını eleman koleksiyonunda olduğu hakkında değerli bilgiler kaybetmek . find()bu bilgiyi bir yineleyici biçiminde korur ve döndürür, bu nedenle STL gibi genel bir kitaplık için daha iyi bir seçimdir. (Bu bool contains(),
a'nın

3
contains(set, element)Setin genel arayüzünü kullanarak ücretsiz bir işlev yazmak kolaydır . Bu nedenle, setin arayüzü işlevsel olarak tamamlanmıştır; kullanışlı bir yöntem eklemek, herhangi bir ek işlevi etkinleştirmeden arayüzü artırır, bu C ++ yolu değildir.
Toby Speight 21

3
Bu günlerde her şeyi kapatıyor muyuz? Bu soru nasıl herhangi bir şekilde "Öncelikle fikir temelli" oluyor?
Bay Uzaylı

Yanıtlar:


148

Sanırım bu muhtemelen yapmaya çalıştıkları std::setve std::multisetolabildiğince benzer oldukları içindir . (Ve açıkça countbunun için son derece mantıklı bir anlamı var std::multiset.)

Şahsen bunun bir hata olduğunu düşünüyorum.

Bunun countsadece bir yazım hatası olduğunu düşünürseniz containsve testi şöyle yazarsanız, o kadar da kötü görünmez :

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

Yine de utanç verici.


5
Bu arada, haritalar ve çoklu haritalarla tamamen aynıdır (bu aynı derecede çirkin ama tüm bu karşılaştırmalardan daha az çirkin .end()).
Matteo Italia

8
Alternatif olarak, contains()fazlalık olacağı gerekçesiyle ek bir üyeye ihtiyaç duymamış olabilirler çünkü herhangi bir std::set<T> sve T tiçin sonucu, sonucuyla s.contains(t)tam olarak aynıdır static_cast<bool>(s.count(t)). Bir koşullu ifadede değeri kullanmak, onu dolaylı olarak kullanacağından bool, count()amaca yeterince iyi hizmet ettiğini düşünmüş olabilirler .
Justin Time - Monica'yı eski

2
Yazım hatası mı? if (myset.ICanHaz(element)) ...: D
Stéphane Gourichon

3
@MartinBonner Onu dışarıda bırakma nedenlerinin aptalca olması gerçekten önemli değil. Ayrıca, konuşmanın% 100 nihai mantık olup olmaması da gerçekten önemli değil. Buradaki cevabınız, nasıl olması gerektiğini düşündüğünüze dair makul bir tahmindir . Konuşma ve sadece içinde yer almayan, aynı zamanda onu önermekle görevli birinden gelen cevap (yapmasalar bile), nasıl bakarsanız bakın, tartışmasız gerçeğe bu tahminden daha yakındır. En azından en azından bu cevapta bahsetmelisiniz, bu büyük bir gelişme olur ve yapılacak sorumlu şey olur.
Jason C

2
@JasonC: Devam edip alttaki bir bölümü düzenler misiniz lütfen? Söylemek istediğiniz noktayı gerçekten anlamıyorum ve yorumlar muhtemelen bunu açıklığa kavuşturmanın en iyi yolu değil. Teşekkürler!
Martin Bonner Monica'yı

44

Yazabilmek için if (s.contains()), contains()yaptığı gibi bir bool(veya boolbaşka bir hikaye olan dönüştürülebilir bir tür) döndürmek zorundadır binary_search.

Temel nedeni tasarım kararın arkasında değil bu şekilde bunu yapmak için yani contains()bir döndüren boolediyorum eleman koleksiyonunda olduğu hakkında değerli bilgiler kaybetmek . find()bu bilgiyi bir yineleyici biçiminde korur ve döndürür, bu nedenle STL gibi genel bir kitaplık için daha iyi bir seçimdir. Bu, Alex Stepanov için her zaman yol gösterici ilke olmuştur, sık sık açıkladığı gibi (örneğin, burada ).

Genel olarak count()yaklaşıma gelince , çoğu zaman sorun olmayan bir çözüm olsa da, bununla ilgili sorun, a'nın yapması gerekenden daha fazla iş contains() yapmasıdır .

Bu, a'nın sahip bool contains()olması çok hoş ve hatta gerekli olmadığı anlamına gelmez. Bir süre önce, aynı konu hakkında ISO C ++ Standardı - Gelecek Öneriler grubunda uzun bir tartışma yaptık .


5
İlginçtir ki, bu tartışmanın arzu edilir olduğu konusunda neredeyse fikir birliği ile sona erdi ve sizden bunun için bir teklif yazmanız istendi.
PJTraill

@PJTraill True ve ileriye gitmememin nedeni contains(), açıkçası, mevcut kapsayıcılar ve algoritmalarla güçlü bir şekilde etkileşime girecek olmasıydı; bu, C ++ 17'ye gelmesi beklenen zamanda, kavramlar ve aralıklardan büyük ölçüde etkilenecek ve Birkaç özel e-posta alışverişinin yanı sıra tartışmanın bir sonucu olarak) önce onları beklemenin daha iyi bir fikir olduğuna ikna oldum. Tabii ki, 2015'te ne kavramların ne de aralıkların C ++ 17'ye girmeyeceği net değildi (aslında, yapacaklarına dair büyük umutlar vardı). Şimdi peşinden gitmeye değeceğinden emin değilim.
Leo Heinsaar

1
Çünkü std::set(sorunun sorduğu şey budur), yapılması countgerekenden daha fazla işin nasıl yapıldığını containsanlamıyorum. Glibc uygulaması count(kabaca) return find(value) == end() ? 0 : 1;. Üçlü operatörle geri dönenle != end()(optimize edicinin kaldırmasını beklediğim) arasındaki ayrıntılar dışında, daha fazla işin nasıl olduğunu göremiyorum.
Martin Bonner Monica'yı

4
"... içerir () bir bool, öğenin koleksiyonun neresinde olduğu hakkında değerli bilgileri kaybedecektir " - Eğer kullanıcı ararsa myset.contains()(eğer varsa), bu bilginin değerli olmadığının oldukça güçlü bir göstergesi olur ( bu bağlamda kullanıcıya).
Keith Thompson

1
Neden yapılması count()gerekenden daha fazla iş contains()yapıyor std::set? It eşsiz yüzden count()sadece olabilir return contains(x) ? 1 : 0;tam olarak aynı olan.
Timmmm

22

Eksik çünkü kimse eklemedi. Kimse eklemedi çünkü STL'deki kaplar, stdarayüzde minimum olacak şekilde tasarlandığı yere dahil edildi. ( std::stringAynı şekilde STL'den gelmediğini unutmayın ).

Garip bir sözdizimine aldırmazsanız, taklit edebilirsiniz:

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

kullanım:

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

Temel olarak, stdbu tekniği kullanarak çoğu C ++ türü için uzatma yöntemleri yazabilirsiniz .

Bunu yapmak çok daha mantıklı:

if (some_set.count(some_element)) {
}

ama uzatma metodu ile eğleniyorum.

Gerçekten üzücü olan şey, verimli yazmak contains, bir multimapveya üzerinde daha hızlı olabilir multiset, çünkü sadece bir öğeyi bulmaları gerekirken count, her birini bulup saymaları gerekir .

7'nin 1 milyar kopyasını içeren bir çoklu kümenin (bilirsiniz, bitmesi durumunda) gerçekten yavaş .count(7)olabilir, ancak çok hızlı olabilir contains(7).

Yukarıdaki genişletme yöntemiyle, öğeyi kullanarak lower_bound, karşılaştırarak endve sonra onu karşılaştırarak bu durum için daha hızlı hale getirebiliriz . Sırasız bir miyav için olduğu kadar düzenli bir miyav için bunu yapmak, süslü SFINAE veya konteynere özgü aşırı yüklemeler gerektirecektir.


2
1 milyar 7 kopya mı? Ve burada düşündüm std::setçiftleri içeremez ve bu nedenle std::set::counther zaman dönecektir 0ya 1.
nwp

5
@nwp std::multiset::countcan
milleniumbug

2
@nwp backticks"Set" kelimesinin etrafındaki eksikliğim, std::setözellikle bahsetmediğim için . Kendinizi daha iyi hissettirmek için çoklu ekleyeceğim
Yakk - Adam Nevraumont 21

3
Görünüşe göre "miyav" a referans olması gereken şakayı kaçırıyorum.
user2357112 Monica'yı

2
@ user2357112 meow, "ayarla veya eşle" için bir yer tutucudur. STL'ye nedenini sorun .
Yakk - Adam Nevraumont

12

Belirli bir vakaya bakıyorsunuz ve büyük resmi görmüyorsunuz. Belgelerde belirtildiği gibi AssociativeContainer konseptinin std::setgereksinimlerini karşılar . Bu kavram için ona sahip olmak bir anlam ifade etmez onun için hemen hemen hiçbir işe yaramaz olarak, yöntem ve fakat hepsi için para cezası çalışır. Yöntem olsa bir için takma ad olarak eklenebilir için , ve bunların karma sürümleri (gibi için de ), ancak kütüphane yaratıcıları gibi görünüyor bunun için gerçek gerek görmedi.containsstd::multisetstd::multimapcountcontainscountstd::setstd::maplengthsize()std::string


8
Bunun stringbir canavar olduğuna dikkat edin : STL'den önce vardı, burada lengthve indeks tabanlı tüm yöntemler vardı ve daha sonra STL modeline sığması için "kapsayıcıya alındı" ... geriye dönük uyumluluk nedenleriyle mevcut yöntemleri kaldırmadan . Bkz. GotW # 84: Monoliths Unstrung => string"minimum üye işlevi miktarı" tasarım ilkesini gerçekten ihlal ediyor.
Matthieu M.

5
Ama sonra soru, "Neden böyle bir AssociativeContainer konseptine sahip olmaya değer?" - ve geçmişte olduğundan emin değilim.
Martin Bonner Monica'yı

24
Bir çoklu kümenin, çoklu haritanın veya haritanın bir şey içerip içermediğini sormak bana çok mantıklı geliyor. Aslında, containsbir dizi / harita üzerinde çaba eşittir, ancak yapılabilir hızlı daha countbir multiset / Multimap'de.
Yakk - Adam Nevraumont

5
İçin AssociativeContainer sınıfları gerektirmez değil bir var containsyöntemini.
user2357112 Monica'yı

6
@Slava Bu demek gibidir size()ve empty()kopyalarıdır, ancak birçok kapta her ikisi de vardır.
Barry

10

Neden bilmiyorum rağmen std::sethiçbir vardır containsama counthangi sadece hiç döner 0ya 1, bir templated yazabilirsiniz containsböyle yardımcı işlevi:

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

Ve bunu şu şekilde kullanın:

    if (contains(myset, element)) ...

3
-1, çünkü bu, aslında containsyöntemin var olması gerçeğiyle doğrudan çelişiyor , sadece aptalca bir şekilde adlandırılıyor.
Matteo Italia

4
"STL rekabetin bu minimal bir arayüz sunmak için" kızılca karga std::string öksürük
bolov

6
@bolov: ne demek istiyorsun? std.::stringSTL'nin bir parçası DEĞİLDİR! Standart kitaplığın bir parçası ve geçmişe dönük olarak şablonu oluşturuldu ...
MFH

3
@MatteoItalia , kod ile paylaşılıyorsa aralığın başlangıcını ve sonunu almak için countetkili bir şekilde iki findsaniye yapması gerektiğinden daha yavaş olabilir multiset.
Mark Ransom

2
OP zaten gereksiz olduğunu biliyor, ancak görünüşe göre kodun açıkça okunmasını istiyor contains. Bunda hiçbir yanlışlık görmüyorum. @MarkRansom küçük SFINAE, bu şablonun olmaması gereken şeylere bağlanmasını engellemektir.
rustyx

7

Bunun gerçek nedeni benim için setbir gizem, ancak aynı tasarımın olası bir açıklaması, mapinsanların kazara verimsiz kod yazmasını engellemek olabilir:

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

Bu, iki maparamayla sonuçlanır .

Bunun yerine, bir yineleyici almaya zorlanırsınız. Bu size yineleyiciyi yeniden kullanmanız gerektiğine dair zihinsel bir ipucu verir:

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

yalnızca bir maparama tüketir .

Bunu anladığımızda setve mapaynı etten yapıldığımızda, bu prensibi için de uygulayabiliriz set. Yani, bir öğe üzerinde hareket etmek istiyorsak, setyalnızca içinde mevcutsa set, bu tasarım şu şekilde kod yazmamızı engelleyebilir:

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

Elbette tüm bunlar sadece bir spekülasyon.


1
Evet, ancak int kümeleri için bu geçerli değildir.
Jabberwocky

7
İnsanların if (myMap.count("Meaning of universe"))güzel yazabilmeleri dışında , yani ...?
Barry

@MichaelWalz Oops, haklısın. Cevabımı, set örneği de içerecek şekilde değiştirdim. Bununla birlikte, bir dizi şeyin mantığı benim için bir muamma.
Martin Drozdik

2
Bu doğru olamaz. Bunlar aynı kolaylıkla ile verimsiz kodu yazabilirsiniz containsolduğu gibi count.
Martin Bonner Monica'yı

2

C ++ 20'den beri,

bool contains( const Key& key ) const

gecerli.


0

Binary_search ne olacak?

 set <int> set1;
 set1.insert(10);
 set1.insert(40);
 set1.insert(30);
 if(std::binary_search(set1.begin(),set1.end(),30))
     bool found=true;

İşe yaramazdı std::unordered_set, ama işe yarardı std::set.
Jabberwocky

Bu normaldir, binary_search sadece ikili ağaçlar için çalışır.
Massimiliano Di Cavio

0

contains () bir bool döndürmelidir. C ++ 20 derleyicisini kullanarak kod için aşağıdaki çıktıyı alıyorum:

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

-1

Diğer bir neden, programcıya std :: set'in matematik küme teorisi anlamında bir küme olduğu yanlış izlenimi vermesidir. Eğer bunu uygularlarsa, pek çok soru da gelir: eğer bir std :: set bir değer için () içeriyorsa, neden başka bir set için ona sahip değil? Union (), kesişim () ve diğer küme işlemleri ve yüklemler nerede?

Cevap, elbette, küme işlemlerinden bazılarının (std :: set_union () vb.) İşlevler olarak zaten uygulandığı ve diğerlerinin de contains () kadar önemsiz bir şekilde uygulandığıdır. İşlevler ve işlev nesneleri, matematik soyutlamalarıyla nesne üyelerine göre daha iyi çalışır ve belirli bir kap türüyle sınırlı değildirler.

Tam bir matematiksel küme işlevselliği uygulamaya ihtiyaç duyulursa, yalnızca temel kapsayıcı seçimi yapmakla kalmaz, aynı zamanda uygulama ayrıntıları seçimi de vardır, örneğin, teori_union () işlevi, işlevsel programlama için daha uygun olan değişmez nesnelerle çalışır mı? veya işlenenlerini değiştirip hafızadan tasarruf eder miydi? Başlangıçtan itibaren işlev nesnesi olarak mı uygulanacak, yoksa C işlevinin uygulanması ve gerekirse std :: function <> kullanılması daha iyi olur mu?

Şu anda olduğu gibi, std :: set sadece bir kaptır, matematik anlamda kümenin uygulanması için çok uygundur, ancak std :: vector gibi teorik bir küme olmaktan neredeyse teorik bir vektör olmaktan uzaktır.

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.