Tại sao std :: set không có hàm thành viên “chứa”?


103

Tôi đang sử dụng rất nhiều std::set<int>và thường tôi chỉ cần kiểm tra xem một bộ như vậy có chứa một số hay không.

Tôi thấy thật tự nhiên khi viết:

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

Nhưng vì thiếu containsthành viên nên viết rườm rà:

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

hoặc không rõ ràng:

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

Có một lý do cho quyết định thiết kế này?


7
Hầu hết các thư viện chuẩn đều hoạt động với các trình vòng lặp nên thông thường các hàm trả về trình vòng lặp là điều bạn mong đợi. Tuy nhiên, không khó để viết một hàm để trừu tượng hóa điều đó đi. Nhiều khả năng trình biên dịch sẽ nội tuyến nó vì nó chỉ nên là một hoặc 2 dòng mã và bạn sẽ nhận được hiệu suất tương tự.
NathanOliver

3
Một vấn đề khác (cơ bản hơn) với count()cách tiếp cận là nó hoạt động nhiều hơn so với việc countains()phải làm.
Leo Heinsaar

11
Các lý do cơ bản đằng sau đó quyết định thiết kế là contains()có lợi nhuận một boolsẽ mất thông tin có giá trị về nơi nguyên tố này là trong bộ sưu tập . find()bảo toàn và trả về thông tin đó dưới dạng một trình lặp, do đó là lựa chọn tốt hơn cho một thư viện chung như STL. (Đó không phải là để nói rằng một bool contains()không phải là một rất đẹp-to-có hoặc thậm chí cần thiết, mặc dù.)
Leo Heinsaar

3
Thật dễ dàng để viết một contains(set, element)hàm miễn phí bằng cách sử dụng giao diện công khai của bộ. Do đó, giao diện của tập hợp là hoàn chỉnh về mặt chức năng; thêm một phương thức tiện lợi chỉ làm tăng giao diện mà không kích hoạt bất kỳ chức năng bổ sung nào, đó không phải là cách C ++.
Toby Speight

3
Chúng ta đang đóng tất cả mọi thứ trong những ngày này? Câu hỏi này là "Chủ yếu dựa trên ý kiến" theo cách nào?
Mr. Alien

Câu trả lời:


148

Tôi nghĩ có lẽ là do họ đang cố gắng tạo ra std::setstd::multisetcàng giống càng tốt. (Và rõ ràng là countcó một ý nghĩa hoàn toàn hợp lý cho std::multiset.)

Cá nhân tôi nghĩ rằng đây là một sai lầm.

Sẽ không tệ lắm nếu bạn giả vờ đó countchỉ là lỗi chính tả containsvà viết bài kiểm tra là:

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

Nó vẫn còn là một sự xấu hổ.


5
Ngẫu nhiên, nó hoàn toàn giống với bản đồ và đa bản đồ (xấu xí như nhau, nhưng ít xấu hơn tất cả những so sánh này với .end()).
Matteo Italia

8
Ngoài ra, họ có thể thấy không cần thêm một thành viên contains(), với lý do rằng nó sẽ là thừa vì đối với bất kỳ std::set<T> sT t, kết quả của s.contains(t)chính xác giống với kết quả của static_cast<bool>(s.count(t)). Vì việc sử dụng giá trị trong một biểu thức điều kiện sẽ ngầm chuyển nó thành bool, họ có thể cảm thấy rằng nó count()phục vụ cho mục đích đủ tốt.
Justin Time - Phục hồi Monica

2
Viết sai chính tả? if (myset.ICanHaz(element)) ...: D
Stéphane Gourichon

3
@MartinBonner Không thực sự quan trọng nếu lý do bỏ qua nó là ngu ngốc. Nó cũng không thực sự quan trọng nếu cuộc trò chuyện không phải là lý do cuối cùng 100%. Câu trả lời của bạn ở đây chỉ là một phỏng đoán hợp lý về cách bạn nghĩ nó phải như vậy. Cuộc trò chuyện và câu trả lời từ ai đó không chỉ tham gia vào nó mà còn được giao nhiệm vụ đề xuất nó (mặc dù họ không) chắc chắn gần với sự thật hơn phỏng đoán này, bất kể bạn nhìn nó như thế nào. Ở mức tối thiểu, ít nhất bạn nên đề cập đến nó trong câu trả lời này, đó sẽ là một cải tiến tuyệt vời và sẽ là điều có trách nhiệm phải làm.
Jason C

