C ++ 'da iki std :: setin kesişim noktası nasıl bulunur?


93

C ++ 'da iki std :: set arasındaki kesişimi bulmaya çalışıyorum, ancak bir hata alıyorum.

Bunun için küçük bir örnek test oluşturdum

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

İkinci program herhangi bir çıktı üretmiyor, ancak s3aşağıdaki değerlerle yeni bir set (hadi diyelim ) olmasını bekliyorum:

s3 = [ 1 , 3 ]

Bunun yerine şu hatayı alıyorum:

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

Bu hatadan anladığım şey, parametre olarak set_intersectionkabul eden bir tanımın olmamasıdır Rb_tree_const_iterator<int>.

Ayrıca, std::set.begin()yöntemin bu tür bir nesne döndürdüğünü düşünüyorum,

std::setC ++ ' da ikinin kesişimini bulmanın daha iyi bir yolu var mı? Tercihen yerleşik bir işlev mi?

Çok teşekkürler!


"Yeni bir setim olmasını bekliyorum (buna s3 diyelim)" Ama yoksun ve yapmadın. Sonuçların nereye gitmesini beklediğinizi anlamıyorum. Ayrıca hangi argümanların geçmesi gerektiğini bulmak için belgeleri okumadınız.
Orbit'te Hafiflik Yarışları

Yanıtlar:


113

Set_intersection için bir çıktı yineleyici sağlamadınız

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

Gibi bir şey yaparak bunu düzeltin

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

std::insertKüme şu anda boş olduğu için bir yineleyiciye ihtiyacınız var . Set bu işlemleri desteklemediği için back_ veya front_inserter kullanamayız.


72
Setler üzerindeki bu kadar temel bir işlemin neden bu kadar arcanlı derecede ayrıntılı bir büyü gerektirdiğini anlamak istiyorum. Neden set<T>& set::isect(set<T>&)gerekli olan basit bir yöntem değil ? (Bir tane isterdim set<T>& set::operator^(set<T>&), ama bu muhtemelen çok uzak bir köprü.)
Ryan V. Bissell

3
@ RyanV.Bissell bu, neredeyse tüm algoritmalara benzer bir tasarım, <algorithm>başka hiçbir şey olmasa bile tutarlı bir şekilde. Bu tarz da sanırım size esneklik sağlıyor. Ve algosların birkaç kapsayıcıyla kullanılmasına izin verir, ancak bu burada olmayabilir. Ayrıca imzanız çalışmayabilir, muhtemelen bir değer döndürmeniz gerekir. Ve sanırım kopya semantiği önceki günlerde çift kopya olurdu. Bir süredir c ++ yapmadım, bu yüzden bunu bir tutam veya 3 tuzla alın
Karthik T

4
Hala kendimi bir STL acemi olarak görüyorum, bu nedenle tuz tanelerinin uygulanması da geçerli. Yorum düzenleme penceremin süresi doldu, bu yüzden referansa göre dönüş hatalarını düzeltemiyorum. Yorumum tutarlılık hakkında bir şikayet değil, bu sözdiziminin neden bu kadar acı tadı olması gerektiğine dair dürüst bir soruydu. Belki de bunu bir SO Soru haline getirmeliyim.
Ryan V. Bissell

3
Aslında, C ++ std lib'nin çoğu bu belirsiz tarzda tasarlanmıştır. Tasarımın zarafeti bariz olsa da (genellik, ancak yalnızca değil), API'nin karmaşıklığı yıkıcı etkilere sahiptir (çoğunlukla insanlar, derleyicileriyle birlikte gelenleri kullanamadıkları için tekerlekleri yeniden icat etmeye devam ettikleri için). Başka bir dünyada, tasarımcılar kendi zevklerini kullanıcılarınınkine tercih ettikleri için tokatlanırlardı. Bu dünyada ... en azından StackOverflow'a sahibiz.

3
Bu bir "genel sözdizimi" - ayrıca bir vektör üzerinde ve bir listede set_intersection yapabilir ve sonuçları bir deque olarak saklayabilirsiniz ve bu şeyi bile verimli bir şekilde yapabilmelisiniz (tabii ki, her ikisine de dikkat etmek sizin probleminiz kaynak kapsayıcılar bunu çağırmadan önce sıralanır). Kötü bulmuyorum, tek sorunum setbaşka bir kümeyle kesişen bir konteyner yöntemi olabileceğidir . Bir kap yerine geçme konu .begin()- .end()başka bir şeydir - C ++ kavramları vardır bir kere bu düzeltilecektir.
Ethouris

25

Bağlantıdaki örneğe bir göz atın: http://en.cppreference.com/w/cpp/algorithm/set_intersection

