làm thế nào để tìm giao của hai std :: set trong C ++?


93

Tôi đã cố gắng tìm điểm giao nhau giữa hai std :: set trong C ++, nhưng tôi vẫn gặp lỗi.

Tôi đã tạo một thử nghiệm mẫu nhỏ cho việc này

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

Chương trình thứ hai không tạo ra bất kỳ đầu ra nào, nhưng tôi mong đợi có một tập hợp mới (chúng ta hãy gọi nó s3) với các giá trị sau:

s3 = [ 1 , 3 ]

Thay vào đó, tôi gặp lỗi:

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

Điều tôi hiểu từ lỗi này là không có định nghĩa nào trong set_intersectionđó chấp nhận Rb_tree_const_iterator<int>làm tham số.

Hơn nữa, tôi cho rằng std::set.begin()phương thức trả về một đối tượng thuộc loại như vậy,

có cách nào tốt hơn để tìm giao của hai std::settrong C ++ không? Tốt hơn là một chức năng tích hợp sẵn?

Cảm ơn rất nhiều!


"Tôi mong đợi để có một bộ mới (hãy gọi nó là s3)" Nhưng bạn không, và bạn đã không. Tôi không hiểu bạn mong đợi kết quả sẽ đi đến đâu. Ngoài ra, bạn đã không đọc tài liệu để tìm hiểu những đối số nào cần vượt qua.
Lightness Races in Orbit

Câu trả lời:


113

Bạn đã không cung cấp một trình lặp đầu ra cho set_intersection

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

Khắc phục điều này bằng cách làm một cái gì đó như

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

Bạn cần một std::inserttrình lặp vì tập hợp này hiện đang trống. Chúng tôi không thể sử dụng back_ hoặc front_inserter vì set không hỗ trợ các hoạt động đó.


70
Tôi muốn hiểu tại sao một hoạt động cơ bản như vậy trên các tập hợp lại yêu cầu một câu thần chú dài dòng phức tạp như vậy. Tại sao không phải là một set<T>& set::isect(set<T>&)phương pháp đơn giản , đó là những người cần thiết? (Tôi muốn yêu cầu một set<T>& set::operator^(set<T>&), nhưng đó là khả năng một cây cầu quá xa.)
Ryan V. Bissell

3
@ RyanV.Bissell đây là một thiết kế tương tự với hầu hết các thuật toán về tính <algorithm>nhất quán nếu không có gì khác. Tôi cho rằng phong cách này cũng mang lại cho bạn sự linh hoạt. Và cho phép các bí danh được sử dụng với một số vùng chứa, mặc dù điều đó có thể không xảy ra ở đây .. Ngoài ra, chữ ký của bạn có thể không hoạt động, bạn có thể cần trả về một giá trị. Và rằng trong những ngày trước khi sao chép ngữ nghĩa sẽ là một bản sao kép, tôi nghĩ. Tôi havent thực hiện c ++ trong một thời gian ngay bây giờ để thực hiện việc này với một nhúm hoặc 3 muối
Karthik T

4
Tôi vẫn coi mình là một người mới làm quen với STL, vì vậy việc áp dụng các hạt muối cũng được áp dụng. Cửa sổ chỉnh sửa nhận xét của tôi đã hết hạn, vì vậy tôi không thể sửa lỗi giả mạo trả về theo tham chiếu. Nhận xét của tôi không phải là một lời phàn nàn về tính nhất quán, mà là một câu hỏi trung thực về lý do tại sao cú pháp này nhất thiết phải có vị đắng như vậy. Có lẽ tôi nên đặt câu hỏi này thành một Câu hỏi SO.
Ryan V. Bissell

3
Trên thực tế, hầu hết C ++ std lib được thiết kế theo kiểu khó hiểu này. Mặc dù sự sang trọng của thiết kế là rõ ràng (tính chung chung, nhưng không chỉ), sự phức tạp của API có tác động tàn phá (chủ yếu là do mọi người tiếp tục sáng tạo lại bánh xe vì họ không thể sử dụng những thứ đi kèm với trình biên dịch của họ). Trong một thế giới khác, các nhà thiết kế sẽ bị tát vì đã ưu ái niềm vui của họ hơn là của người dùng. Trong thế giới này ... tốt, ít nhất chúng ta có StackOverflow.

3
Đây là một "cú pháp chung" - bạn cũng có thể thực hiện set_intersection trên một vectơ và trên một danh sách và lưu trữ kết quả vào một deque, và bạn sẽ có thể thực hiện điều này một cách hiệu quả (tất nhiên, vấn đề của bạn là phải quan tâm đến cả hai vùng chứa nguồn được sắp xếp trước khi gọi cái này). Tôi thấy nó không tệ, điều duy nhất tôi gặp vấn đề là có thể có một phương thức vùng setchứa giao nhau với một tập hợp khác. Chủ đề về việc chuyển một vùng chứa thay vì .begin()- .end()là một điều khác - điều này sẽ được sửa khi C ++ có khái niệm.
Ethouris

25

Hãy xem mẫu trong liên kết: http://en.cppreference.com/w/cpp/algorithm/set_intersection

Bạn cần một vùng chứa khác để lưu trữ dữ liệu giao lộ, mã bên dưới giả sử hoạt động:

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