2
@JasonC: Bạn có thể tiếp tục và chỉnh sửa một phần ở dưới cùng được không? Tôi không thực sự hiểu ý bạn đang cố gắng đưa ra và nhận xét có lẽ không phải là cách tốt nhất để làm rõ điều đó. Cảm ơn!
Martin Bonner ủng hộ Monica

44

Để có thể viết if (s.contains()), contains()phải trả về một bool(hoặc một loại có thể chuyển đổi thành boolmột câu chuyện khác), giống như binary_searchvậy.

Các lý do cơ bản đằng sau những quyết định thiết kế không để làm điều đó theo cách này là contains()trả về một boolsẽ mất thông tin có giá trị về nơi nguyên tố này là trong bộ sưu tập . find()bảo toàn và trả về thông tin đó dưới dạng một trình lặp, do đó là lựa chọn tốt hơn cho một thư viện chung như STL. Đây luôn là nguyên tắc chỉ đạo cho Alex Stepanov, như anh ấy đã thường giải thích (ví dụ: ở đây ).

Đối với count()cách tiếp cận nói chung, mặc dù nó thường là một cách giải quyết ổn, nhưng vấn đề với nó là nó hoạt động nhiều hơn contains() những gì phải làm .

Điều đó không có nghĩa là một bool contains()không phải là một thứ rất đẹp để có hoặc thậm chí là cần thiết. Cách đây ít lâu, chúng ta đã có một cuộc thảo luận dài về vấn đề tương tự này trong nhóm Tiêu chuẩn ISO C ++ - Đề xuất trong tương lai.


5
Và thật thú vị khi lưu ý rằng cuộc thảo luận đó đã kết thúc với sự đồng thuận gần như mong muốn và bạn được yêu cầu viết một đề xuất cho nó.
PJTraill

@PJTraill True, và lý do tôi không tiếp tục là điều đó contains()rõ ràng sẽ tương tác mạnh mẽ với các vùng chứa và thuật toán hiện có, vốn sẽ bị tác động mạnh bởi các khái niệm và phạm vi - vào thời điểm dự kiến ​​sẽ có trong C ++ 17 - và Tôi đã bị thuyết phục (do kết quả của cuộc thảo luận cũng như một vài cuộc trao đổi email riêng tư) rằng tốt hơn hết là bạn nên đợi họ trước. Tất nhiên, vào năm 2015, không rõ ràng rằng không có khái niệm và phạm vi nào sẽ không được đưa vào C ++ 17 (trên thực tế, có nhiều hy vọng rằng chúng sẽ làm được). Tuy nhiên, tôi không chắc bây giờ nó có đáng để theo đuổi hay không.
Leo Heinsaar

1
Đối với std::set(đó là những gì câu hỏi yêu cầu về), tôi không thấy làm thế nào countđể làm việc nhiều hơn containssẽ phải làm. Việc triển khai glibc của countlà (gần đúng) return find(value) == end() ? 0 : 1;. Ngoài thông tin chi tiết về toán tử bậc ba so với chỉ quay lại != end()(mà tôi mong đợi trình tối ưu hóa sẽ loại bỏ), tôi không thể thấy cách nào còn công việc nữa.
Martin Bonner hỗ trợ Monica

4
"... contains () trả về bool sẽ làm mất thông tin có giá trị về vị trí của phần tử trong bộ sưu tập " - Nếu người dùng gọi myset.contains()(nếu nó tồn tại), đó sẽ là một dấu hiệu khá rõ ràng rằng thông tin đó không có giá trị ( cho người dùng trong ngữ cảnh đó).
Keith Thompson

1
Tại sao lại count()làm nhiều việc hơn là contains()phải làm std::set? Nó là duy nhất nên count()có thể giống return contains(x) ? 1 : 0;hệt nhau.
Timmmm

22

Nó thiếu nó bởi vì không ai thêm nó. Không ai thêm nó vì các vùng chứa từ STL mà stdthư viện đã kết hợp ở nơi được thiết kế để tối giản về giao diện. (Lưu ý rằng điều đó std::stringkhông đến từ STL theo cách tương tự).

Nếu bạn không nhớ một số cú pháp lạ, bạn có thể giả mạo nó:

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

sử dụng:

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

Về cơ bản, bạn có thể viết các phương thức mở rộng cho hầu hết các loại C ++ stdbằng cách sử dụng kỹ thuật này.

Sẽ có nhiều ý nghĩa hơn nếu chỉ làm điều này:

if (some_set.count(some_element)) {
}

nhưng tôi thích thú với phương pháp phương pháp mở rộng.

