จะหาจุดตัดของสอง std :: set ใน C ++ ได้อย่างไร?


93

ฉันพยายามหาจุดตัดระหว่างสอง std :: set ใน C ++ แต่ฉันได้รับข้อผิดพลาดเรื่อย ๆ

ฉันสร้างการทดสอบตัวอย่างเล็กน้อยสำหรับสิ่งนี้

#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;

int main() {
  set<int> s1;
  set<int> s2;

  s1.insert(1);
  s1.insert(2);
  s1.insert(3);
  s1.insert(4);

  s2.insert(1);
  s2.insert(6);
  s2.insert(3);
  s2.insert(0);

  set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end());
  return 0;
}

โปรแกรมหลังไม่สร้างเอาต์พุตใด ๆ แต่ฉันคาดว่าจะมีชุดใหม่ (ขอเรียกว่าs3) โดยมีค่าต่อไปนี้:

s3 = [ 1 , 3 ]

แต่ฉันได้รับข้อผิดพลาด:

test.cpp: In function ‘int main()’:
test.cpp:19: error: no matching function for call to ‘set_intersection(std::_Rb_tree_const_iterator<int>, std::_Rb_tree_const_iterator<int>, std::_Rb_tree_const_iterator<int>, std::_Rb_tree_const_iterator<int>)

สิ่งที่ฉันเข้าใจจากข้อผิดพลาดนี้คือไม่มีคำจำกัดความset_intersectionที่ยอมรับRb_tree_const_iterator<int>เป็นพารามิเตอร์

นอกจากนี้ฉันคิดว่าstd::set.begin()วิธีการส่งคืนวัตถุประเภทดังกล่าว

มีวิธีที่ดีกว่าในการหาจุดตัดของสองstd::setใน C ++ หรือไม่? ควรเป็นฟังก์ชันในตัวหรือไม่?

ขอบคุณมาก!


"ฉันคาดว่าจะมีชุดใหม่ (ขอเรียกว่า s3)"แต่คุณทำไม่ได้และคุณไม่ได้ทำ ฉันไม่เข้าใจว่าคุณคาดหวังว่าผลลัพธ์จะไปที่ใด นอกจากนี้คุณยังไม่ได้อ่านเอกสารเพื่อค้นหาว่าข้อโต้แย้งใดที่จะส่งผ่าน
Lightness Races ใน Orbit

คำตอบ:


113

คุณไม่ได้จัดเตรียมตัววนซ้ำเอาต์พุตสำหรับset_int จุดตัด

template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator set_intersection ( InputIterator1 first1, InputIterator1 last1,
                                InputIterator2 first2, InputIterator2 last2,
                                OutputIterator result );

แก้ไขปัญหานี้โดยทำสิ่งที่ต้องการ

...;
set<int> intersect;
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(),
                  std::inserter(intersect,intersect.begin()));

คุณต้องมีตัวทำstd::insertซ้ำเนื่องจากชุดนี้ว่างเปล่า เราไม่สามารถใช้ back_ หรือ front_inserter ได้เนื่องจาก set ไม่รองรับการทำงานเหล่านั้น


72
ฉันอยากจะเข้าใจว่าเหตุใดการดำเนินการพื้นฐานในเซตจึงต้องใช้การร่ายเวทย์ที่ละเอียดอ่อนเช่นนี้ ทำไมไม่ใช้set<T>& set::isect(set<T>&)วิธีง่ายๆที่จำเป็น? (ฉันขอset<T>& set::operator^(set<T>&)แต่นั่นน่าจะเป็นสะพานที่ไกลเกินไป)
Ryan V. Bissell

3
@ RyanV Bissell นี่เป็นการออกแบบที่คล้ายกับอัลกอริทึมเกือบทั้งหมดในความ<algorithm>สอดคล้องกันหากไม่มีอะไรอื่น ผมถือว่าสไตล์นี้ช่วยให้คุณมีความยืดหยุ่น และอนุญาตให้ใช้ algos กับคอนเทนเนอร์ต่างๆได้แม้ว่าจะไม่เกิดขึ้นที่นี่ .. ลายเซ็นของคุณอาจใช้ไม่ได้ด้วยคุณอาจต้องส่งคืนค่า และในสมัยก่อนความหมายของการคัดลอกจะเป็นสำเนาสองครั้งที่ฉันคิด ฉันไม่ได้ทำ c ++ มาระยะหนึ่งแล้วดังนั้นให้ใช้สิ่งนี้ด้วยการหยิกหรือ 3 ของเกลือ
Karthik T

