เหตุใด std :: set จึงไม่มีฟังก์ชันสมาชิก“ ประกอบด้วย”


103

ฉันใช้งานหนักstd::set<int>และบ่อยครั้งฉันต้องตรวจสอบว่าชุดดังกล่าวมีตัวเลขหรือไม่

ฉันคิดว่ามันเป็นธรรมชาติที่จะเขียน:

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

แต่เนื่องจากไม่มีcontainsสมาชิกฉันจึงต้องเขียนสิ่งที่ยุ่งยาก:

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

หรือไม่ชัดเจนเท่า:

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

มีเหตุผลในการตัดสินใจออกแบบนี้หรือไม่?


7
ไลบรารีมาตรฐานส่วนใหญ่ทำงานร่วมกับตัวทำซ้ำดังนั้นโดยปกติฟังก์ชันที่ส่งคืนตัวทำซ้ำจึงเป็นสิ่งที่คุณคาดหวัง ไม่ยากที่จะเขียนฟังก์ชันให้เป็นนามธรรม เป็นไปได้มากที่คอมไพเลอร์จะอินไลน์เนื่องจากควรเป็นเพียงบรรทัดหรือ 2 ของโค้ดและคุณจะได้รับประสิทธิภาพเดียวกัน
NathanOliver

3
อีกปัญหาหนึ่ง (พื้นฐานมากกว่า) เกี่ยวกับcount()แนวทางนี้คือมันได้ผลมากกว่าที่countains()จะต้องทำ
Leo Heinsaar

11
เหตุผลพื้นฐานอยู่เบื้องหลังการตัดสินใจการออกแบบที่เป็นที่contains()ซึ่งผลตอบแทนboolจะสูญเสียข้อมูลที่มีค่าเกี่ยวกับการที่องค์ประกอบที่อยู่ในคอลเลกชัน find()เก็บรักษาและส่งคืนข้อมูลนั้นในรูปแบบของตัววนซ้ำดังนั้นจึงเป็นทางเลือกที่ดีกว่าสำหรับไลบรารีทั่วไปเช่น STL (ไม่ได้หมายความว่า a bool contains()ไม่ใช่สิ่งที่ดีที่จะมีหรือจำเป็นด้วยซ้ำ)
Leo Heinsaar

3
ง่ายต่อการเขียนcontains(set, element)ฟังก์ชันฟรีโดยใช้อินเทอร์เฟซสาธารณะของชุด ดังนั้นอินเทอร์เฟซของชุดจึงทำงานได้สมบูรณ์ การเพิ่มวิธีอำนวยความสะดวกเพียงแค่เพิ่มอินเทอร์เฟซโดยไม่ต้องเปิดใช้งานฟังก์ชันเพิ่มเติมใด ๆ ซึ่งไม่ใช่วิธี C ++
Toby Speight

3
วันนี้เราปิดทุกอย่างหรือเปล่า? คำถามนี้ "ตามความเห็นเบื้องต้น" ในข้อใด?
Mr. Alien

คำตอบ:


148

ฉันคิดว่ามันอาจจะเป็นเพราะพวกเขากำลังพยายามที่จะทำให้std::setและstd::multisetเป็นที่คล้ายกันที่เป็นไปได้ (และเห็นcountได้ชัดว่ามีความหมายที่เหมาะสมอย่างยิ่งสำหรับstd::multiset)

โดยส่วนตัวฉันคิดว่านี่เป็นความผิดพลาด

มันดูไม่แย่นักถ้าคุณแสร้งทำเป็นว่าcountเป็นเพียงการสะกดผิดcontainsและเขียนแบบทดสอบเป็น:

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

มันยังคงน่าเสียดายอยู่


5
บังเอิญมันเหมือนกันทุกประการกับแผนที่และมัลติแมป (ซึ่งน่าเกลียดพอ ๆ กัน แต่น่าเกลียดน้อยกว่าการเปรียบเทียบทั้งหมดนี้ด้วย.end())
Matteo Italia