Điều thực sự đáng buồn là viết một hiệu quả containscó thể nhanh hơn trên một multimaphoặc multiset, vì họ chỉ phải tìm một phần tử, trong khi countphải tìm từng phần tử và đếm chúng .

Một bộ đa có chứa 1 tỷ bản sao của 7 (bạn biết đấy, trong trường hợp bạn hết) có thể thực sự chậm .count(7), nhưng có thể rất nhanhcontains(7) .

Với phương pháp mở rộng ở trên, chúng ta có thể thực hiện nhanh hơn cho trường hợp này bằng cách sử dụng lower_bound, so sánh với endvà sau đó so sánh với phần tử. Tuy nhiên, làm điều đó đối với meo không có thứ tự cũng như meo có thứ tự sẽ yêu cầu SFINAE ưa thích hoặc quá tải dành riêng cho thùng chứa.


2
1 tỷ bản sao của 7? Và ở đây tôi nghĩ std::setkhông thể chứa các bản sao và do đó std::set::countsẽ luôn trả về 0hoặc 1.
nwp

5
@nwp std::multiset::countcan
milleniumbug

2
@nwp Việc tôi thiếu backtickstừ "set" là vì tôi không đề cập std::setcụ thể. Để làm cho bạn cảm thấy tốt hơn, tôi sẽ thêm đa
Yakk - Adam Nevraumont

3
Tôi dường như đã bỏ lỡ trò đùa cho bất cứ điều gì "meo" được cho là tham chiếu đến.
user2357112 hỗ trợ Monica

2
@ user2357112 meo meo là một trình giữ chỗ cho "set hoặc map". Hỏi STL tại sao.
Yakk - Adam Nevraumont

12

Bạn đang xem xét trường hợp cụ thể và không nhìn thấy bức tranh lớn hơn. Như đã nêu trong tài liệu std::set đáp ứng yêu cầu của khái niệm AssociativeContainer . Đối với khái niệm đó, không có ý nghĩa gì khi có containsphương pháp, vì nó khá vô dụng đối với std::multisetstd::multimap, nhưng counthoạt động tốt cho tất cả chúng. Mặc dù phương pháp containscó thể được thêm vào như là một bí danh cho countcho std::set, std::mapvà các phiên bản băm của họ (như lengthcho size()trong std::string), nhưng có vẻ như người sáng tạo thư viện không thấy nhu cầu thực sự cho nó.


8
Lưu ý rằng đó stringlà một con quái vật: nó đã tồn tại trước STL, nơi nó có lengthvà tất cả các phương thức dựa trên chỉ mục và sau đó được "container hóa" để phù hợp với mô hình STL ... mà không xóa các phương thức hiện có vì lý do tương thích ngược . Xem GotW # 84: Monoliths Unstrung => stringthực sự vi phạm nguyên tắc thiết kế "lượng chức năng thành viên tối thiểu".
Matthieu M.

5
Nhưng sau đó câu hỏi trở thành "Tại sao nó đáng để có một khái niệm AssociativeContainer như vậy?" - và tôi không chắc đó là nhận thức muộn màng.
Martin Bonner ủng hộ Monica

24
Hỏi tôi có nhiều tập hợp, nhiều bản đồ hoặc bản đồ có chứa thứ gì đó hoàn toàn phù hợp với tôi không. Trên thực tế, containsnỗ lực ngang nhau trên một tập hợp / bản đồ, nhưng có thể được thực hiện nhanh hơn counttrên một tập hợp / đa bản đồ .
Yakk - Adam Nevraumont

5
AssociativeContainer không yêu cầu các lớp khôngcontainsphương thức.
user2357112 hỗ trợ Monica

6
@Slava Điều đó giống như nói size()empty()là bản sao, nhưng nhiều vùng chứa có cả hai.
Barry

10

Mặc dù tôi không biết tại sao std::setkhông có containsnhưng countnó chỉ trả về 0hoặc 1, bạn có thể viết một containshàm trợ giúp mẫu như thế này:

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

Và sử dụng nó như thế này:

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

3
-1, bởi vì điều này hoàn toàn mâu thuẫn với thực tế là containsphương thức thực sự tồn tại, nó chỉ được đặt tên một cách ngu ngốc.
Matteo Italia

4
"Phấn đấu STL cung cấp một giao diện tối thiểu" con qụa đỏ mỏ std::string ho
bolov

6
@bolov: quan điểm của bạn? std.::stringKHÔNG phải là một phần của STL! Nó là một phần của thư viện tiêu chuẩn và đã được tạo ra từ trước ...
MFH