4
ฉันยังคงคิดว่าตัวเองเป็นมือใหม่ STL ดังนั้นการใช้เกลือเม็ดก็ใช้ได้เช่นกัน หน้าต่างแก้ไขความคิดเห็นของฉันหมดอายุแล้วดังนั้นฉันจึงไม่สามารถแก้ไข faux-pas ที่ส่งคืนโดยอ้างอิงได้ ความคิดเห็นของฉันไม่ใช่การร้องเรียนเกี่ยวกับความสม่ำเสมอ แต่เป็นคำถามที่ตรงไปตรงมาว่าทำไมไวยากรณ์นี้จึงต้องมีรสขม บางทีฉันควรตั้งคำถามนี้ขึ้นมา
Ryan V.Bissell

3
จริงๆแล้ว C ++ std lib ส่วนใหญ่ได้รับการออกแบบมาในลักษณะที่คลุมเครือนี้ ในขณะที่ความสง่างามของการออกแบบนั้นชัดเจน (wrt genericity แต่ไม่เพียงเท่านั้น) ความซับซ้อนของ API ก็มีผลกระทบร้ายแรง (ส่วนใหญ่เป็นเพราะผู้คนยังคงคิดค้นล้อใหม่เนื่องจากไม่สามารถใช้ล้อที่มาพร้อมกับคอมไพเลอร์ได้) ในอีกโลกหนึ่งนักออกแบบน่าจะถูกตบเพราะชอบความสุขของผู้ใช้ ในโลกนี้ ... อย่างน้อยเราก็มี StackOverflow

3
นี่คือ "ไวยากรณ์ทั่วไป" - คุณยังสามารถ set_int Intersection บนเวกเตอร์และในรายการและเก็บผลลัพธ์ไว้ใน deque ได้และคุณควรจะทำสิ่งนี้ได้อย่างมีประสิทธิภาพ (แน่นอนว่าเป็นปัญหาของคุณที่ต้องดูแลทั้งสองอย่าง คอนเทนเนอร์ต้นทางจะถูกจัดเรียงก่อนที่จะเรียกสิ่งนี้) ฉันไม่คิดว่ามันไม่ดีสิ่งเดียวที่ฉันมีปัญหาคืออาจมีวิธีการของsetคอนเทนเนอร์ที่ตัดกับชุดอื่น หัวข้อการส่งผ่านคอนเทนเนอร์แทนที่จะเป็น.begin()- .end()เป็นอีกสิ่งหนึ่ง - สิ่งนี้จะได้รับการแก้ไขเมื่อ C ++ มีแนวคิด
Ethouris

25

ดูตัวอย่างในลิงค์: http://en.cppreference.com/w/cpp/algorithm/set_intarison

คุณต้องมีคอนเทนเนอร์อื่นเพื่อจัดเก็บข้อมูลจุดตัดด้านล่างโค้ดที่ใช้งานได้:

std::vector<int> common_data;
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(), std::back_inserter(common_data));

6
back_inserterไม่สามารถใช้งานได้setเนื่องจากsetไม่มีpush_backฟังก์ชัน
Jack Aidley

6

ดูมาตรฐาน :: set_intersection คุณต้องเพิ่มตัววนซ้ำเอาต์พุตซึ่งคุณจะเก็บผลลัพธ์:

#include <iterator>
std::vector<int> s3;
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(), std::back_inserter(s3));

ดูIdeoneสำหรับรายชื่อทั้งหมด


3
โปรดทราบว่า back_inserter จะไม่ทำงานหากคุณต้องการให้ผลลัพธ์เป็นชุดคุณต้องใช้ std :: inserter เหมือนที่ Karthik ใช้
Joseph Garvin

4

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

template <class InputIterator1, class InputIterator2, class OutputIterator>
  OutputIterator set_intersection (InputIterator1 first1, InputIterator1 last1,
                                   InputIterator2 first2, InputIterator2 last2,
                                   OutputIterator result)
{
  while (first1!=last1 && first2!=last2)
  {
    if (*first1<*first2) ++first1;
    else if (*first2<*first1) ++first2;
    else {
      *result = *first1;
      ++result; ++first1; ++first2;
    }
  }
  return result;
}

คัดลอกมาจากhttp://www.cplusplus.com/reference/algorithm/set_intarison/

ตัวอย่างเช่นหากเอาต์พุตของคุณเป็นชุดคุณสามารถ output.insert (* first1) นอกจากนี้ฟังก์ชันของคุณอาจไม่ได้รับการเทมเพลตหากคุณเขียนโค้ดได้สั้นกว่าการใช้ฟังก์ชัน std set_int Intersection ให้ดำเนินการต่อ