8
อีกทางเลือกหนึ่งที่พวกเขาอาจจะได้เห็นความจำเป็นในการเป็นสมาชิกเพิ่มเติมcontains()ในบริเวณที่มันจะซ้ำซ้อนเพราะสำหรับการใด ๆstd::set<T> sและT tผลของการเป็นสิ่งที่เหมือนกันกับผลการs.contains(t) static_cast<bool>(s.count(t))เนื่องจากการใช้ค่าในนิพจน์เงื่อนไขจะส่งผลให้โดยปริยายboolพวกเขาอาจรู้สึกว่าตอบcount()สนองวัตถุประสงค์ได้ดีพอ
Justin Time - คืนสถานะ Monica

2
สะกดผิด? if (myset.ICanHaz(element)) ...: D
Stéphane Gourichon

3
@MartinBonner มันไม่สำคัญหรอกว่าเหตุผลในการทิ้งมันออกไปนั้นโง่หรือไม่ นอกจากนี้ยังไม่สำคัญว่าการสนทนาไม่ใช่เหตุผลสุดท้าย 100% คำตอบของคุณที่นี่เป็นเพียงการคาดเดาที่สมเหตุสมผลว่าคุณคิดว่ามันควรจะเป็นอย่างไร การสนทนาและคำตอบจากใครบางคนไม่เพียง แต่เกี่ยวข้องกับเรื่องนี้เท่านั้น แต่ยังได้รับมอบหมายให้เสนอ (แม้ว่าพวกเขาจะไม่ได้ทำ) นั้นใกล้เคียงกับความจริงมากกว่าการคาดเดานี้อย่างเหลือเชื่อไม่ว่าคุณจะมองอย่างไร ที่ขั้นต่ำเปลือยคุณที่ควรน้อยพูดถึงมันในคำตอบนี้ว่าจะมีการพัฒนาที่ดีและจะเป็นสิ่งที่ผู้รับผิดชอบที่จะทำ
Jason C

2
@JasonC: คุณช่วยแก้ไขส่วนที่ด้านล่างได้ไหม? ฉันไม่เข้าใจประเด็นที่คุณพยายามทำและความคิดเห็นอาจไม่ใช่วิธีที่ดีที่สุดในการชี้แจง ขอบคุณ!
Martin Bonner สนับสนุน Monica

44

เพื่อให้สามารถเขียนif (s.contains()), contains()มีการส่งกลับbool(หรือชนิดที่จะแปลงสภาพboolซึ่งเป็นอีกเรื่องหนึ่ง) เช่นbinary_searchไม่

เหตุผลพื้นฐานที่อยู่เบื้องหลังการตัดสินใจการออกแบบไม่ได้ที่จะทำมันด้วยวิธีนี้คือว่าcontains()ที่ส่งกลับboolจะสูญเสียข้อมูลที่มีค่าเกี่ยวกับการที่องค์ประกอบที่อยู่ในคอลเลกชัน find()เก็บรักษาและส่งคืนข้อมูลนั้นในรูปแบบของตัววนซ้ำดังนั้นจึงเป็นทางเลือกที่ดีกว่าสำหรับไลบรารีทั่วไปเช่น STL นี่เป็นหลักการชี้นำของ Alex Stepanov มาโดยตลอดอย่างที่เขาอธิบายบ่อยๆ (ตัวอย่างเช่นที่นี่ )

สำหรับcount()แนวทางโดยทั่วไปแม้ว่ามักจะเป็นวิธีแก้ปัญหาที่ถูกต้อง แต่ปัญหาก็คือมันทำงานได้ดีกว่า contains() จะต้องทำ

นั่นไม่ได้หมายความว่า a bool contains()ไม่ใช่สิ่งที่ดีที่จะมีหรือจำเป็นด้วยซ้ำ เมื่อไม่นานมานี้เราได้มีการอภิปรายเกี่ยวกับปัญหาเดียวกันนี้ในกลุ่ม ISO C ++ Standard - Future Proposals