6
back_inserterkhông hoạt động với setnhư setkhông có push_backchức năng.
Jack Aidley

6

Xem std :: set_intersection . Bạn phải thêm một trình lặp đầu ra, nơi bạn sẽ lưu trữ kết quả:

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

Xem Ideone để biết danh sách đầy đủ.


3
Lưu ý rằng back_inserter sẽ không hoạt động nếu bạn muốn kết quả cũng là một tập hợp, khi đó bạn cần std :: insertter như Karthik đã sử dụng.
Joseph Garvin

4

Chỉ cần bình luận ở đây. Tôi nghĩ rằng đã đến lúc thêm phép toán union, crossect vào giao diện thiết lập. Hãy đề xuất điều này trong các tiêu chuẩn tương lai. Tôi đã sử dụng std trong một thời gian dài, mỗi lần tôi sử dụng thao tác thiết lập tôi ước std tốt hơn. Đối với một số hoạt động tập hợp phức tạp, như giao nhau, bạn có thể đơn giản (dễ dàng hơn?) Sửa đổi mã sau:

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

được sao chép từ http://www.cplusplus.com/reference/algorithm/set_intersection/

Ví dụ: nếu đầu ra của bạn là một tập hợp, bạn có thể output.insert (* first1). Hơn nữa, hàm của bạn có thể không được tạo khuôn mẫu, nếu mã của bạn có thể ngắn hơn so với việc sử dụng hàm std set_intersection thì hãy tiếp tục với nó.

Nếu bạn muốn kết hợp hai tập hợp, bạn có thể chỉ cần setA.insert (setB.begin (), setB.end ()); Điều này đơn giản hơn nhiều so với phương thức set_union. Tuy nhiên, điều này sẽ không hoạt động với vector.


4

Nhận xét đầu tiên (được bình chọn tốt) của câu trả lời được chấp nhận phàn nàn về một toán tử bị thiếu cho các hoạt động tập hợp std hiện có.

Một mặt, tôi hiểu việc thiếu các toán tử như vậy trong thư viện chuẩn. Mặt khác, có thể dễ dàng thêm chúng (vì niềm vui cá nhân) nếu muốn. Tôi quá tải

  • operator *() cho sự giao nhau của các bộ
  • operator +() cho liên hiệp các bộ.

Mẫu 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;
}

Tổng hợp và thử nghiệm:

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

$ 

Điều tôi không thích là bản sao của các giá trị trả về trong các toán tử. Có thể là, điều này có thể được giải quyết bằng cách sử dụng nhiệm vụ di chuyển nhưng điều này vẫn nằm ngoài khả năng của tôi.

Do kiến ​​thức hạn chế của tôi về các ngữ nghĩa chuyển động "mới lạ" này, tôi lo ngại về việc trả về toán tử có thể gây ra các bản sao của các tập hợp được trả về. Olaf Dietsche chỉ ra rằng những mối quan tâm này là không cần thiết vì std::setđã được trang bị hàm tạo / gán di chuyển.

Mặc dù tôi tin anh ấy, nhưng tôi đang nghĩ cách kiểm tra điều này (ví dụ như "tự thuyết phục"). Trên thực tế, nó là khá dễ dàng. Vì các mẫu phải được cung cấp trong mã nguồn, bạn có thể chỉ cần thực hiện qua trình gỡ lỗi. Vì vậy, tôi đặt một điểm break ngay tại return s;của operator *()và tiến hành với single bước mà pha chì cho tôi ngay lập tức vào std::set::set(_myt&& _Right): et thì đấy - các nhà xây dựng di chuyển. Cảm ơn, Olaf, vì sự chú ý (của tôi).

Để hoàn thiện, tôi cũng đã triển khai các toán tử gán tương ứng

  • operator *=() cho giao điểm "phá hủy" của các tập hợp
  • operator +=() cho liên hiệp các tập hợp "phá hoại".

Mẫu 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;
}

Tổng hợp và thử nghiệm:

$ 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đã triển khai hàm khởi tạo di chuyển và toán tử gán cần thiết, vì vậy không cần phải lo lắng về điều đó. Ngoài ra trình biên dịch rất có thể sử dụng tối ưu hóa giá trị trả về
Olaf Dietsche

@OlafDietsche Cảm ơn bình luận của bạn. Tôi đã kiểm tra điều này và cải thiện câu trả lời tương ứng. Về RVO, tôi đã có một số cuộc thảo luận nhất định với các đồng nghiệp của mình cho đến khi tôi cho họ thấy trong trình gỡ lỗi của VS2013 rằng điều đó không xảy ra (ít nhất là trong nền tảng devel. Của chúng tôi). Trên thực tế, nó không quan trọng ngoại trừ nếu mã là hiệu suất quan trọng. Trong trường hợp thứ hai, bây giờ tôi không dựa vào RVO. (Đó là thực sự không phải là khó khăn trong C ++ ...)
Scheff

@Scheff cũng Scheff (không phải Bose), giải thích tốt.
JeJo

Ngay cả bây giờ sự hỗ trợ của VS đối với sự tách rời được đảm bảo của C ++ 17 là rất tồi tệ.
Các cuộc đua ánh sáng trong quỹ đạo
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.