หากคุณต้องการรวมสองชุดคุณสามารถ setA.insert (setB.begin (), setB.end ()); วิธีนี้ง่ายกว่าวิธี set_union มาก อย่างไรก็ตามสิ่งนี้จะใช้ไม่ได้กับเวกเตอร์


4

ความคิดเห็นแรก (ได้รับการโหวตอย่างดี) ของคำตอบที่ยอมรับบ่นเกี่ยวกับตัวดำเนินการที่ขาดหายไปสำหรับการดำเนินการชุดมาตรฐานที่มีอยู่

ในแง่หนึ่งฉันเข้าใจว่าไม่มีตัวดำเนินการดังกล่าวในไลบรารีมาตรฐาน ในทางกลับกันมันเป็นเรื่องง่ายที่จะเพิ่ม (เพื่อความสุขส่วนตัว) หากต้องการ ฉันทำงานหนักเกินไป

  • operator *() สำหรับการตัดกันของชุด
  • operator +() สำหรับการรวมกันของชุด

ตัวอย่างtest-set-ops.cc:

#include <algorithm>
#include <iterator>
#include <set>

template <class T, class CMP = std::less<T>, class ALLOC = std::allocator<T> >
std::set<T, CMP, ALLOC> operator * (
  const std::set<T, CMP, ALLOC> &s1, const std::set<T, CMP, ALLOC> &s2)
{
  std::set<T, CMP, ALLOC> s;
  std::set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(),
    std::inserter(s, s.begin()));
  return s;
}

template <class T, class CMP = std::less<T>, class ALLOC = std::allocator<T> >
std::set<T, CMP, ALLOC> operator + (
  const std::set<T, CMP, ALLOC> &s1, const std::set<T, CMP, ALLOC> &s2)
{
  std::set<T, CMP, ALLOC> s;
  std::set_union(s1.begin(), s1.end(), s2.begin(), s2.end(),
    std::inserter(s, s.begin()));
  return s;
}

// sample code to check them out:

#include <iostream>

using namespace std;

template <class T>
ostream& operator << (ostream &out, const set<T> &values)
{
  const char *sep = " ";
  for (const T &value : values) {
    out << sep << value; sep = ", ";
  }
  return out;
}

int main()
{
  set<int> s1 { 1, 2, 3, 4 };
  cout << "s1: {" << s1 << " }" << endl;
  set<int> s2 { 0, 1, 3, 6 };
  cout << "s2: {" << s2 << " }" << endl;
  cout << "I: {" << s1 * s2 << " }" << endl;
  cout << "U: {" << s1 + s2 << " }" << endl;
  return 0;
}

รวบรวมและทดสอบ:

$ g++ -std=c++11 -o test-set-ops test-set-ops.cc 

$ ./test-set-ops     
s1: { 1, 2, 3, 4 }
s2: { 0, 1, 3, 6 }
I: { 1, 3 }
U: { 0, 1, 2, 3, 4, 6 }

$ 

สิ่งที่ฉันไม่ชอบคือสำเนาของค่าส่งคืนในตัวดำเนินการ อาจเป็นไปได้ว่าสิ่งนี้สามารถแก้ไขได้โดยใช้การมอบหมายการย้าย แต่ก็ยังเกินทักษะของฉัน

เนื่องจากความรู้ที่ จำกัด ของฉันเกี่ยวกับความหมายของการย้าย "แฟนซีใหม่" เหล่านี้ฉันจึงกังวลเกี่ยวกับการส่งคืนตัวดำเนินการซึ่งอาจทำให้เกิดสำเนาของชุดที่ส่งคืน Olaf Dietscheชี้ให้เห็นว่าข้อกังวลเหล่านี้ไม่จำเป็นเนื่องจากstd::setมีตัวสร้าง / มอบหมายการเคลื่อนย้ายอยู่แล้ว

แม้ว่าฉันจะเชื่อเขา แต่ฉันก็คิดว่าจะตรวจสอบสิ่งนี้อย่างไร (สำหรับบางอย่างเช่น "การเชื่อมั่นในตนเอง") จริงๆแล้วมันค่อนข้างง่าย เนื่องจากต้องมีเทมเพลตในซอร์สโค้ดคุณสามารถทำตามขั้นตอนด้วยโปรแกรมดีบั๊ก ดังนั้นฉันจึงวางจุดพักไว้ที่ด้านขวาreturn s;ของoperator *()และดำเนินการต่อด้วยขั้นตอนเดียวซึ่งนำฉันไปสู่std::set::set(_myt&& _Right): et voilà - ตัวสร้างการย้าย ขอบคุณโอลาฟสำหรับการรู้แจ้ง (ของฉัน)