5
และเป็นที่น่าสนใจที่จะทราบว่าการอภิปรายนั้นจบลงด้วยความเห็นพ้องต้องกันว่าเป็นที่พึงปรารถนาและคุณถูกขอให้เขียนข้อเสนอ
PJTraill

@PJTraill True และเหตุผลที่ฉันไม่ก้าวไปข้างหน้าคือcontains()เห็นได้ชัดว่าจะโต้ตอบอย่างรุนแรงกับคอนเทนเนอร์และอัลกอริทึมที่มีอยู่ซึ่งจะได้รับผลกระทบอย่างมากจากแนวคิดและช่วง - ในเวลาที่คาดว่าจะเข้าสู่ C ++ 17 - และ ฉันเชื่อมั่น (อันเป็นผลมาจากการสนทนาและการแลกเปลี่ยนอีเมลส่วนตัวสองสามรายการ) ว่าควรรอก่อน แน่นอนว่าในปี 2015 ยังไม่ชัดเจนว่าทั้งแนวคิดและช่วงจะไม่ทำให้มันเป็น C ++ 17 (อันที่จริงมีความหวังสูงที่จะเป็นเช่นนั้น) ตอนนี้ฉันไม่แน่ใจว่ามันคุ้มค่าที่จะไล่ตาม
Leo Heinsaar

1
สำหรับstd::set(ซึ่งเป็นคำถามที่ถามเกี่ยวกับ) ฉันไม่เห็นว่าcountจะได้ผลมากกว่าที่containsจะต้องทำอย่างไร การดำเนินงานของ glibc countมี return find(value) == end() ? 0 : 1;(ประมาณ) นอกเหนือจากรายละเอียดเกี่ยวกับตัวดำเนินการ ternary เทียบกับเพิ่งกลับมา!= end()(ซึ่งฉันคาดว่าเครื่องมือเพิ่มประสิทธิภาพจะลบออก) ฉันไม่เห็นว่ามีการทำงานอย่างไรอีก
Martin Bonner สนับสนุน Monica

4
"... มี () ซึ่งส่งคืนบูลจะสูญเสียข้อมูลที่มีค่าเกี่ยวกับตำแหน่งขององค์ประกอบในคอลเล็กชัน " - หากผู้ใช้เรียกmyset.contains()(ถ้ามี) นั่นจะเป็นข้อบ่งชี้ที่ชัดเจนว่าข้อมูลนั้นไม่มีค่า ( ให้กับผู้ใช้ในบริบทนั้น)
Keith Thompson

1
ทำไมcount()ทำงานมากกว่าที่contains()จะต้องทำเพื่อstd::set? มันไม่เหมือนใครซึ่งcount()อาจจะreturn contains(x) ? 1 : 0;เหมือนกันทุกประการ
Timmmm

22

มันขาดเพราะไม่มีใครเพิ่ม ไม่มีใครเพิ่มเนื่องจากคอนเทนเนอร์จาก STL ที่stdไลบรารีรวมไว้ซึ่งออกแบบมาให้มีส่วนต่อประสานน้อยที่สุด (สังเกตว่าstd::stringว่าไม่ได้มาจาก STL ในลักษณะเดียวกัน)

หากคุณไม่สนใจไวยากรณ์แปลก ๆ คุณสามารถปลอมได้:

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

ใช้:

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

โดยทั่วไปคุณสามารถเขียนวิธีการขยายสำหรับ C ++ ส่วนใหญ่ได้ stdประเภทโดยใช้เทคนิคนี้

มันสมเหตุสมผลกว่าที่จะทำสิ่งนี้:

if (some_set.count(some_element)) {
}

แต่ฉันรู้สึกสนุกกับวิธีการขยาย