3
@MatteoItalia countcó thể chậm hơn vì hiệu quả cần thực hiện hai findgiây để nhận được phần đầu và phần cuối của phạm vi nếu mã được chia sẻ với multiset.
Mark Ransom

2
OP đã biết nó là thừa, nhưng dường như muốn đọc mã một cách rõ ràng contains. Tôi thấy không có gì sai với điều đó. @MarkRansom SFINAE nhỏ là để ngăn mẫu này liên kết với những thứ không nên.
gỉyx

7

Đối setvới tôi, lý do thực sự là một bí ẩn, nhưng một lời giải thích khả dĩ cho thiết kế tương tự này mapcó thể là ngăn mọi người tình cờ viết mã kém hiệu quả:

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

Điều này sẽ dẫn đến hai map tra cứu.

Thay vào đó, bạn buộc phải có một trình lặp. Điều này cung cấp cho bạn một gợi ý tinh thần rằng bạn nên sử dụng lại trình lặp:

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

mà chỉ cần một lần maptra cứu.

Khi chúng ta nhận ra điều đó setmapđược làm từ cùng một xác thịt, chúng ta cũng có thể áp dụng nguyên tắc này cho set. Có nghĩa là, nếu chúng ta muốn thực hiện một mục setduy nhất nếu nó có trong set, thiết kế này có thể ngăn chúng ta viết mã như sau:

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

Tất nhiên tất cả những điều này chỉ là suy đoán.


1
Có, nhưng đối với các tập hợp int, điều này không áp dụng.
Jabberwocky

7
Ngoại trừ mọi người chỉ có thể viết if (myMap.count("Meaning of universe"))tốt, vì vậy ...?
Barry

@MichaelWalz Rất tiếc, bạn nói đúng. Tôi đã sửa đổi câu trả lời của mình để bao gồm cả ví dụ đã nêu. Tuy nhiên, lý do cho một tập hợp các int là một bí ẩn đối với tôi.
Martin Drozdik

2
Điều này không thể đúng. Họ có thể dễ dàng viết mã không hiệu quả của bạn với containsnhư vậy count.
Martin Bonner hỗ trợ Monica

2

Kể từ c ++ 20,

bool contains( const Key& key ) const

có sẵn.


0

Còn binary_search thì sao?

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

Nó sẽ không hiệu quả std::unordered_set, nhưng std::setnó sẽ làm được.
Jabberwocky

Nó bình thường, binary_search chỉ hoạt động cho cây nhị phân.
Massimiliano Di Cavio

0

chứa () phải trả về bool. Sử dụng trình biên dịch C ++ 20, tôi nhận được đầu ra sau cho mã:

#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

Một lý do khác là nó sẽ tạo cho một lập trình viên ấn tượng sai lầm rằng std :: set là một tập hợp theo nghĩa lý thuyết tập hợp toán học. Nếu họ triển khai điều đó, thì nhiều câu hỏi khác sẽ theo sau: nếu một std :: set có chứa () cho một giá trị, tại sao nó không có nó cho một tập hợp khác? Các phép toán và vị từ union (), giao () và các tập hợp khác ở đâu?

Tất nhiên, câu trả lời là một số hoạt động tập hợp đã được triển khai dưới dạng các hàm trong (std :: set_union (), v.v.) và các hoạt động khác được triển khai nhỏ như hàm chứa (). Các hàm và các đối tượng hàm hoạt động tốt hơn với các trừu tượng toán học so với các thành viên đối tượng và chúng không bị giới hạn ở loại vùng chứa cụ thể.

Nếu một người cần triển khai một chức năng tập hợp toán học đầy đủ, anh ta không chỉ có lựa chọn vùng chứa bên dưới mà còn có lựa chọn chi tiết triển khai, ví dụ: liệu hàm theory_union () của anh ta có hoạt động với các đối tượng không thay đổi, phù hợp hơn với lập trình hàm không , hay nó sẽ sửa đổi các toán hạng và tiết kiệm bộ nhớ? Nó sẽ được triển khai dưới dạng đối tượng hàm ngay từ đầu hay tốt hơn nên triển khai là một hàm C và sử dụng std :: function <> nếu cần?

Như hiện tại, std :: set chỉ là một vùng chứa, rất phù hợp cho việc triển khai set theo nghĩa toán học, nhưng nó gần như là một tập lý thuyết như std :: vector từ việc trở thành một vector lý thuyết.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.