เพื่อความสมบูรณ์ฉันใช้ตัวดำเนินการมอบหมายที่เกี่ยวข้องด้วย

  • operator *=() สำหรับจุดตัดของชุด "ทำลายล้าง"
  • operator +=() สำหรับการรวมกลุ่มของชุด "ทำลายล้าง"

ตัวอย่างtest-set-assign-ops.cc:

#include <iterator>
#include <set>

template <class T, class CMP = std::less<T>, class ALLOC = std::allocator<T> >
std::set<T, CMP, ALLOC>& operator *= (
  std::set<T, CMP, ALLOC> &s1, const std::set<T, CMP, ALLOC> &s2)
{
  auto iter1 = s1.begin();
  for (auto iter2 = s2.begin(); iter1 != s1.end() && iter2 != s2.end();) {
    if (*iter1 < *iter2) iter1 = s1.erase(iter1);
    else {
      if (!(*iter2 < *iter1)) ++iter1;
      ++iter2;
    }
  }
  while (iter1 != s1.end()) iter1 = s1.erase(iter1);
  return s1;
}

template <class T, class CMP = std::less<T>, class ALLOC = std::allocator<T> >
std::set<T, CMP, ALLOC>& operator += (
  std::set<T, CMP, ALLOC> &s1, const std::set<T, CMP, ALLOC> &s2)
{
  s1.insert(s2.begin(), s2.end());
  return s1;
}

// sample code to check them out:

#include <iostream>

using namespace std;

template <class T>
ostream& operator << (ostream &out, const set<T> &values)
{
  const char *sep = " ";
  for (const T &value : values) {
    out << sep << value; sep = ", ";
  }
  return out;
}

int main()
{
  set<int> s1 { 1, 2, 3, 4 };
  cout << "s1: {" << s1 << " }" << endl;
  set<int> s2 { 0, 1, 3, 6 };
  cout << "s2: {" << s2 << " }" << endl;
  set<int> s1I = s1;
  s1I *= s2;
  cout << "s1I: {" << s1I << " }" << endl;
  set<int> s2I = s2;
  s2I *= s1;
  cout << "s2I: {" << s2I << " }" << endl;
  set<int> s1U = s1;
  s1U += s2;
  cout << "s1U: {" << s1U << " }" << endl;
  set<int> s2U = s2;
  s2U += s1;
  cout << "s2U: {" << s2U << " }" << endl;
  return 0;
}

รวบรวมและทดสอบ:

$ g++ -std=c++11 -o test-set-assign-ops test-set-assign-ops.cc 

$ ./test-set-assign-ops
s1: { 1, 2, 3, 4 }
s2: { 0, 1, 3, 6 }
s1I: { 1, 3 }
s2I: { 1, 3 }
s1U: { 0, 1, 2, 3, 4, 6 }
s2U: { 0, 1, 2, 3, 4, 6 }

$

1
std::setได้ใช้ตัวสร้างการเคลื่อนย้ายและตัวดำเนินการมอบหมายที่จำเป็นอยู่แล้วดังนั้นไม่ต้องกังวลเรื่องนี้ นอกจากนี้คอมไพเลอร์ส่วนใหญ่ยังใช้การเพิ่มประสิทธิภาพการคืนค่า
Olaf Dietsche

@OlafDietsche ขอบคุณสำหรับความคิดเห็นของคุณ ฉันตรวจสอบสิ่งนี้และปรับปรุงคำตอบตามลำดับ เกี่ยวกับ RVO ฉันได้พูดคุยกับเพื่อนร่วมงานของฉันแล้วจนกระทั่งฉันแสดงให้พวกเขาเห็นในดีบักเกอร์ของ VS2013 ว่ามันจะไม่เกิดขึ้น (อย่างน้อยก็ในแพลตฟอร์ม devel. ของเรา) จริงๆแล้วมันไม่สำคัญเลยยกเว้นว่าโค้ดนั้นมีความสำคัญต่อประสิทธิภาพ ในกรณีหลังนี้ฉันเก็บไว้ตอนนี้ไม่ต้องพึ่งพา RVO (นั่นไม่ใช่เรื่องยากใน C ++ ... )
Scheff

@Scheff well Scheff (ไม่ใช่ Bose) คำอธิบายที่ดี
JeJo

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