สิ่งที่น่าเศร้าจริงๆคือการเขียนประสิทธิภาพcontainsอาจเร็วขึ้นบน a multimapหรือmultisetเพียงแค่ต้องหาองค์ประกอบเดียวในขณะcountที่ต้องหาแต่ละองค์ประกอบและนับและนับพวกเขา

ชุดหลายชุดที่มี 1 พันล้านชุดของ 7 (คุณรู้ไหมว่าในกรณีที่คุณหมด) อาจมีช้า.count(7)มาก แต่อาจมีเร็วมากcontains(7)แต่อาจมีความรวดเร็ว

ด้วยวิธีการขยายข้างต้นเราสามารถทำให้เร็วขึ้นสำหรับกรณีนี้โดยใช้lower_boundเปรียบเทียบกับendแล้วเปรียบเทียบกับองค์ประกอบ อย่างไรก็ตามการทำเช่นนั้นสำหรับแมวที่ไม่มีการเรียงลำดับเช่นเดียวกับแมวที่ได้รับคำสั่งจะต้องใช้ SFINAE แบบแฟนซีหรือการโอเวอร์โหลดเฉพาะคอนเทนเนอร์


2
1 พันล้านเล่ม 7? และที่นี่ผมคิดว่าstd::setไม่สามารถมีรายการที่ซ้ำกันและดังนั้นจึงstd::set::countมักจะกลับหรือ0 1
nwp

5
@nwp std::multiset::countcan
milleniumbug

2
@nwp ฉันขาดbackticksคำว่า "set" เป็นเพราะฉันไม่ได้หมายถึงstd::setโดยเฉพาะ เพื่อให้คุณรู้สึกดีขึ้นฉันจะเพิ่ม multi
Yakk - Adam Nevraumont

3
ดูเหมือนว่าฉันจะพลาดเรื่องตลกที่ "เหมียว" ควรจะอ้างอิงถึง
user2357112 รองรับ Monica

2
@ user2357112 meow เป็นตัวยึดสำหรับ "set or map" ถามSTLว่าทำไม
Yakk - Adam Nevraumont

12

คุณกำลังพิจารณาในบางกรณีและไม่เห็นภาพที่ใหญ่ขึ้น ตามที่ระบุไว้ในเอกสาร std::setเป็นไปตามข้อกำหนดของแนวคิดAssociativeContainer สำหรับแนวคิดนั้นมันไม่สมเหตุสมผลเลยที่จะมีcontainsวิธีการเพราะมันค่อนข้างไร้ประโยชน์สำหรับstd::multisetและstd::multimapแต่ก็countใช้ได้ดีสำหรับพวกเขาทั้งหมด แม้ว่าวิธีการcontainsอาจจะเพิ่มเป็นชื่อแทนสำหรับcountสำหรับstd::set, std::mapและรุ่นแฮชของพวกเขา (เช่นlengthสำหรับsize()ในstd::string) แต่ดูเหมือนว่าผู้สร้างห้องสมุดไม่เห็นความจำเป็นที่แท้จริงสำหรับมัน


8
โปรดทราบว่าstringเป็นสัตว์ประหลาด: มีอยู่ก่อน STL ซึ่งมีlengthและวิธีการทั้งหมดที่อิงดัชนีจากนั้นได้รับการ "containerified" เพื่อให้พอดีกับรูปแบบ STL ... . ดูGotW # 84: Monoliths Unstrung => stringละเมิดหลักการออกแบบ "จำนวนฟังก์ชันขั้นต่ำของสมาชิก" จริงๆ
Matthieu M.

5
แต่แล้วคำถามก็กลายเป็น "เหตุใดจึงควรมีแนวคิด AssociativeContainer เช่นนั้น" - และฉันไม่แน่ใจว่ามันถูกมองข้ามไป
Martin Bonner สนับสนุน Monica