Kavşak verilerini depolamak için başka bir kaba ihtiyacınız var, aşağıdaki kodun çalıştığını varsayalım:

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

6
back_inserterile çalışmıyor setolarak sethiçbir sahiptir push_backişlevi.
Jack Aidley

6

Std :: set_intersection'a bakın . Sonucu depolayacağınız bir çıktı yineleyicisi eklemelisiniz:

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

Tam liste için Ideone'a bakın .


3
Sonucun aynı zamanda bir küme olmasını istiyorsanız back_inserter çalışmayacaktır, o zaman Karthik'in kullandığı gibi std :: inserter'a ihtiyacınız olacaktır.
Joseph Garvin

4

Sadece buraya yorum yapın. Sanırım set arayüzüne union, intersect işlemi ekleme zamanı. Bunu gelecekteki standartlarda önerelim. Std'yi uzun süredir kullanıyorum, set işlemini her kullandığımda std'nin daha iyi olmasını diledim. Kesişim gibi bazı karmaşık küme işlemleri için, aşağıdaki kodu basitçe (daha kolay?) Değiştirebilirsiniz:

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_intersection/ adresinden kopyalandı

Örneğin, çıktınız bir küme ise, çıktı.insert (* ilk1) yapabilirsiniz. Ayrıca, işleviniz şablon haline getirilmeyebilir. Eğer kodunuz std set_interection işlevini kullanmaktan daha kısa olabilirse, onunla devam edin.

Eğer iki kümeden oluşan bir birleşim yapmak istiyorsanız, basitçe A.insert (setB.begin (), setB.end ()); Bu, set_union yönteminden çok daha basittir. Ancak bu vektör ile çalışmayacaktır.


4

Kabul edilen cevabın ilk (iyi oylanmış) yorumu , mevcut standart küme işlemleri için eksik bir operatörden şikayet eder.

Bir yandan, standart kitaplıkta bu tür operatörlerin eksikliğini anlıyorum. Öte yandan, istenirse onları eklemek (kişisel zevk için) kolaydır. Aşırı yükledim

  • operator *() setlerin kesişimi için
  • operator +() setlerin birliği için.

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

Derlendi ve test edildi:

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

$ 

Sevmediğim şey, operatörlerde dönüş değerlerinin kopyasıdır. Belki bu, taşıma ataması kullanılarak çözülebilir, ancak bu hala becerilerimin ötesinde.

Bu "yeni fantezi" hareket semantiği hakkındaki sınırlı bilgim nedeniyle, dönen kümelerin kopyalarına neden olabilecek operatör geri dönüşleri konusunda endişeliydim. Olaf Dietschestd::set , halihazırda taşıma yapıcı / görevlendirme ile donatılmış olduğu için bu endişelerin gereksiz olduğuna dikkat çekti .

Ona inanmama rağmen, bunu nasıl kontrol edeceğimi düşünüyordum ("kendi kendine inandırıcı" gibi bir şey için). Aslında oldukça kolaydır. Şablonların kaynak kodda sağlanması gerektiğinden, hata ayıklayıcıyla adım adım ilerleyebilirsiniz. Böylece, doğru bir kırılma noktası yerleştirilir return s;arasında operator *()ve içine hemen beni kurşunlu tek adımla devam std::set::set(_myt&& _Right): et voilà - hamle yapıcısı. Teşekkürler Olaf, aydınlanmam için.

Eksiksizlik adına, ilgili atama operatörlerini de uyguladım

  • operator *=() setlerin "yıkıcı" kesişimi için
  • operator +=() setlerin "yıkıcı" birliği için.

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

Derlendi ve test edildi:

$ 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::setzaten gerekli taşıma oluşturucusunu ve atama operatörünü uygular, bu yüzden endişelenmenize gerek yok. Ayrıca derleyici büyük olasılıkla geri dönüş değeri optimizasyonunu kullanıyor
Olaf Dietsche

@OlafDietsche Yorumunuz için teşekkürler. Bunu kontrol ettim ve sırasıyla cevabı geliştirdim. RVO hakkında, VS2013 hata ayıklayıcısında bunun olmadığını gösterene kadar meslektaşlarımla zaten bazı tartışmalar yaptım (en azından geliştirme platformumuzda). Aslında, kodun performans açısından kritik olması dışında o kadar da önemli değil. İkinci durumda, şimdilik RVO'ya güvenmemeye devam ediyorum. (Yani ... ++ değil aslında C olduğunu zor)
Scheff

@Scheff iyi Scheff (Bose değil), güzel açıklama.
JeJo

Şimdi bile VS'nin C ++ 17'nin garantili seçimine verdiği destek üzücü.
Orbit'te Hafiflik Yarışları
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.