24
การถามว่ามัลติเซ็ตมัลติแมปหรือแผนที่มีอะไรที่เหมาะสมกับฉันไหม ในความเป็นจริงcontainsคือความเท่าเทียมกันในความพยายามในชุด / แผนที่ แต่สามารถทำได้เร็วขึ้นกว่าcountใน MultiSet / Multimap
Yakk - Adam Nevraumont

5
AssociativeContainer ไม่จำเป็นต้องเรียนที่จะไม่ได้มีcontainsวิธีการ
user2357112 รองรับ Monica

6
@Slava นั่นเหมือนกับการพูดsize()และempty()ซ้ำกัน แต่หลายคอนเทนเนอร์มีทั้งสองอย่าง
Barry

10

แม้ว่าฉันไม่รู้ว่าทำไมstd::setไม่มีcontainsแต่countสิ่งที่เคยส่งกลับมาเท่านั้น0หรือ1คุณสามารถเขียนcontainsฟังก์ชันตัวช่วยเทมเพลตได้ดังนี้:

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

และใช้มันดังนี้:

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

3
-1 เนื่องจากสิ่งนี้ขัดแย้งกันอย่างตรงไปตรงมาโดยข้อเท็จจริงที่ว่าในความเป็นจริงcontainsวิธีนี้มีอยู่จริงมันเป็นเพียงการตั้งชื่อแบบโง่ ๆ
Matteo Italia

4
"มุ่งมั่นที่จะนำเสนอ STL อินเตอร์เฟซที่น้อยที่สุด" Chough std::string ไอ
bolov

6
@bolov: จุดของคุณ? std.::stringไม่ได้เป็นส่วนหนึ่งของ STL! เป็นส่วนหนึ่งของไลบรารีมาตรฐานและมีการสร้างเทมเพลตย้อนหลัง ...
MFH

3
@MatteoItalia countสามารถจะช้าเพราะมันได้อย่างมีประสิทธิภาพต้องทำทั้งสองที่จะได้รับการเริ่มต้นและสิ้นสุดของช่วงถ้ารหัสร่วมกันกับfind multiset
Mark Ransom

2
สหกรณ์รู้อยู่แล้วว่ามันจะซ้ำซ้อน containsแต่เห็นได้ชัดว่าต้องการรหัสอย่างชัดเจนอ่าน ฉันไม่เห็นอะไรผิดปกติกับสิ่งนั้น @MarkRansom SFINAE ตัวน้อยคือการป้องกันไม่ให้แม่แบบนี้ผูกกับสิ่งที่ไม่ควร
rustyx

7

เหตุผลที่แท้จริงsetเป็นเรื่องลึกลับสำหรับฉัน แต่คำอธิบายที่เป็นไปได้อย่างหนึ่งสำหรับการออกแบบเดียวกันนี้mapอาจเป็นการป้องกันไม่ให้ผู้คนเขียนโค้ดที่ไม่มีประสิทธิภาพโดยบังเอิญ:

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

ซึ่งจะทำให้เกิดการmapค้นหาสองครั้ง

แต่คุณถูกบังคับให้รับตัววนซ้ำ สิ่งนี้ช่วยให้คุณมีคำใบ้ว่าคุณควรใช้ตัววนซ้ำ:

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

ซึ่งใช้การmapค้นหาเพียงครั้งเดียว

เมื่อเราตระหนักถึงสิ่งนั้นsetและmapสร้างขึ้นจากเนื้อเดียวกันเราก็สามารถนำหลักการนี้ไปsetใช้ได้เช่นกัน นั่นคือหากเราต้องการดำเนินการกับรายการในsetกรณีที่มีอยู่ในการsetออกแบบนี้เท่านั้นการออกแบบนี้สามารถป้องกันไม่ให้เราเขียนโค้ดได้ดังนี้:

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

แน่นอนว่าทั้งหมดนี้เป็นเพียงการคาดเดาเท่านั้น


1
ใช่ แต่สำหรับชุดของ ints จะใช้ไม่ได้
Jabberwocky

7
ยกเว้นคนเขียนได้if (myMap.count("Meaning of universe"))ดีงั้นเหรอ ... ?
Barry

@MichaelWalz อ๊ะคุณพูดถูก ฉันแก้ไขคำตอบของฉันเพื่อรวมตัวอย่างชุดด้วย อย่างไรก็ตามการหาเหตุผลของชุด ints เป็นเรื่องลึกลับสำหรับฉัน
Martin Drozdik

2
สิ่งนี้ไม่สามารถถูกต้องได้ พวกเขาสามารถได้อย่างง่ายดายเพียงเขียนโค้ดที่ไม่มีประสิทธิภาพของคุณด้วยเช่นเดียวกับcontains count
Martin Bonner สนับสนุน Monica


0

แล้ว 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;

มันจะไม่ได้ผลstd::unordered_setแต่สำหรับstd::setมันจะ
Jabberwocky

เป็นเรื่องปกติ binary_search ใช้กับต้นไม้ไบนารีเท่านั้น
Massimiliano Di Cavio

0

มี () ต้องส่งคืนบูล การใช้คอมไพเลอร์ C ++ 20 ฉันได้รับผลลัพธ์ต่อไปนี้สำหรับโค้ด:

#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

อีกเหตุผลหนึ่งคือมันจะทำให้โปรแกรมเมอร์เข้าใจผิดว่า std :: set คือเซตในความหมายของทฤษฎีเซตคณิตศาสตร์ หากพวกเขาใช้สิ่งนั้นจะมีคำถามอื่น ๆ ตามมา: ถ้าชุด std :: มี () สำหรับค่าทำไมมันถึงไม่มีสำหรับชุดอื่น ยูเนี่ยน (), จุดตัด () และการดำเนินการเซ็ตและเพรดิเคตอื่น ๆ อยู่ที่ไหน

คำตอบคือแน่นอนว่าการดำเนินการเซ็ตบางส่วนถูกนำไปใช้แล้วเป็นฟังก์ชันใน (std :: set_union () เป็นต้น) และอื่น ๆ จะถูกนำไปใช้งานเล็กน้อยตามที่มี () ฟังก์ชันและอ็อบเจกต์ฟังก์ชันทำงานร่วมกับนามธรรมทางคณิตศาสตร์ได้ดีกว่าสมาชิกอ็อบเจ็กต์และไม่ จำกัด เฉพาะคอนเทนเนอร์บางประเภท

หากจำเป็นต้องใช้ฟังก์ชันชุดคณิตศาสตร์เต็มรูปแบบเขาไม่เพียงมีตัวเลือกคอนเทนเนอร์พื้นฐานเท่านั้น แต่เขายังมีรายละเอียดการนำไปใช้งานอีกด้วยเช่นฟังก์ชัน theory_union () ของเขาจะทำงานกับวัตถุที่ไม่เปลี่ยนรูปได้หรือไม่ซึ่งเหมาะสำหรับการเขียนโปรแกรมเชิงฟังก์ชัน หรือจะแก้ไขตัวถูกดำเนินการและบันทึกหน่วยความจำ? มันจะถูกนำไปใช้เป็นวัตถุฟังก์ชันตั้งแต่เริ่มต้นหรือจะเป็นการดีกว่าที่จะใช้เป็นฟังก์ชัน C และใช้ std :: function <> ถ้าจำเป็น?

อย่างที่เป็นอยู่ตอนนี้ std :: set เป็นเพียงคอนเทนเนอร์ซึ่งเหมาะอย่างยิ่งสำหรับการนำ set ไปใช้ในความหมายทางคณิตศาสตร์ แต่มันเกือบจะห่างไกลจากการเป็นเซตทางทฤษฎีเช่น std :: vector จากการเป็นเวกเตอร์เชิงทฤษฎี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.